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

Merge branch 'master' into develop

parents 8d6ead47 e0490037
...@@ -3,5 +3,22 @@ ...@@ -3,5 +3,22 @@
**Implemented enhancements:** **Implemented enhancements:**
- Define volume types [\#529]
- User experience acces directly to virtual desktop [\#1145]
- Restrict access using client headers [\#1213]
- Rebase virtual machines [\#1224]
- Change number of virtual CPUs [\#1238]
- Added Kali 2020 to the default ISO list [\#1236]
- Add turkish translation [\#1239]
- Arabic translation [\#1244]
**Bugfixes** **Bugfixes**
- Anonymous users don't get deleted [\#1023]
- Graphic parameters are removed on remote node base [\#1135]
- Renew mac with 2 devices [\#1232]
**Refactor**
- Turn frontend pulling to websockets [\#1187]
- Save screenshot database [\#1207]
...@@ -3,7 +3,7 @@ package Ravada; ...@@ -3,7 +3,7 @@ package Ravada;
use warnings; use warnings;
use strict; use strict;
our $VERSION = '0.7.0'; our $VERSION = '0.8.0';
use Carp qw(carp croak); use Carp qw(carp croak);
use Data::Dumper; use Data::Dumper;
...@@ -878,6 +878,7 @@ sub _add_indexes_generic($self) { ...@@ -878,6 +878,7 @@ sub _add_indexes_generic($self) {
my %index = ( my %index = (
requests => [ requests => [
"index(status,at_time)" "index(status,at_time)"
,"index(id,date_changed,status,at_time)"
,"index(date_changed)" ,"index(date_changed)"
,"index(start_time,command,status,pid)" ,"index(start_time,command,status,pid)"
] ]
...@@ -887,6 +888,9 @@ sub _add_indexes_generic($self) { ...@@ -887,6 +888,9 @@ sub _add_indexes_generic($self) {
,iptables => [ ,iptables => [
"index(id_domain,time_deleted,time_req)" "index(id_domain,time_deleted,time_req)"
] ]
,messages => [
"index(id_request,date_send)"
]
); );
for my $table ( keys %index ) { for my $table ( keys %index ) {
my $known = $self->_get_indexes($table); my $known = $self->_get_indexes($table);
...@@ -1407,7 +1411,14 @@ Returns the default display IP read from the config file ...@@ -1407,7 +1411,14 @@ Returns the default display IP read from the config file
=cut =cut
sub display_ip { sub display_ip($self=undef, $new_ip=undef) {
if (defined $new_ip) {
if (!length $new_ip) {
delete $CONFIG->{display_ip};
} else {
$CONFIG->{display_ip} = $new_ip;
}
}
my $ip = $CONFIG->{display_ip}; my $ip = $CONFIG->{display_ip};
return $ip if $ip; return $ip if $ip;
} }
...@@ -1677,21 +1688,17 @@ sub create_domain { ...@@ -1677,21 +1688,17 @@ sub create_domain {
or confess "Unknown base id: $id_base"; or confess "Unknown base id: $id_base";
$vm = $base->_vm; $vm = $base->_vm;
} }
my $user = Ravada::Auth::SQL->search_by_id($id_owner); my $user = Ravada::Auth::SQL->search_by_id($id_owner)
or confess "Error: Unkown user '$id_owner'";
$request->status("creating machine") if $request; $request->status("creating machine") if $request;
if ( $base && $base->is_base ) { if ( $base && $base->is_base && $base->volatile_clones || $user->is_temporary ) {
$request->status("balancing") if $request; $request->status("balancing") if $request;
$vm = $vm->balance_vm($base) or die "Error: No free nodes available."; $vm = $vm->balance_vm($base) or die "Error: No free nodes available.";
$request->status("creating machine on ".$vm->name) if $request; $request->status("creating machine on ".$vm->name) if $request;
} }
confess "No vm found, request = ".Dumper(request => $request) if !$vm; confess "Error: missing vm " if !$vm;
carp "WARNING: no VM defined, we will use ".$vm->name
if !$vm_name && !$id_base;
confess "I can't find any vm ".Dumper($self->vm) if !$vm;
my $domain; my $domain;
eval { $domain = $vm->create_domain(%args)}; eval { $domain = $vm->create_domain(%args)};
...@@ -2692,7 +2699,7 @@ sub _can_fork { ...@@ -2692,7 +2699,7 @@ sub _can_fork {
delete $reqs{$pid} if !$request || $request->status eq 'done'; delete $reqs{$pid} if !$request || $request->status eq 'done';
} }
my $n_pids = scalar(keys %reqs); my $n_pids = scalar(keys %reqs);
return 1 if $n_pids <= $req->requests_limit(); return 1 if $n_pids < $req->requests_limit();
my $msg = $req->command my $msg = $req->command
." waiting for processes to finish" ." waiting for processes to finish"
...@@ -3386,6 +3393,15 @@ sub _cmd_list_isos($self, $request){ ...@@ -3386,6 +3393,15 @@ sub _cmd_list_isos($self, $request){
$request->output(encode_json(\@isos)); $request->output(encode_json(\@isos));
} }
sub _cmd_set_time($self, $request) {
my $id_domain = $request->args('id_domain');
my $domain = Ravada::Domain->open($id_domain)
or confess "Error: domain $id_domain not found";
return if !$domain->is_active;
eval { $domain->set_time() };
die "$@ , retry.\n" if $@;
}
sub _clean_requests($self, $command, $request=undef, $status='requested') { sub _clean_requests($self, $command, $request=undef, $status='requested') {
my $query = "DELETE FROM requests " my $query = "DELETE FROM requests "
." WHERE command=? " ." WHERE command=? "
...@@ -3593,9 +3609,12 @@ sub _cmd_cleanup($self, $request) { ...@@ -3593,9 +3609,12 @@ sub _cmd_cleanup($self, $request) {
$self->_clean_volatile_machines( request => $request); $self->_clean_volatile_machines( request => $request);
$self->_clean_temporary_users( ); $self->_clean_temporary_users( );
$self->_clean_requests('cleanup', $request); $self->_clean_requests('cleanup', $request);
$self->_clean_requests('cleanup', $request,'done'); for my $cmd ( qw(cleanup enforce_limits refresh_vms
$self->_clean_requests('enforce_limits', $request,'done'); manage_pools refresh_machine screenshot
$self->_clean_requests('refresh_vms', $request,'done'); open_iptables ping_backend
)) {
$self->_clean_requests($cmd, $request,'done');
}
} }
sub _req_method { sub _req_method {
...@@ -3645,6 +3664,7 @@ sub _req_method { ...@@ -3645,6 +3664,7 @@ sub _req_method {
,add_hardware => \&_cmd_add_hardware ,add_hardware => \&_cmd_add_hardware
,remove_hardware => \&_cmd_remove_hardware ,remove_hardware => \&_cmd_remove_hardware
,change_hardware => \&_cmd_change_hardware ,change_hardware => \&_cmd_change_hardware
,set_time => \&_cmd_set_time
# Domain ports # Domain ports
,expose => \&_cmd_expose ,expose => \&_cmd_expose
......
...@@ -198,6 +198,7 @@ sub search_user { ...@@ -198,6 +198,7 @@ sub search_user {
confess "ERROR: I can't connect to LDAP " if!$ldap; confess "ERROR: I can't connect to LDAP " if!$ldap;
$username = escape_filter_value($username); $username = escape_filter_value($username);
$username =~ s/ /\\ /g;
my $filter = "($field=$username)"; my $filter = "($field=$username)";
if ( exists $$CONFIG->{ldap}->{filter} ) { if ( exists $$CONFIG->{ldap}->{filter} ) {
...@@ -237,12 +238,7 @@ sub search_user { ...@@ -237,12 +238,7 @@ sub search_user {
return if !$mesg->count(); return if !$mesg->count();
my @entries; return $mesg->entries;
for my $entry ($mesg->entries) {
push @entries,($entry) if $entry->get_value($field) eq $username;
}
return @entries;
} }
=head2 add_group =head2 add_group
......
...@@ -36,6 +36,7 @@ our $IPTABLES_CHAIN = 'RAVADA'; ...@@ -36,6 +36,7 @@ our $IPTABLES_CHAIN = 'RAVADA';
our %PROPAGATE_FIELD = map { $_ => 1} qw( run_timeout ); our %PROPAGATE_FIELD = map { $_ => 1} qw( run_timeout );
our $TIME_CACHE_NETSTAT = 60; # seconds to cache netstat data output our $TIME_CACHE_NETSTAT = 60; # seconds to cache netstat data output
our $RETRY_SET_TIME=10;
_init_connector(); _init_connector();
...@@ -59,6 +60,7 @@ requires 'resume'; ...@@ -59,6 +60,7 @@ requires 'resume';
requires 'rename'; requires 'rename';
requires 'dettach'; requires 'dettach';
requires 'set_time';
#storage #storage
requires 'add_volume'; requires 'add_volume';
...@@ -152,8 +154,9 @@ around 'prepare_base' => \&_around_prepare_base; ...@@ -152,8 +154,9 @@ around 'prepare_base' => \&_around_prepare_base;
#before 'prepare_base' => \&_pre_prepare_base; #before 'prepare_base' => \&_pre_prepare_base;
# after 'prepare_base' => \&_post_prepare_base; # after 'prepare_base' => \&_post_prepare_base;
before 'start' => \&_start_preconditions; #before 'start' => \&_start_preconditions;
after 'start' => \&_post_start; # after 'start' => \&_post_start;
around 'start' => \&_around_start;
before 'pause' => \&_allow_shutdown; before 'pause' => \&_allow_shutdown;
after 'pause' => \&_post_pause; after 'pause' => \&_post_pause;
...@@ -291,6 +294,65 @@ sub _vm_disconnect { ...@@ -291,6 +294,65 @@ sub _vm_disconnect {
$self->_vm->disconnect(); $self->_vm->disconnect();
} }
sub _around_start($orig, $self, @arg) {
$self->_start_preconditions(@arg);
my %arg;
if (!(scalar(@arg) % 2) ) {
%arg = @arg;
} else {
$arg{user} = $arg[0];
}
my $listen_ip = delete $arg{listen_ip};
my $remote_ip = $arg{remote_ip};
for (;;) {
eval { $self->_start_checks(@arg) };
if ($@ && $@ =~/base file not found/ && !$self->_vm->is_local) {
$self->_request_set_base();
next;
}
if (!defined $listen_ip) {
my $display_ip;
if ($remote_ip) {
my $set_password = 0;
my $network = Ravada::Network->new(address => $remote_ip);
$set_password = 1 if $network->requires_password();
$display_ip = $self->_listen_ip($remote_ip);
$arg{set_password} = $set_password;
} else {
$display_ip = $self->_listen_ip();
}
$arg{listen_ip} = $display_ip;
}
eval { $self->$orig(%arg) };
my $error = $@;
last if !$error;
warn "WARNING: $error ".$self->_vm->name." ".$self->_vm->enabled if $error;
if ($error && $self->id_base && !$self->is_local && $self->_vm->enabled) {
$self->_request_set_base();
next;
}
die $@;
}
$self->_post_start(%arg);
}
sub _request_set_base($self) {
my $base = Ravada::Domain->open($self->id_base);
$base->_set_base_vm_db($self->_vm->id,0);
Ravada::Request->set_base_vm(
uid => Ravada::Utils::user_daemon->id
,id_domain => $base->id
,id_vm => $self->_vm->id
);
my $vm_local = $self->_vm->new( host => 'localhost' );
$self->_set_vm($vm_local, 1);
}
sub _start_preconditions{ sub _start_preconditions{
my ($self) = @_; my ($self) = @_;
...@@ -315,44 +377,68 @@ sub _start_preconditions{ ...@@ -315,44 +377,68 @@ sub _start_preconditions{
_allow_manage(@_); _allow_manage(@_);
} }
#_check_used_memory(@_); #_check_used_memory(@_);
$self->status('starting');
}
sub _start_checks($self, @args) {
return if $self->_search_already_started('fast');
my $vm_local = $self->_vm->new( host => 'localhost' );
my $vm = $vm_local;
my ($id_vm, $request);
if (!scalar(@args) % 2) {
my %args = @args;
# We may be asked to start the machine in a specific id_vmanager
$id_vm = delete $args{id_vm};
$request = delete $args{request} if exists $args{request};
}
# If not specific id_manager we go to the last id_vmanager unless it was localhost
# If the last VManager was localhost it will try to balance here.
$id_vm = $self->_data('id_vm')
if !$id_vm && defined $self->_data('id_vm')
&& $self->_data('id_vm') != $vm_local->id;
if ($id_vm) {
$vm = Ravada::VM->open($id_vm);
if ( !$vm->is_enabled || !$vm->ping ) {
$vm = $vm_local;
$id_vm = undef;
}
}
$self->_check_tmp_volumes();
return if $self->_search_already_started();
# if it is a clone ( it is not a base ) # if it is a clone ( it is not a base )
if ($self->id_base) { if ($self->id_base) {
# $self->_set_last_vm(1) # $self->_set_last_vm(1)
if ( !$self->is_local && ( !$self->_vm->enabled || !$self->_vm->ping) ) { if ( !$self->is_local
my $vm_local = $self->_vm->new( host => 'localhost' ); && ( !$self->_vm->enabled || !base_in_vm($self->id_base,$self->_vm->id)
|| !$self->_vm->ping) ) {
$self->_set_vm($vm_local, 1); $self->_set_vm($vm_local, 1);
} }
my $vm; if ( !$vm->is_alive ) {
if ($id_vm) { $vm->disconnect();
$vm = Ravada::VM->open($id_vm); $vm->connect;
if ( !$vm->is_alive ) { $vm = $vm_local if !$vm->is_local && !$vm->is_alive;
$vm->disconnect();
$vm->connect;
}
}; };
warn $@ if $@; if ($id_vm) {
if ($vm) {
$self->_set_vm($vm); $self->_set_vm($vm);
} else { } else {
$self->_balance_vm(); $self->_balance_vm();
} }
$self->rsync(request => $request) if !$self->is_volatile && !$self->_vm->is_local(); $self->rsync(request => $request) if !$self->is_volatile && !$self->_vm->is_local();
} elsif (!$self->is_local) { } elsif (!$self->is_local) {
my $vm_local = $self->_vm->new( host => 'localhost' );
$self->_set_vm($vm_local, 1); $self->_set_vm($vm_local, 1);
} }
$self->status('starting');
$self->_check_free_vm_memory(); $self->_check_free_vm_memory();
#TODO: remove them and make it more general now we have nodes #TODO: remove them and make it more general now we have nodes
#$self->_check_cpu_usage($request); #$self->_check_cpu_usage($request);
} }
sub _search_already_started($self) { sub _search_already_started($self, $fast = 0) {
my $sth = $$CONNECTOR->dbh->prepare( my $sql = "SELECT id FROM vms where vm_type=?";
"SELECT id FROM vms where vm_type=?" $sql .= " AND is_active=1" if $fast;
); my $sth = $$CONNECTOR->dbh->prepare($sql);
$sth->execute($self->_vm->type); $sth->execute($self->_vm->type);
my %started; my %started;
while (my ($id) = $sth->fetchrow) { while (my ($id) = $sth->fetchrow) {
...@@ -407,10 +493,25 @@ sub _balance_vm($self) { ...@@ -407,10 +493,25 @@ sub _balance_vm($self) {
my $base; my $base;
$base = Ravada::Domain->open($self->id_base) if $self->id_base; $base = Ravada::Domain->open($self->id_base) if $self->id_base;
my $vm_free = $self->_vm->balance_vm($base); my $vm_free;
return if !$vm_free; for (;;) {
$vm_free = $self->_vm->balance_vm($base);
return if !$vm_free;
$self->migrate($vm_free) if $vm_free->id != $self->_vm->id; last if $vm_free->id == $self->_vm->id;
eval { $self->migrate($vm_free) };
last if !$@;
if ($@ && $@ =~ /file not found/i) {
$base->_set_base_vm_db($vm_free->id,0);
Ravada::Request->set_base_vm(
uid => Ravada::Utils::user_daemon->id
,id_domain => $base->id
,id_vm => $vm_free->id
);
next;
}
die $@;
}
return $vm_free->id; return $vm_free->id;
} }
...@@ -460,6 +561,8 @@ sub _allow_remove($self, $user) { ...@@ -460,6 +561,8 @@ sub _allow_remove($self, $user) {
return if !$self->is_known(); # already removed return if !$self->is_known(); # already removed
confess "Error: arg user is not Ravada::Auth object" if !ref($user);
die "ERROR: remove not allowed for user ".$user->name die "ERROR: remove not allowed for user ".$user->name
unless $user->can_remove_machine($self); unless $user->can_remove_machine($self);
...@@ -639,7 +742,8 @@ sub prepare_base($self, $with_cd) { ...@@ -639,7 +742,8 @@ sub prepare_base($self, $with_cd) {
for my $volume ($self->list_volumes_info()) { for my $volume ($self->list_volumes_info()) {
my $base_file = $volume->base_filename; my $base_file = $volume->base_filename;
next if !$base_file || $base_file =~ /\.iso$/; next if !$base_file || $base_file =~ /\.iso$/;
die "Error: file '$base_file' already exists" if $self->_vm->file_exists($base_file); confess "Error: file '$base_file' already exists in ".$self->_vm->name
if $self->_vm->file_exists($base_file);
} }
for my $volume ($self->list_volumes_info()) { for my $volume ($self->list_volumes_info()) {
...@@ -802,12 +906,34 @@ sub _check_free_vm_memory { ...@@ -802,12 +906,34 @@ sub _check_free_vm_memory {
return if $vm_free_mem > $self->_vm->min_free_memory; return if $vm_free_mem > $self->_vm->min_free_memory;
my $msg = "Error: No free memory. Only "._gb($vm_free_mem)." out of " my $msg = "Error: No free memory in ".$self->_vm->name.". Only "._gb($vm_free_mem)." out of "
._gb($self->_vm->min_free_memory)." GB required.\n"; ._gb($self->_vm->min_free_memory)." GB required.\n";
die $msg; die $msg;
} }
sub _check_tmp_volumes($self) {
confess "Error: only clones temporary volumes can be checked."
if !$self->id_base;
my $vm_local = $self->_vm->new( host => 'localhost' );
for my $vol ( $self->list_volumes_info) {
next unless $vol->file && $vol->file =~ /\.(TMP|SWAP)\./;
next if $vm_local->file_exists($vol->file);
my $base = Ravada::Domain->open($self->id_base);
my @volumes = $base->list_files_base_target;
my ($file_base) = grep { $_->[1] eq $vol->info->{target} } @volumes;
if (!$file_base) {
warn "Error: I can't find base volume for target ".$vol->info->{target}
.Dumper(\@volumes);
}
my $vol_base = Ravada::Volume->new( file => $file_base->[0]
, vm => $vm_local
);
$vol_base->clone(file => $vol->file);
}
}
sub _check_cpu_usage($self, $request=undef){ sub _check_cpu_usage($self, $request=undef){
return if ref($self) =~ /Void/i; return if ref($self) =~ /Void/i;
...@@ -900,7 +1026,7 @@ sub _around_display_info($orig,$self,$user ) { ...@@ -900,7 +1026,7 @@ sub _around_display_info($orig,$self,$user ) {
if (!$self->readonly) { if (!$self->readonly) {
$self->_set_display_ip($display); $self->_set_display_ip($display);
$self->_data(display => encode_json($display)); $self->_data(display => encode_json($display)) if $self->is_active;
} }
return $display; return $display;
} }
...@@ -1795,7 +1921,10 @@ sub is_locked { ...@@ -1795,7 +1921,10 @@ sub is_locked {
$self->_init_connector() if !defined $$CONNECTOR; $self->_init_connector() if !defined $$CONNECTOR;
my $sth = $$CONNECTOR->dbh->prepare("SELECT id,at_time FROM requests " my $sth = $$CONNECTOR->dbh->prepare("SELECT id,at_time FROM requests "
." WHERE id_domain=? AND status <> 'done'"); ." WHERE id_domain=? AND status <> 'done'"
." AND command <> 'open_iptables' "
." AND command <> 'set_time'"
);
$sth->execute($self->id); $sth->execute($self->id);
my ($id, $at_time) = $sth->fetchrow; my ($id, $at_time) = $sth->fetchrow;
$sth->finish; $sth->finish;
...@@ -1946,18 +2075,54 @@ sub remove_base($self, $user) { ...@@ -1946,18 +2075,54 @@ sub remove_base($self, $user) {
return $self->_do_remove_base($user); return $self->_do_remove_base($user);
} }
sub _do_remove_base($self, $user) { sub _cascade_remove_base_in_nodes($self) {
if ($self->is_base) { my $req_nodes;
for my $vm ( $self->list_vms ) { for my $vm ( $self->list_vms ) {
$self->remove_base_vm(vm => $vm, user => $user) if !$vm->is_local; next if $vm->is_local;
} my @after;
push @after,(after_request => $req_nodes->id) if $req_nodes;
$req_nodes = Ravada::Request->remove_base_vm(
id_vm => $vm->id
,id_domain => $self->id
,uid => Ravada::Utils::user_daemon->id
,@after
);
} }
$self->is_base(0); if ( $req_nodes ) {
my $vm_local = $self->_vm->new( host => 'localhost' );
Ravada::Request->remove_base_vm(
id_vm => $vm_local->id
,id_domain => $self->id
,uid => Ravada::Utils::user_daemon->id
,after_request => $req_nodes->id
);
$self->is_base(0);
}
return $req_nodes;
}
sub _do_remove_base($self, $user) {
return