Commit 0022a545 authored by Francesc Guasch's avatar Francesc Guasch
Browse files

Merge branch 'develop' of https://github.com/UPC/ravada into develop

parents e7611aeb 38a178d9
......@@ -17,3 +17,4 @@ yarn.lock
node_modules/
t/vm/b10*
public/fallback
public/img/version-*svg
......@@ -173,6 +173,7 @@ sub do_start {
Ravada::Request->cleanup();
Ravada::Request->refresh_vms() if rand(5)<3;
Ravada::Request->enforce_limits() if rand(5)<2;
Ravada::Request->manage_pools() if rand(5)<2;
$t_refresh = time;
}
sleep 1 if time - $t0 <1;
......
morris.js/morris.css https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css
https://use.fontawesome.com/releases/v5.10.1/fontawesome-free-5.10.1-web.zip
introjs/bin/introjs.css https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.7.0/introjs.css
jquery/jquery.min.js https://code.jquery.com/jquery-3.3.1.min.js
https://jqueryui.com/resources/download/jquery-ui-1.11.1.zip
bootstrap/dist/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
https://code.angularjs.org/1.5.5/angular-1.5.5.zip
https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.5.0/ui-bootstrap.min.js
raphael.js/ https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js
https://github.com/snapappointments/bootstrap-select/archive/v1.13.9.zip
morris.js/ https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js
intro.js/ https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.7.0/intro.js
bootstrap/ https://github.com/twbs/bootstrap/releases/download/v4.3.1/bootstrap-4.3.1-dist.zip
../img/logoUPC.png https://www.upc.edu/"><img src="https://www.upc.edu/content/assets/images/logoUPC.png
../img/version-$version-brightbreen.svg wget https://img.shields.io/badge/version-$version-brightgreen.svg
../img/latest.svg https://readthedocs.org/projects/ravada/badge/?version=latest
../img/License-AGPL%20v3-blue.svg https://img.shields.io/badge/License-AGPL%20v3-blue.svg
https://download.cksource.com/CKEditor/CKEditor/CKEditor%204.12.1/ckeditor_4.12.1_standard_easyimage.zip
......@@ -16,7 +16,7 @@
,secrets => ['changeme1','changeme2']
,login_custom => ''
,footer => '/bootstrap/footer'
,login_bg_file => 'img/custom/background.jpg'
,login_bg_file => 'img/intro-bg.jpg'
,login_message => 'Acme VDI service'
,monitoring => 0
,guide => ''
......@@ -24,4 +24,5 @@
,session_timeout => 5*60
,session_timeout_admin => 15*60
,auto_view => 0
,fallback => 0
};
......@@ -3,7 +3,7 @@ package Ravada;
use warnings;
use strict;
our $VERSION = '0.4.5';
our $VERSION = '0.4.7';
use Carp qw(carp croak);
use Data::Dumper;
......@@ -1285,9 +1285,14 @@ sub _upgrade_tables {
$self->_upgrade_table('domains','internal_id','varchar(64) DEFAULT NULL');
$self->_upgrade_table('domains','id_vm','int default null');
$self->_upgrade_table('domains','volatile_clones','int NOT NULL default 0');
$self->_upgrade_table('domains','comment',"varchar(80) DEFAULT ''");
$self->_upgrade_table('domains','client_status','varchar(32)');
$self->_upgrade_table('domains','client_status_time_checked','int NOT NULL default 0');
$self->_upgrade_table('domains','pools','int NOT NULL default 0');
$self->_upgrade_table('domains','pool_clones','int NOT NULL default 0');
$self->_upgrade_table('domains','pool_start','int NOT NULL default 0');
$self->_upgrade_table('domains','is_pool','int NOT NULL default 0');
$self->_upgrade_table('domains','needs_restart','int not null default 0');
$self->_upgrade_table('domains_network','allowed','int not null default 1');
......@@ -2127,7 +2132,7 @@ sub process_requests {
warn "req ".$req->id." , command: ".$req->command." , status: ".$req->status()
." , error: '".($req->error or 'NONE')."'\n" if $DEBUG || $VERBOSE;
sleep 1 if $DEBUG;
# sleep 1 if $DEBUG;
}
$sth->finish;
......@@ -2402,6 +2407,72 @@ sub _do_execute_command {
}
sub _cmd_manage_pools($self, $request) {
my @domains;
my $id_domain = $request->defined_arg('id_domain');
my $uid = $request->defined_arg('uid');
if (!$uid) {
$uid = Ravada::Utils::user_daemon->id;
$request->arg( uid => $uid );
}
confess if !defined $uid;
if ($id_domain) {
my $domain = Ravada::Domain->open($id_domain)
or die "Error: missing domain ".$id_domain;
push @domains,($domain);
} else {
push @domains, $self->list_domains;
}
for my $domain ( @domains ) {
next if !$domain->pools();
my @clone_pool = $domain->clones(is_pool => 1);
my $number = $domain->pool_clones() - scalar(@clone_pool);
if ($number > 0 ) {
$self->_pool_create_clones($domain, $number, $request);
}
my $count_active = 0;
for my $clone_data (@clone_pool) {
last if $count_active >= $domain->pool_start;
my $clone = Ravada::Domain->open($clone_data->{id});
# warn $clone->name."".($clone->client_status or '')." $count_active >= "
# .$domain->pool_start."\n";
if ( ! $clone->is_active ) {
Ravada::Request->start_domain(
uid => $uid
,id_domain => $clone->id
);
$count_active++;
} else {
$count_active++ if !$clone->client_status
|| $clone->client_status =~ /disconnected/i;
}
}
}
}
sub _pool_create_clones($self, $domain, $number, $request) {
my @arg_clone = ( no_pool => 1 );
$request->status("cloning $number");
if (!$domain->is_base) {
my @requests = $domain->list_requests();
return if grep { $_->command eq 'prepare_base' } @requests;
$request->status("preparing base");
my $req_base = Ravada::Request->prepare_base(
uid => $request->args('uid')
,id_domain => $domain->id
);
push @arg_clone, ( after_request => $req_base->id ) if $req_base;
}
Ravada::Request->clone(
uid => $request->args('uid')
,id_domain => $domain->id
,number => $number
,is_pool => 1
,start => 1
,@arg_clone
);
}
sub _cmd_screenshot {
my $self = shift;
my $request = shift;
......@@ -2449,6 +2520,17 @@ sub _cmd_create{
warn "$$ creating domain ".Dumper($request->args) if $DEBUG;
my $domain;
if ( $request->defined_arg('id_base') ) {
my $base = Ravada::Domain->open($request->args('id_base'));
if ( $base->pools && !$request->defined_arg('no_pools') ) {
$request->{args}->{id_domain} = delete $request->{args}->{id_base};
$request->{args}->{uid} = delete $request->{args}->{id_owner};
my $clone = $self->_cmd_clone($request);
$request->id_domain($clone->id);
return $clone;
}
}
$domain = $self->create_domain(request => $request);
my $msg = '';
......@@ -2475,7 +2557,15 @@ sub _can_fork {
my $type = $req->type;
my $n_pids = scalar(keys %{$self->{pids}->{$type}});
return 1 if !$self->{pids}->{$type};
my %reqs = %{$self->{pids}->{$type}};
for my $pid (keys %reqs) {
my $id_req = $reqs{$pid};
my $request = Ravada::Request->open($id_req);
delete $reqs{$pid} if !$request || $request->status eq 'done';
}
my $n_pids = scalar(keys %reqs);
return 1 if $n_pids <= $req->requests_limit();
my $msg = $req->command
......@@ -2589,21 +2679,67 @@ sub _cmd_open_iptables {
}
sub _cmd_clone($self, $request) {
my $number = $request->defined_arg('number');
my $no_pool = $request->defined_arg('no_pool');
return _req_clone_many($self, $request) if $number;
my $domain = Ravada::Domain->open($request->args('id_domain'))
or confess "Error: Domain ".$request->args('id_domain')." not found";
my @args = ( request => $request);
push @args, ( memory => $request->args('memory'))
if $request->defined_arg('memory');
my $args = $request->args();
$args->{request} = $request;
my $user = Ravada::Auth::SQL->search_by_id($request->args('uid'))
or die "Error: User missing, id: ".$request->args('uid');
push @args,(user => $user);
$domain->clone(
name => $request->args('name')
,@args
$args->{user} = $user;
for (qw(id_domain uid at )) {
delete $args->{$_};
}
my $name = ( $request->defined_arg('name') or $domain->name."-".$user->name );
if ( $domain->pools && !$no_pool ) {
my $clone = $domain->_search_pool_clone($user);
my $remote_ip = $request->defined_arg('remote_ip');
my $start = $request->defined_arg('start');
if ($start || $clone->is_active) {
$clone->start(user => $user, remote_ip => $remote_ip);
$clone->_data('client_status', 'connecting ...');
$clone->_data('client_status_time_checked',time);
Ravada::Request->manage_pools( uid => Ravada::Utils::user_daemon->id);
}
return $clone;
}
my $clone = $domain->clone(
name => $name
,%$args
);
}
sub _req_clone_many($self, $request) {
my $args = $request->args();
my $id_domain = $args->{id_domain};
my $base = Ravada::Domain->open($id_domain);
my $number = ( delete $args->{number} or 1 );
my $domains = $self->list_domains_data();
my %domain_exists = map { $_->{name} => 1 } @$domains;
my @reqs;
for ( 1 .. $number ) {
my $n = $_;
my $name;
for ( ;; ) {
while (length($n) < length($number)) { $n = "0".$n };
$name = $base->name."-".$n;
last if !$domain_exists{$name}++;
$n++;
}
$args->{name} = $name;
my $req2 = Ravada::Request->clone( %$args );
push @reqs, ( $req2 );
}
return @reqs;
}
sub _cmd_start {
......@@ -2624,7 +2760,16 @@ sub _cmd_start {
$self->_remove_unnecessary_downs($domain);
$domain->start(user => $user, remote_ip => $request->args('remote_ip'));
my @args = ( user => $user );
push @args, ( remote_ip => $request->defined_arg('remote_ip') )
if $request->defined_arg('remote_ip');
$domain->start(@args);
Ravada::Request->manage_pools(
uid => Ravada::Utils::user_daemon->id
) if $domain->is_pool && $request->defined_arg('remote_ip');
my $msg = 'Domain '
."<a href=\"/machine/view/".$domain->id.".html\">"
.$domain->name."</a>"
......@@ -2998,9 +3143,9 @@ sub _cmd_refresh_vms($self, $request=undef) {
$self->_refresh_disabled_nodes( $request );
$self->_refresh_down_nodes( $request );
my ($active_domain, $active_vm) = $self->_refresh_active_domains($request);
my $active_vm = $self->_refresh_active_vms();
my $active_domain = $self->_refresh_active_domains($request);
$self->_refresh_down_domains($active_domain, $active_vm);
$self->_remove_unnecessary_downs();
$self->_clean_requests('refresh_vms', $request);
$self->_refresh_volatile_domains();
......@@ -3095,11 +3240,15 @@ sub _cmd_list_isos($self, $request){
$request->output(encode_json(\@isos));
}
sub _clean_requests($self, $command, $request=undef) {
sub _clean_requests($self, $command, $request=undef, $status='requested') {
my $query = "DELETE FROM requests "
." WHERE command=? "
." AND status='requested'";
." AND status=?";
if ($status eq 'done') {
my $date= Time::Piece->localtime(time - 300);
$query .= " AND date_changed < ".$CONNECTOR->dbh->quote($date->ymd." ".$date->hms);
}
if ($request) {
confess "Wrong request" if !ref($request) || ref($request) !~ /Request/;
$query .= " AND id <> ?";
......@@ -3107,21 +3256,16 @@ sub _clean_requests($self, $command, $request=undef) {
my $sth = $CONNECTOR->dbh->prepare($query);
if ($request) {
$sth->execute($command, $request->id);
$sth->execute($command, $status, $request->id);
} else {
$sth->execute($command);
$sth->execute($command, $status);
}
}
sub _refresh_active_domains($self, $request=undef) {
my $id_domain;
$id_domain = $request->defined_arg('id_domain') if $request;
sub _refresh_active_vms ($self) {
my %active_domain;
my %active_vm;
for my $vm ($self->list_vms) {
$request->status('working',"checking active domains on ".$vm->name)
if $request;
if ( !$vm->enabled() || !$vm->is_active ) {
$vm->shutdown_domains();
$active_vm{$vm->id} = 0;
......@@ -3129,21 +3273,30 @@ sub _refresh_active_domains($self, $request=undef) {
next;
}
$active_vm{$vm->id} = 1;
}
return \%active_vm;
}
sub _refresh_active_domains($self, $request=undef) {
my $id_domain;
$id_domain = $request->defined_arg('id_domain') if $request;
my %active_domain;
if ($id_domain) {
my $domain = $vm->search_domain_by_id($id_domain);
$self->_refresh_active_domain($vm, $domain, \%active_domain) if $domain;
my $domain = $self->search_domain_by_id($id_domain);
$self->_refresh_active_domain($domain, \%active_domain) if $domain;
} else {
my @domains;
eval { @domains = $vm->list_domains };
eval { @domains = $self->list_domains };
warn $@ if $@;
for my $domain (@domains) {
next if $active_domain{$domain->id};
next if $domain->is_hibernated;
$self->_refresh_active_domain($vm, $domain, \%active_domain);
$self->_refresh_active_domain($domain, \%active_domain);
$self->_remove_unnecessary_downs($domain) if !$domain->is_active;
}
}
}
return \%active_domain, \%active_vm;
return \%active_domain;
}
sub _refresh_down_nodes($self, $request = undef ) {
......@@ -3180,7 +3333,7 @@ sub _refresh_disabled_nodes($self, $request = undef ) {
$sth->finish;
}
sub _refresh_active_domain($self, $vm, $domain, $active_domain) {
sub _refresh_active_domain($self, $domain, $active_domain) {
return if $domain->is_hibernated();
my $is_active = $domain->is_active();
......@@ -3188,9 +3341,6 @@ sub _refresh_active_domain($self, $vm, $domain, $active_domain) {
my $status = 'shutdown';
if ( $is_active ) {
$status = 'active';
$domain->_data(id_vm => $vm->id)
if !defined$domain->_data('id_vm')
|| $domain->_data('id_vm') != $vm->id;
}
$domain->_set_data(status => $status);
$domain->info(Ravada::Utils::user_daemon) if $is_active;
......@@ -3223,16 +3373,13 @@ sub _refresh_down_domains($self, $active_domain, $active_vm) {
}
}
sub _remove_unnecessary_downs($self, @domains) {
@domains = $self->list_domains( active => 0 ) if !scalar @domains;
sub _remove_unnecessary_downs($self, $domain) {
for my $domain (@domains) {
my @requests = $domain->list_requests(1);
for my $req (@requests) {
$req->status('done') if $req->command =~ /shutdown/;
$req->_remove_messages();
}
}
}
sub _refresh_volatile_domains($self) {
......@@ -3294,6 +3441,9 @@ sub _cmd_set_base_vm {
sub _cmd_cleanup($self, $request) {
$self->_clean_volatile_machines( request => $request);
$self->_clean_requests('cleanup', $request);
$self->_clean_requests('cleanup', $request,'done');
$self->_clean_requests('enforce_limits', $request,'done');
$self->_clean_requests('refresh_vms', $request,'done');
$self->_wait_pids();
}
......@@ -3334,7 +3484,6 @@ sub _req_method {
,rebase_volumes => \&_cmd_rebase_volumes
,refresh_storage => \&_cmd_refresh_storage
,refresh_machine => \&_cmd_refresh_machine
,refresh_vms => \&_cmd_refresh_vms
,domain_autostart=> \&_cmd_domain_autostart
,change_owner => \&_cmd_change_owner
,add_hardware => \&_cmd_add_hardware
......@@ -3360,6 +3509,8 @@ sub _req_method {
#isos
,list_isos => \&_cmd_list_isos
,manage_pools => \&_cmd_manage_pools
);
return $methods{$cmd};
}
......@@ -3506,6 +3657,14 @@ sub _enforce_limits_active($self, $request) {
for my $request ($domain->list_requests) {
next DOMAIN if $request->command =~ /shutdown/;
}
if ($domain->is_pool) {
$domain->id_owner(Ravada::Utils::user_daemon->id);
$domain->_data(comment => '');
Ravada::Request->shutdown(user => Ravada::Utils::user_daemon->id
,id_domain => $domain->id
);
return;
}
if ($domain->can_hybernate && !$domain->is_volatile) {
$domain->hybernate($USER_DAEMON);
} else {
......
......@@ -51,7 +51,7 @@ Internal OO build
sub BUILD {
my $self = shift;
die "ERROR: Login failed ".$self->name
die "ERROR: Login failed '".$self->name."'"
if !$self->login;
return $self;
}
......@@ -175,6 +175,7 @@ sub search_user {
_init_ldap_admin();
return search_user(
name => $username
,base => $base
,field => $field
,retry => ++$retry
,typesonly => $typesonly
......@@ -186,8 +187,10 @@ sub search_user {
return if !$mesg->count();
my @entries = $mesg->entries;
# warn join ( "\n",map { $_->dn } @entries);
my @entries;
for my $entry ($mesg->entries) {
push @entries,($entry) if $entry->get_value($field) eq $username;
}
return @entries;
}
......@@ -321,15 +324,29 @@ sub add_to_group {
sub login($self) {
my $user_ok;
my $allowed;
if ($$CONFIG->{ldap}->{ravada_posix_group}) {
$allowed = search_user (name => $self->name, field => 'memberUid', base => $$CONFIG->{ldap}->{ravada_posix_group}) || 0;
$self->{_ldap_entry} = $allowed;
} else {
$allowed = 1;
my $posix_group_name = $$CONFIG->{ldap}->{ravada_posix_group};
if ($posix_group_name) {
my ($posix_group) = search_user (
name => $posix_group_name
,field => 'cn'
, base => 'ou=groups,'._dc_base()
);
if (!$posix_group) {
warn "Warning: posix group $posix_group_name not found";
return;
}
my @member = $posix_group->get_value('memberUid');
my $user_name = $self->name;
my ($found) = grep /^$user_name$/,@member;
if (!$found) {
warn "Error: $user_name is not a member of posix group $posix_group_name\n";
warn Dumper(\@member) if $Ravada::DEBUG;
return;
}
$self->{_ldap_entry} = $posix_group;
}
if ($allowed) {
$user_ok = $self->_login_bind()
if !exists $$CONFIG->{ldap}->{auth}
|| !$$CONFIG->{ldap}->{auth}
......@@ -339,9 +356,6 @@ sub login($self) {
$self->_check_user_profile($self->name) if $user_ok;
$LDAP_ADMIN->unbind if $LDAP_ADMIN && exists $self->{_auth} && $self->{_auth} eq 'bind';
return $user_ok;
} else {
return 0;
}
}
sub _login_bind {
......
......@@ -35,7 +35,7 @@ our $IPTABLES_CHAIN = 'RAVADA';
our %PROPAGATE_FIELD = map { $_ => 1} qw( run_timeout );
our $TIME_CACHE_NETSTAT = 10; # seconds to cache netstat data output
our $TIME_CACHE_NETSTAT = 60; # seconds to cache netstat data output
_init_connector();
......@@ -883,6 +883,20 @@ sub id($self) {
##################################################################################
sub _execute_request($self, $field, $value) {
my %req = (
pools => 'manage_pools'
,pool_start => 'manage_pools'
,pool_clones => 'manage_pools'
);
my $exec = $req{$field} or return;
Ravada::Request->_new_request(
command => $exec
,args => { id_domain => $self->id , uid => Ravada::Utils::user_daemon->id }
);
}
sub _data($self, $field, $value=undef, $table='domains') {
_init_connector();
......@@ -908,6 +922,7 @@ sub _data($self, $field, $value=undef, $table='domains') {
$sth->finish;
$self->{$data}->{$field} = $value;
$self->_propagate_data($field,$value) if $PROPAGATE_FIELD{$field};
$self->_execute_request($field,$value);
}
return $self->{$data}->{$field} if exists $self->{$data}->{$field};
......@@ -1243,6 +1258,11 @@ sub info($self, $user) {
,has_clones => ( $self->has_clones or undef)
,needs_restart => ( $self->needs_restart or 0)
,type => $self->type
,pools => $self->pools
,pool_start => $self->pool_start
,pool_clones => $self->pool_clones
,is_pool => $self->is_pool
,comment => $self->_data('comment')
};
if ($is_active) {
eval {
......@@ -1597,17 +1617,25 @@ Returns a list of clones from this virtual machine
my @clones = $domain->clones
=cut
sub clones {
my $self = shift;
sub clones($self, %filter) {
_init_connector();
my $sth = $$CONNECTOR->dbh->prepare("SELECT id, id_vm, name FROM domains "
." WHERE id_base = ? AND (is_base=NULL OR is_base=0)");
$sth->execute($self->id);
my $query =
"SELECT id, id_vm, name, id_owner, status, client_status, is_pool"
." FROM domains "
." WHERE id_base = ? AND (is_base=NULL OR is_base=0)";
my @values = ($self->id);
if (keys %filter) {
$query .= "AND ( ".join(" AND ",map { "$_ = ?" } sort keys %filter)." )";
push @values,map {$filter{$_} } sort keys %filter;
}
my $sth = $$CONNECTOR->dbh->prepare($query);
$sth->execute(@values);
my @clones;
while (my $row = $sth->fetchrow_hashref) {
# TODO: open the domain, now it returns only the id
lock_hash(%$row);
push @clones , $row;
}
return @clones;
......@@ -1790,6 +1818,9 @@ sub clone {
my $remote_ip = delete $args{remote_ip};
my $request = delete $args{request};
my $memory = delete $args{memory};
my $start = delete $args{start};
my $is_pool = delete $args{is_pool};
my $no_pool = delete $args{no_pool};
confess "ERROR: Unknown args ".join(",",sort keys %args)
if keys %args;
......@@ -1802,6 +1833,7 @@ sub clone {