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

Refactor admin machines (#1296)

* feature(backend): auto shutdown disconnected
* feature(frontend): do not fetch data if not changed

issue #1276 
parent dc3d3d4e
......@@ -877,7 +877,10 @@ sub _add_indexes($self) {
sub _add_indexes_generic($self) {
my %index = (
requests => [
domains => [
"index(date_changed)"
]
,requests => [
"index(status,at_time)"
,"index(id,date_changed,status,at_time)"
,"index(date_changed)"
......@@ -892,6 +895,7 @@ sub _add_indexes_generic($self) {
]
,messages => [
"index(id_request,date_send)"
,"index(date_changed)"
]
);
for my $table ( keys %index ) {
......@@ -1377,6 +1381,8 @@ sub _upgrade_tables {
$self->_upgrade_table('volumes','name','char(200)');
$self->_upgrade_table('domain_ports', 'internal_ip','char(200)');
$self->_upgrade_table('messages','date_changed','timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
}
sub _upgrade_timestamps($self) {
......@@ -2929,6 +2935,7 @@ sub _cmd_start {
$domain = $self->search_domain($name) if $name;
$domain = $self->search_domain_by_id($id_domain) if $id_domain;
die "Unknown domain '".($name or $id_domain)."'" if !$domain;
$domain->status('starting');
my $uid = $request->args('uid');
my $user = Ravada::Auth::SQL->search_by_id($uid);
......
......@@ -200,11 +200,11 @@ sub _access_allowed($self, $id_base, $id_clone, $access_data) {
}
sub list_machines($self, $user) {
return $self->list_domains() if $user->can_list_machines();
sub list_machines($self, $user, @filter) {
return $self->list_domains(@filter) if $user->can_list_machines();
my @list = ();
push @list,(@{filter_base_without_clones($self->list_domains())}) if $user->can_list_clones();
push @list,(@{filter_base_without_clones($self->list_domains(@filter))}) if $user->can_list_clones();
push @list,(@{$self->_list_own_clones($user)}) if $user->can_list_clones_from_own_base();
push @list,(@{$self->_list_own_machines($user)}) if $user->can_list_own_machines();
......@@ -214,8 +214,8 @@ sub list_machines($self, $user) {
return [sort { $a->{name} cmp $b->{name} } values %uniq];
}
sub _around_list_machines($orig, $self, $user) {
my $machines = $self->$orig($user);
sub _around_list_machines($orig, $self, $user, @filter) {
my $machines = $self->$orig($user, @filter);
for my $m (@$machines) {
eval { $m->{can_shutdown} = $user->can_shutdown($m->{id}) };
......@@ -226,7 +226,12 @@ sub _around_list_machines($orig, $self, $user) {
$m->{can_view} = 1 if $m->{id_owner} == $user->id || $user->is_admin;
$m->{can_manage} = ( $user->can_manage_machine($m->{id}) or 0);
eval {
$m->{can_change_settings} = ( $user->can_change_settings($m->{id}) or 0);
};
#may have been deleted just now
next if $@ && $@ =~ /Unknown domain/;
die $@ if $@;
$m->{can_hibernate} = 0;
$m->{can_hibernate} = 1 if $user->can_shutdown($m->{id})
......@@ -274,6 +279,11 @@ sub list_domains($self, %args) {
my $where = '';
for my $field ( sort keys %args ) {
$where .= " AND " if $where;
if (!defined $args{$field}) {
$where .= " $field IS NULL ";
delete $args{$field};
next;
}
$where .= " d.$field=?"
}
$where = "WHERE $where" if $where;
......@@ -304,7 +314,7 @@ sub list_domains($self, %args) {
$row->{is_locked} = $domain->is_locked;
$row->{is_hibernated} = ( $domain->is_hibernated or 0);
$row->{is_paused} = 1 if $domain->is_paused;
$row->{is_active} = 1 if $row->{status} eq 'active';
$row->{is_active} = 1 if $row->{status} =~ /active|starting/;
$row->{has_clones} = $domain->has_clones;
# $row->{disk_size} = ( $domain->disk_size or 0);
# $row->{disk_size} /= (1024*1024*1024);
......
......@@ -6,7 +6,6 @@ use strict;
use Data::Dumper;
use Hash::Util qw( lock_hash unlock_hash);
use Moose;
no warnings "experimental::signatures";
use feature qw(signatures);
......@@ -38,7 +37,14 @@ my %SUB = (
,request => \&_request
);
our %TABLE_CHANNEL = (
list_alerts => 'messages'
,list_machines => 'domains'
,list_requests => 'requests'
);
my $A_WHILE;
my $LIST_MACHINES_FIRST_TIME = 1;
######################################################################
......@@ -102,6 +108,12 @@ sub _list_machines($rvd, $args) {
|| $user->can_list_clones_from_own_base()
|| $user->is_admin()
);
if ($LIST_MACHINES_FIRST_TIME) {
$LIST_MACHINES_FIRST_TIME = 0;
return $rvd->list_machines($user, id_base => undef);
}
return $rvd->list_machines($user);
}
......@@ -178,7 +190,7 @@ sub _ping_backend($rvd, $args) {
# If there are requests in state different that requested it's ok
if ( scalar(@reqs) > $requested ) {
_its_been_a_while(1);
return 1;
return 2;
}
my ($ping_backend)
......@@ -225,31 +237,18 @@ sub _different_list($list1, $list2) {
}
sub _different_hash($h1,$h2) {
my $different = 0;
for my $key (keys %$h1) {
next if $key =~ /^_/;
next if !defined $h1->{$key} && !defined $h2->{$key};
if (!exists $h2->{$key}
|| !defined $h1->{$key} && defined $h2->{$key}
|| defined $h1->{$key} && !defined $h2->{$key}
|| _different($h1->{$key}, $h2->{$key})) {
unlock_hash(%$h1);
$h1->{_timestamp} = time - $T0;
lock_hash(%$h1);
$different = 1;
}
}
if (!exists $h1->{_timestamp}) {
unlock_hash(%$h1);
if (exists $h2->{_timestamp}) {
$h1->{_timestamp} = $h2->{_timestamp}
} else {
$h1->{_timestamp} = time - $T0;
return 1;
}
lock_hash(%$h1);
}
return $different;
return 0;
}
sub _different($var1, $var2) {
return 1 if !defined $var1 && defined $var2;
......@@ -266,38 +265,76 @@ sub BUILD {
for my $key ( keys %{$self->clients} ) {
my $ws_client = $self->clients->{$key}->{ws};
my $channel = $self->clients->{$key}->{channel};
$channel =~ s{/.*}{};
my $exec = $SUB{$channel} or die "Error: unknown channel $channel";
my $ret = $exec->($self->ravada, $self->clients->{$key});
my $old_ret = $self->clients->{$key}->{ret};
if ( _different($ret, $old_ret )) {
warn "WS: send $channel" if $DEBUG;
$ws_client->send( { json => $ret } );
$self->clients->{$key}->{ret} = $ret;
}
_send_answer($self, $ws_client, $channel, $key);
}
});
}
sub _list_machines_fast($self, $ws, $login) {
my $user = Ravada::Auth::SQL->new(name => $login) or die "Error: uknown user $login";
my $ret0 = $self->ravada->list_domains();
my @ret;
for my $dom (@$ret0) {
next if !$user->is_admin && $dom->{id_owner} != $user->id;
$dom->{can_start} = 1;
$dom->{can_view} = 1;
$dom->{can_manage} = 1;
push @ret,($dom) if !$dom->{id_base};
}
$ws->send( { json => \@ret } );
sub _old_info($self, $key, $new_count=undef, $new_changed=undef) {
my $args = $self->clients->{$key};
$args->{"_count_$key"} = $new_count if defined $new_count;
$args->{"_changed_$key"} = $new_changed if defined $new_changed;
my $old_count = ($args->{"_count_$key"} or 0 );
my $old_changed = ($args->{"_changed_$key"} or '' );
return ($old_count, $old_changed);
}
sub _date_changed_table($self, $table) {
my $rvd = $self->ravada;
my $sth = $rvd->_dbh->prepare("SELECT MAX(date_changed) FROM $table");
$sth->execute;
my ($date) = $sth->fetchrow;
return $date;
}
sub _count_table($self, $table) {
my $rvd = $self->ravada;
my $sth = $rvd->_dbh->prepare("SELECT count(*) FROM $table");
$sth->execute;
my ($count) = $sth->fetchrow;
return $count;
}
sub _new_info($self, $key) {
my $channel = $self->clients->{$key}->{channel};
$channel =~ s{/.*}{};
my $table = $TABLE_CHANNEL{$channel} or return;
return ($self->_count_table($table),$self->_date_changed_table($table));
}
sub _send_answer($self, $ws_client, $channel, $key = $ws_client) {
$channel =~ s{/.*}{};
my $exec = $SUB{$channel} or die "Error: unknown channel $channel";
my $old_ret = $self->clients->{$key}->{ret};
my ($old_count, $old_changed) = $self->_old_info($key);
my ($new_count, $new_changed) = $self->_new_info($key);
return $old_ret if defined $new_count && defined $new_changed
&& $old_count eq $new_count && $old_changed eq $new_changed;
$self->_old_info($key, $new_count, $new_changed)
unless $channel eq 'list_machines' && $LIST_MACHINES_FIRST_TIME;
my $ret = $exec->($self->ravada, $self->clients->{$key});
if ( _different($ret, $old_ret )) {
warn "WS: send $channel" if $DEBUG;
$ws_client->send( { json => $ret } );
$self->clients->{$key}->{ret} = $ret;
}
$self->unsubscribe($key) if $channel eq 'ping_backend' && $ret eq 2;
}
sub subscribe($self, %args) {
my $ws = $args{ws};
my %args2 = %args;
......@@ -308,9 +345,10 @@ sub subscribe($self, %args) {
, %args
, ret => undef
};
if ( $args{channel} eq 'list_machines' && $0 !~ /\.t$/) {
$self->_list_machines_fast($ws, $args{login})
if ($args{channel} eq 'list_machines') {
$LIST_MACHINES_FIRST_TIME = 1 ;
}
$self->_send_answer($ws,$args{channel});
}
sub unsubscribe($self, $ws) {
......
......@@ -175,6 +175,9 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.ws_fail = false;
ws.send('list_machines');
};
ws.onclose = function() {
ws = new WebSocket(url);
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
......@@ -187,26 +190,29 @@ ravadaApp.directive("solShowMachine", swMach)
mach = data[i];
if (!mach.id_base
&& (typeof $scope.list_machines[mach.id] == 'undefined'
|| $scope.list_machines[mach.id]._timestamp != mach._timestamp)
|| $scope.list_machines[mach.id].date_changed != mach.date_changed)
){
$scope.list_machines[mach.id] = mach;
$scope.list_machines[mach.id].childs = {};
$scope.list_machines[mach.id].childs_loading = true;
}
}
$scope.n_clones = 0;
for (var i=0, iLength = data.length; i<iLength; i++){
mach = data[i];
var childs;
var childs = {};
if (mach.id_base) {
childs = $scope.list_machines[mach.id_base].childs;
$scope.list_machines[mach.id_base].childs_loading = false;
}
if (mach.id_base
&& ( typeof childs[mach.id] == 'undefined'
|| childs[mach.id]._timestamp != mach._timestamp
|| childs[mach.id].date_changed != mach.date_changed
)
){
childs[mach.id] = mach;
$scope.n_clones++;
$scope.list_machines[mach.id_base].childs_loading = false;
}
}
if ($scope.auto_hide_clones) {
......@@ -231,6 +237,10 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.show_requests = false;
var ws = new WebSocket(url);
ws.onopen = function (event) { ws.send('list_requests') };
ws.onclose = function() {
ws = new WebSocket(url);
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
$scope.$apply(function () {
......@@ -294,7 +304,10 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.action = function(target,action,machineId){
$http.get('/'+target+'/'+action+'/'+machineId+'.json')
.then(function() {
.success(function() {
}).error(function(data,status) {
console.error('Repos error', status, data);
window.location.reload();
});
};
$scope.set_autostart= function(machineId, value) {
......@@ -303,7 +316,12 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.set_public = function(machineId, value) {
if (value) value=1;
else value = 0;
$http.get("/machine/public/"+machineId+"/"+value);
$http.get("/machine/public/"+machineId+"/"+value)
.error(function(data,status) {
console.error('Repos error', status, data);
window.location.reload();
});
};
$scope.can_remove_base = function(machine) {
......
......@@ -128,6 +128,9 @@
ws_connected = true;
ws.send(channel);
};
ws.onclose = function() {
ws = new WebSocket(url);
};
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
$scope.$apply(function () {
......@@ -199,6 +202,9 @@
subscribe_requests = function(url) {
var ws = new WebSocket(url);
ws.onopen = function(event) { ws.send('list_requests') };
ws.onclose = function() {
ws = new WebSocket(url);
};
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
$scope.$apply(function () {
......@@ -949,6 +955,10 @@
$scope.subscribe_alerts = function(url) {
var ws = new WebSocket(url);
ws.onopen = function(event) { ws.send('list_alerts') };
ws.onclose = function() {
ws = new WebSocket(url);
};
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
$scope.$apply(function () {
......
......@@ -487,6 +487,8 @@ get '/machine/view/(:id).(:type)' => sub {
my $id = $c->stash('id');
my $type = $c->stash('type');
return $c->redirect_to('/login') if !_logged_in($c);
my ($domain) = _search_requested_machine($c);
return access_denied($c) if !$domain;
......@@ -2230,6 +2232,7 @@ sub start_machine {
my ($domain, $type) = _search_requested_machine($c);
return $c->render(text => "Domain not found") if !$domain;
$domain->_data(status => 'starting');
my $req = Ravada::Request->start_domain( uid => $USER->id
,name => $domain->name
......
......@@ -282,6 +282,9 @@
</td>
<td class="lgMachNode"></td>
</tr>
<tr ng-show="show_clones[machine.id] && machine.childs_loading ">
<td> <%=l 'Loading ...' %> </td>
</tr>
<tr ng-show="show_clones[machine.id]" ng-repeat="child in machine.childs | orderObjectBy:'name'">
<td class="lgMachName">
&nbsp;<i title="[cloned]" class="fa fa-fw fa-long-arrow-right"
......
......@@ -103,6 +103,21 @@
<%=l 'Virtual Machine will start on host start.' %></small>
</div>
</div>
<div class="row">
<div class="col-lg-3 mt-2">
<label class="control-label" for="autostart"><%=l 'Shutdown disconnected' %></label>
</div>
<div class="col-lg-2">
<input type="checkbox" ng-model="new_shutdown_disconnected" name="autostart"
ng-true-value="1" ng-false-value="0"
ng-change="set_bool('shutdown_disconnected',new_shutdown_disconnected)"
>
</div>
<div class="col-md-7"><small class="text-secondary">
<%=l 'Virtual Machine will be shut down when user disconnects.' %></small>
</div>
</div>
% }
% if ($USER->is_admin){
......
Markdown is supported
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