Commit e567a930 authored by Francesc Guasch's avatar Francesc Guasch
Browse files

wip(nodes): node management in frontend

issue #530
parent ebe221eb
......@@ -738,10 +738,27 @@ sub _update_data {
sub _add_indexes($self) {
return if $CONNECTOR->dbh->{Driver}{Name} !~ /mysql/i;
$self->_add_indexes_vms();
$self->_add_indexes_domains();
$self->_add_indexes_requests();
}
sub _add_indexes_vms($self) {
my %index;
my $sth = $CONNECTOR->dbh->prepare("show index from vms");
$sth->execute;
while (my $row = $sth->fetchrow_hashref) {
$index{$row->{Key_name}}->{$row->{Column_name}}++;
}
my $index_name = 'hostname_vm_type';
return if $index{$index_name};
warn "INFO: Adding index to vms: $index_name";
$sth = $CONNECTOR->dbh->prepare("ALTER TABLE vms add unique $index_name"
." (hostname, vm_type)");
$sth->execute;
}
sub _add_indexes_domains($self) {
my %index;
my $sth = $CONNECTOR->dbh->prepare("show index from domains");
......@@ -755,6 +772,7 @@ sub _add_indexes_domains($self) {
."(id_base)");
$sth->execute;
}
sub _add_indexes_requests($self) {
my %index;
my $sth = $CONNECTOR->dbh->prepare("show index from requests");
......@@ -1041,6 +1059,7 @@ sub _upgrade_tables {
$self->_upgrade_table('vms','cached_active_time',"integer DEFAULT 0");
$self->_upgrade_table('vms','public_ip',"varchar(128) DEFAULT NULL");
$self->_upgrade_table('vms','is_active',"int DEFAULT 0");
$self->_upgrade_table('vms','enabled',"int DEFAULT 1");
$self->_upgrade_table('vms','min_free_memory',"text DEFAULT NULL");
$self->_upgrade_table('vms', 'max_load', 'int not null default 10');
......@@ -2595,6 +2614,9 @@ sub _refresh_active_domains($self, $request=undef) {
my %active_domain;
my %active_vm;
for my $vm ($self->list_vms) {
$request->status('working',"checking active domains on ".$vm->name)
if $request;
next if !$vm->enabled();
if ( !$vm->is_active ) {
$active_vm{$vm->id} = 0;
$vm->disconnect();
......@@ -2605,7 +2627,7 @@ sub _refresh_active_domains($self, $request=undef) {
my $domain = $vm->search_domain_by_id($id_domain);
$self->_refresh_active_domain($vm, $domain, \%active_domain) if $domain;
} else {
for my $domain ($vm->list_domains( active => 1)) {
for my $domain ($vm->list_domains( )) {
next if $active_domain{$domain->id};
next if $domain->is_hibernated;
$self->_refresh_active_domain($vm, $domain, \%active_domain);
......@@ -2825,8 +2847,10 @@ sub vm($self) {
$sth->execute();
my @vms;
while ( my ($id) = $sth->fetchrow()) {
my $vm = Ravada::VM->open($id);
eval { $vm->vm };
my $vm;
eval {
$vm = Ravada::VM->open($id);
};
if ( $@ ) {
warn $@;
next;
......
......@@ -328,7 +328,7 @@ sub _search_already_started($self) {
my %started;
while (my ($id) = $sth->fetchrow) {
my $vm = Ravada::VM->open($id);
next if !$vm->is_active;
next if !$vm->is_enabled || !$vm->is_active;
my $domain = $vm->search_domain($self->name);
next if !$domain;
......@@ -1153,7 +1153,7 @@ sub _after_remove_domain {
my $self = shift;
my ($user, $cascade) = @_;
$self->_remove_iptables(user => $user);
$self->_remove_iptables( );
$self->_remove_domain_cascade($user) if !$cascade;
if ($self->is_known && $self->is_base) {
......@@ -1600,7 +1600,7 @@ sub _post_shutdown {
my %arg = @_;
my $timeout = delete $arg{timeout};
$self->_remove_iptables(%arg);
$self->_remove_iptables();
$self->_data(status => 'shutdown')
if $self->is_known && !$self->is_volatile && !$self->is_active;
......@@ -1724,8 +1724,12 @@ sub _remove_iptables {
my %args = @_;
my $user = delete $args{user};
my $port = delete $args{port};
my $id_vm = delete $args{id_vm};
if($port && !$id_vm) {
$id_vm = $self->_data('id_vm');
}
delete $args{request};
......@@ -1737,8 +1741,7 @@ sub _remove_iptables {
);
my @iptables;
push @iptables, ( $self->_active_iptables(id_domain => $self->id)) if $self->is_known();
push @iptables, ( $self->_active_iptables(user => $user) ) if $user;
push @iptables, ( $self->_active_iptables(port => $port) ) if $port;
push @iptables, ( $self->_active_iptables(port => $port, id_vm => $id_vm) ) if $port;
my %rule;
for my $row (@iptables) {
......
......@@ -402,6 +402,31 @@ sub domain_exists {
return 1;
}
=head2 node_exists
Returns true if the node name exists
if ($rvd->node('node_name')) {
...
}
=cut
sub node_exists {
my $self = shift;
my $name = shift;
my $sth = $CONNECTOR->dbh->prepare(
"SELECT id FROM vms"
." WHERE name=? "
);
$sth->execute($name);
my ($id) = $sth->fetchrow;
$sth->finish;
return 0 if !defined $id;
return 1;
}
=head2 list_vm_types
Returns a reference to a list of Virtual Machine Managers known by the system
......@@ -428,7 +453,7 @@ Returns a list of Virtual Managers
sub list_vms($self, $type=undef) {
my $sql = "SELECT id,name,hostname,is_active, vm_type FROM vms ";
my $sql = "SELECT id,name,hostname,is_active, vm_type, enabled FROM vms ";
my @args = ();
if ($type) {
......@@ -437,12 +462,13 @@ sub list_vms($self, $type=undef) {
$type2 = 'qemu' if $type eq 'KVM';
@args = ( $type, $type2);
}
my $sth = $CONNECTOR->dbh->prepare($sql);
my $sth = $CONNECTOR->dbh->prepare($sql." ORDER BY vm_type,name");
$sth->execute(@args);
my @list;
while (my $row = $sth->fetchrow_hashref) {
$self->_list_bases_vm($row);
$row->{bases}= $self->_list_bases_vm($row->{id});
$row->{machines}= $self->_list_machines_vm($row->{id});
$row->{type} = $row->{vm_type};
delete $row->{vm_type};
lock_hash(%$row);
......@@ -452,20 +478,36 @@ sub list_vms($self, $type=undef) {
return @list;
}
sub _list_bases_vm($self, $node) {
sub _list_bases_vm($self, $id_node) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT d.id FROM domains d,bases_vm bv"
." WHERE d.is_base=1"
." AND d.id = bv.id_domain "
." AND bv.id_vm=?"
);
$sth->execute($node->{id});
my @bases;
$sth->execute($id_node);
while ( my ($id_domain) = $sth->fetchrow ) {
$node->{"base_".$id_domain} =0;
push @bases,($id_domain);
}
$sth->finish;
return \@bases;
}
sub _list_machines_vm($self, $id_node) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT d.id FROM domains d"
." WHERE d.status='active'"
." AND d.id_vm=?"
);
my @bases;
$sth->execute($id_node);
while ( my ($id_domain) = $sth->fetchrow ) {
push @bases,($id_domain);
}
$sth->finish;
return \@bases;
}
=head2 list_iso_images
Returns a reference to a list of the ISO images known by the system
......@@ -967,6 +1009,36 @@ sub disconnect_vm {
%VM = ();
}
=head2 enable_node
Enables or disables a node
$rvd->enable_node($id_node, $value);
Returns true if the node is enabled, false otherwise.
=cut
sub enable_node($self, $id_node, $value) {
my $sth = $CONNECTOR->dbh->prepare("UPDATE vms SET enabled=? WHERE id=?");
$sth->execute($value, $id_node);
$sth->finish;
return $value;
}
sub add_node($self,%arg) {
my $sql = "INSERT INTO vms "
."("
.join(",",sort keys %arg)
.")"
." VALUES ( ".join(",", map { '?' } keys %arg).")";
my $sth = $CONNECTOR->dbh->prepare($sql);
$sth->execute(map { $arg{$_} } sort keys %arg );
$sth->finish;
}
=head2 version
Returns the version of the main module
......
......@@ -819,13 +819,18 @@ sub ping($self, $option=undef) {
warn "trying tcp" if $debug;
my $p = Net::Ping->new('tcp',2);
return 1 if $p->ping($self->host);
my $ping_ok;
eval { $ping_ok = $p->ping($self->host) };
warn $@ if $@;
return 1 if $ping_ok;
$p->close();
return if $>; # icmp ping requires root privilege
warn "trying icmp" if $debug;
$p= Net::Ping->new('icmp',2);
return 1 if $p->ping($self->host);
eval { $ping_ok = $p->ping($self->host) };
warn $@ if $@;
return 1 if $ping_ok;
return 0;
}
......@@ -881,6 +886,20 @@ sub _cached_active_time($self, $value=undef) {
return $self->_data('cached_active_time', $value);
}
=head2 enabled
Returns if the domain is enabled.
=cut
sub enabled($self) {
return $self->_data('enabled');
}
sub is_enabled($self) {
return $self->enabled();
}
=head2 remove
Remove the virtual machine manager.
......
......@@ -43,8 +43,10 @@ sub _connect {
|| $self->host eq '127.0.0.1'
|| $self->{_ssh};
my ($out, $err)
= $self->run_command("ls -l ".$self->dir_img." || mkdir -p ".$self->dir_img);
my ($out, $err);
eval {
($out, $err)= $self->run_command("ls -l ".$self->dir_img." || mkdir -p ".$self->dir_img);
};
warn "ERROR: error connecting to ".$self->host." $err" if $err;
return 0 if $err;
......
......@@ -6,6 +6,9 @@ ravadaApp.directive("solShowMachine", swMach)
.controller("machinesPage", machinesPageC)
.controller("usersPage", usersPageC)
.controller("messagesPage", messagesPageC)
.controller("manage_nodes",manage_nodes)
.controller("new_node", newNodeCtrl)
;
function swMach() {
return {
......@@ -284,4 +287,45 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.updatePromise = $interval($scope.updateMessages,3000);
};
function manage_nodes($scope, $http, $interval) {
$scope.list_nodes = function() {
$http.get('/list_nodes.json').then(function(response) {
$scope.nodes = response.data;
});
};
$scope.node_enable=function(id) {
$http.get('/node/enable/'+id+'.json');
$scope.list_nodes();
};
$scope.node_disable=function(id) {
$http.get('/node/disable/'+id+'.json');
$scope.list_nodes();
};
$scope.list_nodes();
$interval($scope.list_nodes,30 * 1000);
};
function newNodeCtrl($scope, $http, $interval) {
$http.get('/list_vm_types.json').then(function(response) {
$scope.backends = response.data;
$scope.backend = response.data[0];
});
$scope.validate_node_name = function() {
console.log($scope.name);
$http.get('/node/exists/'+$scope.name)
.then(duplicated_callback, unique_callback);
function duplicated_callback(response) {
if ( response.data ) {
$scope.name_duplicated=true;
} else {
$scope.name_duplicated=false;
}
};
function unique_callback() {
$scope.name_duplicated=false;
}
};
};
}());
......@@ -267,6 +267,29 @@ get '/list_vm_types.json' => sub {
$c->render(json => $RAVADA->list_vm_types);
};
get '/list_nodes.json' => sub {
my $c = shift;
$c->render(json => [$RAVADA->list_vms]);
};
get '/node/enable/(:id).json' => sub {
my $c = shift;
return access_denied($c) if !$USER->is_admin;
return $c->render(json => {enabled => $RAVADA->enable_node($c->stash('id'),1)});
};
get '/node/disable/(:id).json' => sub {
my $c = shift;
return access_denied($c) if !$USER->is_admin;
return $c->render(json => {enabled => $RAVADA->enable_node($c->stash('id'),0)});
};
any '/new_node' => sub {
my $c = shift;
return access_denied($c) if !$USER->is_admin;
return new_node($c);
};
get '/list_bases.json' => sub {
my $c = shift;
......@@ -498,6 +521,13 @@ get '/machine/exists/#name' => sub {
};
get '/node/exists/#name' => sub {
my $c = shift;
my $name = $c->stash('name');
return $c->render(json => $RAVADA->node_exists($name));
};
get '/machine/rename/#id/#value' => sub {
my $c = shift;
return access_denied($c) if !$USER->can_manage_machine($c->stash('id'));
......@@ -1067,9 +1097,6 @@ sub admin {
$c->stash(list_users => $RAVADA->list_users($c->param('name') ))
}
}
if ($page eq 'nodes') {
$c->stash(list_nodes => [$RAVADA->list_vms]);
}
if ($page eq 'machines') {
Ravada::Request->refresh_vms();
$c->stash(n_clones_hide => ($CONFIG_FRONT->{admin}->{hide_clones} or 10) );
......@@ -1083,6 +1110,9 @@ sub admin {
$c->stash( monitoring => 1 ) if $c->session('monitoring');
}
}
if ($page eq 'nodes') {
Ravada::Request->refresh_vms();
}
$c->render( template => 'main/admin_'.$page);
};
......@@ -1111,6 +1141,21 @@ sub new_machine {
);
};
sub new_node {
my $c = shift;
push @{$c->stash->{css}}, '/css/admin.css';
push @{$c->stash->{js}}, '/js/admin.js';
if ($c->param('_submit')) {
$c->req->params->remove('_submit');
my $pairs = $c->req->params()->pairs;
$RAVADA->add_node(@$pairs);
return $c->render(text => 'node created. <a href ="/admin/nodes/">nodes</a> ');
}
return $c->render(template => 'main/new_node');
}
sub req_new_domain {
my $c = shift;
my $name = $c->param('name');
......
<!DOCTYPE html>
<html>
<html ng-app="ravada.app">
%= include 'bootstrap/header'
<body id="page-top" data-spy="scroll" data-target=".navbar-fixed-top" role="document">
<div id="wrapper">
%= include 'bootstrap/navigation'
<div id="page-wrapper">
<div id="page-wrapper" ng-controller="manage_nodes">
<div id="admin-content">
<h2>Nodes</h2>
<div class="row">
<div class="col-md-8"><h2>Nodes</h2></div>
<div class="col-md-4" align="right">
<h2><a type="button"
class="btn btn-success" href="/new_node.html"><b>New Node</b></a>
</h2>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><%=l 'Type' %></th>
<th><%=l 'Name' %></th>
<th><%=l 'Host' %></th>
<th><%=l 'Address' %></th>
<th><%=l 'Bases' %></th>
<th><%=l 'Machines' %></th>
<th><%=l 'Status' %></th>
<th><%=l 'Action' %></th>
</tr>
</thead>
<tbody>
% for my $node (@$list_nodes) {
<tr>
<td><%= $node->{name} %></td>
<td><%= $node->{hostname} %></td>
<tr ng-repeat="node in nodes">
<td>{{node.type}}</td>
<td>{{node.name}}</td>
<td>{{node.hostname}}</td>
<td>{{node.bases.length}}</td>
<td>{{node.machines.length}}</td>
<td>
<span ng-show="{{node.is_active}}"
class="label label-success"><%=l 'Active' %></span>
<span ng-show="{{!node.is_active}}"
class="label label-danger"><%=l 'Shutdown' %></span>
<br/>
<span ng-show="!node.enabled"
class="label label-warning"><%=l 'Disabled' %></span>
</td>
<td>
<a type="button" class="btn btn-success btn-sm"
ng-click="node_enable(node.id)"
ng-show="!node.enabled"
title="<%=l 'Enable' %>">
<i class="fa fa-check"></i>
</a>
<a type="button" class="btn btn-warning btn-sm"
ng-click="node_disable(node.id)"
ng-show="node.enabled"
title="<%=l 'Disable' %>">
<i class="fa fa-window-close"></i>
</a>
</td>
</tr>
% }
</tbody>
</table>
</div>
</div>
</div>
%= include 'bootstrap/footer'
%= include 'bootstrap/scripts'
</body>
</html>
<!DOCTYPE html>
<html ng-app="ravada.app">
%= include 'bootstrap/header'
<body id="page-top" data-spy="scroll" data-target=".navbar-fixed-top" role="document">
<div id="wrapper">
%= include 'bootstrap/navigation'
<div id="page-wrapper" ng-controller="new_node">
<div class="panel panel-default">
<div class="panel-heading">
<h2><%=l 'New Node' %></h2>
</div>
<form name="new_nodeForm" role="form" method="post" novalidate
action="/new_node.html">
<div class="form-group row">
<label for="vm_type" class="col-lg-3 control-label"><%=l 'Backend' %> <a
title="Choose the virtualization type of the Node."><i class="fa fa-info-circle"></i></a></label>
<div class="col-lg-3">
<select class= "form-control"
name="vm_type"
ng-model="vm_type"
ng-options="item for item in backends track by item "
required=""
></select>
</div>
</div>
<div class="form-group row">
<label for="name" class="col-lg-3 control-label"><%=l 'Name' %></label>
<input class="col-lg-3" name="name" type="text"
ng-model="name"
ng-change="validate_node_name()"
required
>
</div>
<div class="form-group row">
<label for="hostname" class="col-lg-3 control-label"><%=l 'Address' %></label>
<input class="col-lg-3" name="hostname" type="text"
ng-model="hostname"
required>
</div>
<div ng-show="name_duplicated"
class="alert alert-warning fade in">
<%=l 'A node with that name already exists.' %>
</div>
<div class="form-group row">
<input type="submit" name="_submit"
ng-disabled="new_nodeForm.$invalid || name_duplicated"
>
</div>
</form>
</div>
</div><!-- page wrapper -->
</div><!-- wrapper -->
%= include 'bootstrap/footer'
%= include 'bootstrap/scripts'
</body>
</html>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment