Unverified Commit 6dacac73 authored by Francesc Guasch's avatar Francesc Guasch Committed by GitHub
Browse files

Feature #947 node disable (#950)

* test(nodes): shutdown domains on disabled node

issue #947

* feature(frontend): shutdown machines on disabling node

issue #947

* feature(frontend): confirm node disable

Confirm the user wants to disable a node with active machines
The layout changed from tables to divs for adding modal dialogs

issue #947

* wip(frontend): show the names of the machines in node

issue #947

* feature(nodes): start in active nodes only

Also make sure prepare base hasn't already run

issue #947

* wip(frontend): removed debug
parent d1a3259d
......@@ -308,7 +308,7 @@ sub _start_preconditions{
# if it is a clone ( it is not a base )
if ($self->id_base) {
# $self->_set_last_vm(1)
if ( !$self->is_local && !$self->_vm->ping ) {
if ( !$self->is_local && ( !$self->_vm->enabled || !$self->_vm->ping) ) {
my $vm_local = $self->_vm->new( host => 'localhost' );
$self->_set_vm($vm_local, 1);
}
......@@ -819,7 +819,7 @@ sub open($class, @args) {
}
}
my $vm_local;
if (!$vm || !$vm->is_active || !$vm->enabled) {
if ( !$vm || !$vm->is_active ) {
$vm_local = {};
my $vm_class = "Ravada::VM::".$row->{vm};
bless $vm_local, $vm_class;
......@@ -1638,6 +1638,7 @@ sub _pre_shutdown {
$self->resume(user => Ravada::Utils::user_daemon);
}
$self->list_disks;
}
sub _post_shutdown {
......@@ -2811,7 +2812,7 @@ sub set_base_vm($self, %args) {
$self->_set_base_vm_db($vm->id, $value);
$self->remove_base($user);
} else {
$self->prepare_base($user);
$self->prepare_base($user) if !$self->is_base();
$request->status("working","Preparing base") if $request;
}
} elsif ($value) {
......
......@@ -503,14 +503,14 @@ sub _list_bases_vm($self, $id_node) {
sub _list_machines_vm($self, $id_node) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT d.id FROM domains d"
"SELECT d.id, name 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);
while ( my ($id_domain, $name) = $sth->fetchrow ) {
push @bases,({ id => $id_domain, name => $name });
}
$sth->finish;
return \@bases;
......
......@@ -289,22 +289,39 @@ ravadaApp.directive("solShowMachine", swMach)
function manage_nodes($scope, $http, $interval) {
$scope.list_nodes = function() {
$http.get('/list_nodes.json').then(function(response) {
$scope.nodes = response.data;
});
if (!$scope.modal_open) {
$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.modal_open = false;
$http.get('/node/enable/'+id+'.json').then(function() {
$scope.list_nodes();
});
};
$scope.node_disable=function(id) {
$http.get('/node/disable/'+id+'.json');
$scope.list_nodes();
$scope.modal_open = false;
$http.get('/node/disable/'+id+'.json').then(function() {
$scope.list_nodes();
});
};
$scope.node_remove=function(id) {
$http.get('/node/remove/'+id+'.json');
$scope.list_nodes();
};
$scope.confirm_disable_node = function(id , n_machines) {
if (n_machines > 0 ) {
$scope.modal_open = true;
$('#confirm_disable_'+id).modal({show:true})
} else {
$scope.node_disable(id);
}
};
$scope.modal_open = false;
$scope.list_nodes();
$interval($scope.list_nodes,30 * 1000);
};
......
......@@ -281,6 +281,11 @@ get '/node/enable/(:id).json' => sub {
get '/node/disable/(:id).json' => sub {
my $c = shift;
return access_denied($c) if !$USER->is_admin;
my $machines = $RAVADA->_list_machines_vm($c->stash('id'));
for ( @$machines ) {
my $req = Ravada::Request->shutdown_domain( uid => $USER->id , id_domain => $_->{id} );
}
return $c->render(json => {enabled => $RAVADA->enable_node($c->stash('id'),0)});
};
get '/node/remove/(:id).json' => sub {
......
use warnings;
use strict;
use Carp qw(confess);
use Data::Dumper;
use Digest::MD5;
use Test::More;
use lib 't/lib';
use Test::Ravada;
no warnings "experimental::signatures";
use feature qw(signatures);
use_ok('Ravada');
init();
##################################################################################
sub test_disable_node($vm, $node) {
my $base = create_domain($vm);
$base->prepare_base(user_admin);
$base->set_base_vm(user => user_admin, node => $node);
my $clone = $base->clone(user => user_admin, name => new_domain_name);
$clone->migrate($node);
$clone->start(user_admin);
$node->is_enabled(0);
is($clone->_vm->name, $node->name);
my $timeout = 20;
my $req = Ravada::Request->shutdown_domain(
uid => user_admin->id
,timeout => 20
, id_domain => $clone->id
);
rvd_back->_process_requests_dont_fork();
is($req->status,'done');
is($req->error,'');
for ( 0 .. $timeout + 1 ) {
last if !$clone->is_active;
sleep 1;
rvd_back->_process_requests_dont_fork();
}
is($clone->is_active, 0 );
if ( $vm->type eq 'KVM' ) {
is($clone->domain->is_active, 0);
} else {
diag("TODO check internal is active ".$vm->type);
}
$clone->remove(user_admin);
$base->remove(user_admin);
}
##################################################################################
clean();
$Ravada::Domain::MIN_FREE_MEMORY = 256 * 1024;
for my $vm_name ( 'KVM', 'Void') {
my $vm;
eval { $vm = rvd_back->search_vm($vm_name) };
SKIP: {
my $msg = "SKIPPED: $vm_name virtual manager not found ".($@ or '');
my $REMOTE_CONFIG = remote_config($vm_name);
if (!keys %$REMOTE_CONFIG) {
my $msg = "skipped, missing the remote configuration for $vm_name in the file "
.$Test::Ravada::FILE_CONFIG_REMOTE;
diag($msg);
skip($msg,10);
}
if ($vm && $>) {
$msg = "SKIPPED: Test must run as root";
$vm = undef;
}
diag($msg) if !$vm;
skip($msg,10) if !$vm;
diag("Testing remote node in $vm_name");
my $node = remote_node($vm_name) or next;
clean_remote_node($node);
ok($node->vm,"[$vm_name] expecting a VM inside the node") or do {
remove_node($node);
next;
};
is($node->is_local,0,"Expecting ".$node->name." ".$node->ip." is remote" ) or BAIL_OUT();
test_disable_node($vm,$node);
NEXT:
clean_remote_node($node);
remove_node($node);
}
}
END: {
clean();
done_testing();
}
......@@ -14,56 +14,84 @@
</h2>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><%=l 'Type' %></th>
<th><%=l 'Name' %></th>
<th><%=l 'Address' %></th>
<th><%=l 'Bases' %></th>
<th><%=l 'Machines' %></th>
<th><%=l 'Status' %></th>
<th><%=l 'Action' %></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="node in nodes">
<td>{{node.type}}</td>
<td><span class="badge badge-info">{{node.name}}</span></td>
<td>{{node.hostname}}</td>
<td>{{node.bases.length}}</td>
<td>{{node.machines.length}}</td>
<td>
<div class="row">
<div class="col-md-1"><b><%=l 'Type' %></b></div>
<div class="col-md-2"><b><%=l 'Name' %></b></div>
<div class="col-md-2"><b><%=l 'Address' %></b></div>
<div class="col-md-1"><b><%=l 'Bases' %></b></div>
<div class="col-md-1"><b><%=l 'Machines' %></b></div>
<div class="col-md-2"><b><%=l 'Status' %></b></div>
<div class="col-md-2"><b><%=l 'Action' %></b></div>
</div>
<div class="row" ng-repeat="node in nodes | orderBy:'is_local':true">
<div class="col-md-1">{{node.type}}</div>
<div class="col-md-2">{{node.name}}</div>
<div class="col-md-2">{{node.hostname}}</div>
<div class="col-md-1">{{node.bases.length}}</div>
<div class="col-md-1">{{node.machines.length}}</div>
<div class="col-md-2">
<span ng-show="{{node.is_active}}"
class="badge badge-success"><%=l 'Active' %></span>
<span ng-show="{{!node.is_active}}"
class="badge badge-danger"><%=l 'Shutdown' %></span>
<br/>
<span ng-show="!node.enabled"
class="badge badge-warning"><%=l 'Disabled' %></span>
</td>
<td>
<a type="button" class="btn btn-success btn-sm text-white"
class="label label-warning"><%=l 'Disabled' %></span>
</div>
<div class="col-md-2" ng-show="node.is_local">
<%= l 'This node is local' %>
</div>
<div class="col-md-2" ng-show="!node.is_local">
<button type="button" class="btn btn-success btn-sm"
ng-click="node_enable(node.id)"
ng-disabled="node.enabled"
title="<%=l 'Enable' %>">
<i class="fas fa-check"></i>
</a>
<a type="button" class="btn btn-warning btn-sm"
ng-click="node_disable(node.id)"
ng-disabled="!node.enabled"
<i class="fa fa-check"></i>
</button>
<button type="button" class="btn btn-warning btn-sm"
ng-click="confirm_disable_node(node.id, node.machines.length)"
ng-disabled="!node.enabled || node.is_local"
title="<%=l 'Disable' %>">
<i class="fas fa-window-close"></i>
</a>
<a type="button" class="btn btn-danger btn-sm text-white {{node.action_remove}}"
<i class="fa fa-window-close"></i>
</button>
<button type="button" class="btn btn-danger btn-sm {{node.action_remove}}"
ng-click="node_remove(node.id)"
title="<%=l 'Remove' %>">
<i class="far fa-trash-alt"></i>
</a>
</td>
</tr>
</tbody>
</table>
<i class="glyphicon glyphicon-trash"></i>
</button>
</div>
<div class="modal" tabindex="-1" role="dialog" id="confirm_disable_{{node.id}}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Confirm disable node</h4>
</div>
<div class="modal-body">
<p>Disabling this node will shut all the
{{node.machines.length}}
machines down.
Are you sure ?</p>
<ul>
<li ng-repeat="machine in node.machines">
{{machine.name}}
</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal"
ng-click="modal_open=false"><%=l 'No' %></button>
<button type="button" class="btn btn-default"
data-dismiss="modal"
ng-click="node_disable(node.id)"><%=l 'Yes' %></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
......
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