From 38adc355c80135658bc12953bc2afa0501feb7dc Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Wed, 3 Jul 2024 11:26:33 +0200 Subject: [PATCH] Refactor test volatiles and Host Devices (#2064) * refactor: fixed volatile clones and check correct network * test: host devices * refactor: remove volatile just gone --- lib/Ravada.pm | 189 +++++++++++++++++-------- lib/Ravada/Domain.pm | 194 +++++++++++++++++-------- lib/Ravada/Domain/Void.pm | 5 +- lib/Ravada/Front.pm | 1 + lib/Ravada/HostDevice.pm | 57 +++++++- lib/Ravada/Request.pm | 5 +- lib/Ravada/Route.pm | 6 +- lib/Ravada/Utils.pm | 37 ++++- lib/Ravada/VM.pm | 68 ++++++--- lib/Ravada/VM/KVM.pm | 30 ++-- public/js/ravada.js | 12 ++ script/rvd_back | 2 + script/rvd_front | 54 +++++-- t/device/00_host_device.t | 26 +++- t/device/10_templates.t | 2 + t/device/40_mediated_device.t | 19 ++- t/device/50_nodes.t | 63 +++++++-- t/kvm/71_description_clones.t | 4 + t/kvm/n10_nodes.t | 21 +-- t/lib/Test/Ravada.pm | 8 +- t/mojo/70_volatile.t | 24 +++- t/mojo/80_check_resources.t | 2 +- t/mojo/90_host_devices.t | 203 +++++++++++++++++++++++++++ t/nodes/10_basic.t | 13 +- t/vm/20_base.t | 8 +- t/vm/p10_pools.t | 3 + t/vm/v10_volatile.t | 43 ++++-- t/vm/v20_volatile_clones.t | 37 ++++- templates/bootstrap/requests.html.ep | 1 + templates/main/run_request.html.ep | 20 ++- templates/main/vm_hostdev.html.ep | 4 +- 31 files changed, 933 insertions(+), 228 deletions(-) create mode 100644 t/mojo/90_host_devices.t diff --git a/lib/Ravada.pm b/lib/Ravada.pm index 897f6c9c6..4301b384f 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -2696,7 +2696,7 @@ sub _sql_insert_defaults($self){ ,{ id_parent => $id_backend ,name => 'time_zone' - ,value => _default_time_zone() + ,value => Ravada::Utils::default_time_zone() } ,{ id_parent => $id_backend @@ -2783,26 +2783,6 @@ sub _sql_insert_defaults($self){ } } -sub _default_time_zone() { - return $ENV{TZ} if exists $ENV{TZ}; - my $timedatectl = `which timedatectl`; - chomp $timedatectl; - if (!$timedatectl) { - warn "Warning: No time zone found, checked TZ, missing timedatectl"; - return 'UTC'; - } - my @cmd = ( $timedatectl, '-p', 'Timezone','show'); - my ($in, $out, $err); - run3(\@cmd,\$in,\$out,\$err); - my ($tz) = $out =~ /=(.*)/; - chomp $out; - if (!$tz) { - warn "Warning: No timezone found in @cmd\n$out"; - return 'UTC' - } - return $tz; -} - sub _sql_insert_values($self, $table, $entry) { my $sql = "INSERT INTO $table " ."( " @@ -3548,6 +3528,8 @@ sub remove_domain { } my $user = Ravada::Auth::SQL->search_by_id( $arg{uid}); + die "Error: user id:$arg{uid} removed\n" if !$user; + die "Error: user ".$user->name." can't remove domain $id" if !$user->can_remove_machine($id); @@ -3987,6 +3969,7 @@ sub process_requests { for my $req (sort { $a->priority <=> $b->priority } @reqs) { next if $req eq 'refresh_vms' && scalar@reqs > 2; + next if $req eq 'refresh_vms' && $self->_processing_start(); next if !$req->id; next if $req->status() =~ /^(done|working)$/; @@ -4021,6 +4004,17 @@ sub process_requests { return scalar(@reqs); } +sub _processing_start($self) { + my $sth = $CONNECTOR->dbh->prepare( + "SELECT id FROM requests WHERE " + ." ( command = 'start' OR command='refresh_vms' OR command='clone')" + ." AND status = 'working'" + ); + $sth->execute(); + my ($id) = $sth->fetchrow; + return $id; +} + sub _date_now($seconds = 0) { my @now = localtime(time + $seconds); $now[4]++; @@ -4260,6 +4254,21 @@ sub list_vm_types { return keys %type; } +sub _stop_refresh($self) { + my $sth = $CONNECTOR->dbh->prepare( + "SELECT id FROM requests where status='working' " + ." AND ( command like ? ) " + ); + $sth->execute('refresh%'); + while ( my ($id) = $sth->fetchrow ) { + eval { + my $req = Ravada::Request->open($id); + $req->stop; + }; + + } +} + sub _execute { my $self = shift; my $request = shift; @@ -4281,6 +4290,10 @@ sub _execute { return; } + if ($request->command eq 'clone' || $request->command eq 'start') { + $self->_stop_refresh(); + } + $self->_wait_pids(); return if !$self->_can_fork($request); @@ -4566,13 +4579,29 @@ sub _cmd_create{ } sub _cmd_list_host_devices($self, $request) { - my $id_host_device = $request->args('id_host_device'); + my $id_host_device = $request->defined_arg('id_host_device'); - my $hd = Ravada::HostDevice->search_by_id( - $id_host_device - ); + my @id_hd; - my %list= $hd->list_devices_nodes; + if ( $id_host_device ) { + @id_hd = ($id_host_device); + } else { + my $sth = $CONNECTOR->dbh->prepare( + "SELECT id,name FROM host_devices " + ." WHERE enabled=1" + ); + $sth->execute; + while ( my ($id_hd, $name) = $sth->fetchrow ) { + push @id_hd , ($id_hd ); + } + } + + for my $id_hd (@id_hd) { + my $hd = Ravada::HostDevice->search_by_id( $id_hd); + next if !$hd; + eval { $hd->list_devices_nodes }; + warn $@ if $@; + } } @@ -4820,10 +4849,26 @@ sub _cmd_clone($self, $request) { $args->{options}->{network} = $net_bundle->{name} if $net_bundle; - my $clone = $domain->clone( - name => $name - ,%$args - ); + my $clone = $self->search_domain($name); + die "Error: virtual machine ".$name." already exists " + if $clone && $clone->id_owner != $user->id; + + if (!$clone) { + my $volatile = $domain->volatile_clones; + if (defined $args->{volatile}) { + $volatile = $args->{volatile}; + } + for my $try ( 1 .. 3 ) { + eval { + $clone = $domain->clone( name => $name ,%$args); + }; + my $err = $@; + warn $err if $err; + next if $err && $err =~ /No field in .*_data/i; + die $err if $err; + last if $clone; + } + } $request->id_domain($clone->id) if $clone; my $req_next = $request; @@ -4833,7 +4878,7 @@ sub _cmd_clone($self, $request) { ,id_domain => $clone->id ,remote_ip => $request->defined_arg('remote_ip') ,after_request => $req_next->id - ) if $request->defined_arg('start'); + ) if $clone && $request->defined_arg('start'); } @@ -4966,7 +5011,12 @@ sub _cmd_start { my $domain; $domain = $self->search_domain($name) if $name; $domain = Ravada::Domain->open($id_domain) if $id_domain; - die "Unknown domain '".($name or $id_domain)."'" if !$domain; + + if(!$domain) { + $self->_remove_inactive_gone($id_domain); + die "Unknown machine '".($name or $id_domain)."'\n"; + } + $domain->status('starting'); my $uid = $request->args('uid'); @@ -5081,7 +5131,7 @@ sub _cmd_shutdown_clones { my $is_active; eval { $domain2 = $self->search_domain_by_id($id); - $is_active = $domain2->is_active; + $is_active = $domain2->is_active if $domain2; }; warn $@ if $@; if ($is_active) { @@ -5638,18 +5688,43 @@ sub _cmd_check_storage($self, $request) { } } +sub _remove_inactive_gone($self,$id_domain) { + my $sth = $CONNECTOR->dbh->prepare( + "SELECT name,is_volatile " + ." FROM domains " + ." WHERE id=?" + ); + $sth->execute($id_domain); + my ($name,$is_volatile) = $sth->fetchrow; + if ($is_volatile) { + my $req = Ravada::Request->remove_domain( + name => $name + ,uid => Ravada::Utils::user_daemon->id + ); + $self->_cmd_remove($req); + } +} + sub _cmd_refresh_machine($self, $request) { my $id_domain = $request->args('id_domain'); my $user = Ravada::Auth::SQL->search_by_id($request->args('uid')); # it may have been removed on shutdown when volatile - my $domain = Ravada::Domain->open($id_domain) or return; + my $domain = Ravada::Domain->open($id_domain); + + return $self->_remove_inactive_gone($id_domain) if !$domain; $domain->check_status(); $domain->list_volumes_info(); my $is_active = $domain->is_active; - $self->_remove_unnecessary_downs($domain) if !$is_active; + if (!$is_active) { + $self->_remove_unnecessary_downs($domain); + if ( $domain->is_volatile && !$domain->_volatile_active ) { + $domain->remove(Ravada::Utils::user_daemon); + return; + } + } $domain->info($user); $domain->client_status(1) if $is_active; $domain->_check_port_conflicts(); @@ -5658,7 +5733,7 @@ sub _cmd_refresh_machine($self, $request) { ,timeout => 60, retry => 10) if $is_active && $domain->ip && $domain->list_ports; - $domain->_dettach_host_devices() if !$is_active; + $domain->_unlock_host_devices() if !$is_active; } sub _cmd_refresh_machine_ports($self, $request) { @@ -5959,7 +6034,7 @@ sub _cmd_migrate($self, $request) { if $request->defined_arg('remote_ip'); $domain->start(user => $user, @remote_ip) - if $request->defined_arg('start'); + if $request->defined_arg('start') && $domain->_vm->id == $node->id; } @@ -5968,7 +6043,7 @@ sub _cmd_rsync_back($self, $request) { my $id_domain = $request->args('id_domain') or die "ERROR: Missing id_domain"; my $domain = Ravada::Domain->open($id_domain); - return if $domain->is_active; + return if $domain->is_active || $domain->is_volatile; my $user = Ravada::Auth::SQL->search_by_id($uid); die "Error: user ".$user->name." not allowed to migrate domain ".$domain->name @@ -6034,7 +6109,8 @@ sub _refresh_active_domains($self, $request=undef) { next if $@ =~ /not found/; warn $@; } - $self->_refresh_active_domain($domain, \%active_domain) if $domain; + $self->_refresh_active_domain($domain, \%active_domain) + if $domain && !$domain->is_locked; } else { my @domains; eval { @domains = $self->list_domains_data }; @@ -6050,7 +6126,9 @@ sub _refresh_active_domains($self, $request=undef) { next if $@ =~ /not found/; warn $@; } - next if !$domain; + next if !$domain || $domain->is_locked; + next if $domain->_volatile_active(); + $self->_refresh_active_domain($domain, \%active_domain); $self->_remove_unnecessary_downs($domain) if !$domain->is_active; last if !$CAN_FORK && time - $t0 > 10; @@ -6223,7 +6301,8 @@ sub _refresh_active_domain($self, $domain, $active_domain) { $domain->client_status(1); $domain->_post_shutdown() - if $domain->_data('status') eq 'shutdown' && !$domain->_data('post_shutdown'); + if $domain->_data('status') eq 'shutdown' && !$domain->_data('post_shutdown') + && !$domain->_volatile_active; } sub _refresh_hibernated($self, $domain) { @@ -6285,16 +6364,20 @@ sub _remove_unnecessary_downs($self, $domain) { sub _refresh_volatile_domains($self) { my $sth = $CONNECTOR->dbh->prepare( - "SELECT id, name, id_vm, id_owner, vm FROM domains WHERE is_volatile=1" + "SELECT id, name, id_vm, id_owner, vm FROM domains WHERE is_volatile=1 " + ." AND date_changed < ? " ); - $sth->execute(); + $sth->execute(Ravada::Utils::date_now(-120)); while ( my ($id_domain, $name, $id_vm, $id_owner, $type) = $sth->fetchrow ) { my $domain; eval { $domain = Ravada::Domain->open(id => $id_domain, _force => 1) } ; + next if $domain && $domain->is_locked; if ( !$domain || $domain->status eq 'down' || !$domain->is_active) { - if ($domain) { - $domain->_post_shutdown(user => $USER_DAEMON); - $domain->remove($USER_DAEMON); + if ($domain && !$domain->is_locked ) { + Ravada::Request->shutdown_domain( + uid => $USER_DAEMON->id + ,id_domain => $id_domain + ); } else { my $user; eval { $user = Ravada::Auth::SQL->search_by_id($id_owner) }; @@ -6304,14 +6387,6 @@ sub _refresh_volatile_domains($self) { $user->remove(); } } - my $sth_del = $CONNECTOR->dbh->prepare("DELETE FROM domains WHERE id=?"); - $sth_del->execute($id_domain); - $sth_del->finish; - - $sth_del = $CONNECTOR->dbh->prepare("DELETE FROM requests where id_domain=?"); - $sth_del->execute($id_domain); - $sth_del->finish; - Ravada::Domain::_remove_domain_data_db($id_domain, $type); } } } @@ -6395,7 +6470,7 @@ sub _domain_just_started($self, $domain) { my $start_time = time - 300; $sth->execute($start_time); while ( my ($id, $command, $args) = $sth->fetchrow ) { - next if $command !~ /create|clone|start|open/i; + next if $command !~ /create|clone|start|open|shutdown/i; my $args_h = decode_json($args); return 1 if exists $args_h->{id_domain} && defined $args_h->{id_domain} && $args_h->{id_domain} == $domain->id; @@ -6782,6 +6857,8 @@ sub _clean_volatile_machines($self, %args) { ); if ($domain_real) { next if $domain_real->domain && $domain_real->is_active; + next if $domain_real->is_locked; + next if $domain_real->_volatile_active; eval { $domain_real->_post_shutdown() }; warn $@ if $@; eval { $domain_real->remove($USER_DAEMON) }; @@ -6790,7 +6867,7 @@ sub _clean_volatile_machines($self, %args) { my $user; eval { $user = Ravada::Auth::SQL->search_by_id($domain->{id_owner})}; warn $@ if $@; - $user->remove() if $user; + $user->remove() if $user && $user->is_temporary; } $sth_remove->execute($domain->{id}); diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index ae7ad1477..be027b893 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -46,6 +46,7 @@ our %PROPAGATE_FIELD = map { $_ => 1} qw( run_timeout shutdown_disconnected); our $TIME_CACHE_NETSTAT = 60; # seconds to cache netstat data output our $RETRY_SET_TIME=10; +our $TTL_REMOVE_VOLATILE = 60; our $DEBUG_RSYNC = 0; @@ -300,7 +301,9 @@ sub _vm_disconnect { sub _around_start($orig, $self, @arg) { $self->_post_hibernate() if $self->is_hibernated && !$self->_data('post_hibernated'); - $self->_dettach_host_devices() if !$self->is_active; + if ( !$self->is_active ) { + $self->_unlock_host_devices(0); + } $self->_start_preconditions(@arg); @@ -322,9 +325,10 @@ sub _around_start($orig, $self, @arg) { my $enable_host_devices; $enable_host_devices = $request->defined_arg('enable_host_devices') if $request; $enable_host_devices = 1 if !defined $enable_host_devices; + my $is_volatile = ($self->is_volatile || $arg{is_volatile}); for (1 .. 5) { - eval { $self->_start_checks(@arg, enable_host_devices => $enable_host_devices) }; + eval { $self->_start_checks(%arg, enable_host_devices => $enable_host_devices) }; my $error = $@; if ($error) { if ( $error =~/base file not found/ && !$self->_vm->is_local) { @@ -363,8 +367,10 @@ sub _around_start($orig, $self, @arg) { } $$CONNECTOR->disconnect; $self->status('starting') if $self->is_known(); + $self->_vm->_add_instance_db($self->id); eval { $self->$orig(%arg) }; $error = $@; + warn $error if $error; last if !$error; my $vm_name = Ravada::VM::_get_name_by_id($self->_data('id_vm')); @@ -374,8 +380,6 @@ sub _around_start($orig, $self, @arg) { die $error if $error =~ /No DRM render nodes/; - warn "WARNING: $error ".$vm_name if $error; - ;# pool has asynchronous jobs running. next if $error && ref($error) && $error->code == 1 && $error !~ /internal error.*unexpected address/ @@ -385,7 +389,7 @@ sub _around_start($orig, $self, @arg) { && $error !~ /child process/i ; - if ($error && $self->id_base && !$self->is_local && $self->_vm->enabled) { + if ($error && $self->is_known && $self->id_base && !$self->is_local && $self->_vm->enabled) { $self->_request_set_base(); next; } @@ -424,6 +428,7 @@ sub _start_preconditions{ my $remote_ip = delete $args{remote_ip}; $request = delete $args{request} if exists $args{request}; $id_vm = delete $args{id_vm}; + delete $args{is_volatile}; confess "ERROR: Unknown argument ".join("," , sort keys %args) ."\n\tknown: remote_ip, user" if keys %args; @@ -473,6 +478,7 @@ sub _start_checks($self, @args) { my $vm = $vm_local; my ($id_vm, $request, $enable_host_devices); + my $is_volatile = $self->is_volatile; if (!(scalar(@args) % 2)) { my %args = @args; @@ -480,6 +486,7 @@ sub _start_checks($self, @args) { $id_vm = delete $args{id_vm}; $request = delete $args{request} if exists $args{request}; $enable_host_devices = delete $args{enable_host_devices}; + $is_volatile = ($self->is_volatile || $args{is_volatile}); } my $id_prev = $self->_data('id_vm'); @@ -495,18 +502,19 @@ sub _start_checks($self, @args) { if ( !$vm->enabled || !$vm->ping ) { $vm = $vm_local; $id_vm = undef; - } elsif ($enable_host_devices && !$self->_available_hds($vm)) { + } elsif ($enable_host_devices && !$self->_available_hds($id_vm)) { $vm = $vm_local; $id_vm = undef; } } # if it is a clone ( it is not a base ) - if ($self->id_base) { + my $id_base = $self->id_base; + if ($id_base && !$is_volatile) { $self->_check_tmp_volumes(); # $self->_set_last_vm(1) if ( !$self->is_local - && ( !$self->_vm->enabled || !base_in_vm($self->id_base,$self->_vm->id) + && ( !$self->_vm->enabled || !base_in_vm($id_base,$self->_vm->id) || !$self->_vm->ping) ) { $self->_set_vm($vm_local, 1); } @@ -519,13 +527,13 @@ sub _start_checks($self, @args) { $self->_set_vm($vm); } else { $self->_balance_vm($request, $enable_host_devices) - if !$self->is_volatile; + if !$is_volatile; } - if ( !$self->is_volatile && !$self->_vm->is_local() ) { - if (!base_in_vm($self->id_base, $self->_vm->id)) { + if ( !$is_volatile && !$self->_vm->is_local() ) { + if (!base_in_vm($id_base, $self->_vm->id)) { my $args = { uid => Ravada::Utils::user_daemon->id - ,id_domain => $self->id_base + ,id_domain => $id_base ,id_vm => $self->_vm->id }; @@ -598,14 +606,14 @@ sub _search_already_started($self, $fast = 0) { return keys %started; } -sub _available_hds($self, $vm) { +sub _available_hds($self, $id_vm , $host_devices=[$self->list_host_devices]) { - my @host_devices = $self->list_host_devices(); - return 1 if !@host_devices; + return 1 if !@$host_devices; my $available=1; - for my $hd (@host_devices) { - if (! $hd->list_available_devices($vm->id) ) { + for my $hd (@$host_devices) { + my @devs_vm=$hd->list_available_devices_cached($id_vm) ; + if (!@devs_vm ) { $available=0; last; } @@ -637,21 +645,24 @@ sub _filter_vm_available_hd($self, @vms) { return @vms_ret; } -sub _balance_vm($self, $request=undef, $host_devices=undef) { +sub _balance_vm($self, $request=undef, $host_devices=1) { return if $self->{_migrated}; - my $base; $base = Ravada::Domain->open($self->id_base) if $self->id_base; my $vm_free; for (my $count=0;$count<10;$count++) { + + $self->_data('date_status_change'=>Ravada::Utils::now()); $vm_free = $self->_vm->balance_vm($self->_data('id_owner'),$base , $self->id, $host_devices); return if !$vm_free; next if !$vm_free->vm || !$vm_free->is_active; last if $vm_free->id == $self->_vm->id; + $self->_data('date_status_change'=>Ravada::Utils::now()); eval { $self->migrate($vm_free, $request) }; + $self->_data('date_status_change'=>Ravada::Utils::now()); last if !$@; if ($@ && $@ =~ /file not found/i) { $base->_set_base_vm_db($vm_free->id,0) unless $vm_free->is_local; @@ -660,7 +671,7 @@ sub _balance_vm($self, $request=undef, $host_devices=undef) { ,id_domain => $base->id ,id_vm => $vm_free->id ); - next; + confess $@; } die $@; } @@ -877,6 +888,7 @@ sub _around_prepare_base($orig, $self, @args) { if !scalar (\@base_img); $self->_prepare_base_db(@base_img); + $self->_set_base_vm_db($self->_vm->id, 1); $self->_post_prepare_base($user, $request); } @@ -910,6 +922,7 @@ Prepares the virtual machine as a base: sub prepare_base($self, $with_cd) { my @base_img; + for my $volume ($self->list_volumes_info()) { next if !$volume->file; my $base_file = $volume->base_filename; @@ -976,8 +989,6 @@ sub _pre_prepare_base($self, $user, $request = undef ) { sleep 1; } } - $self->_dettach_host_devices() if !$self->is_active; - # $self->_post_remove_base(); if (!$self->is_local) { my $vm_local = Ravada::VM->open( type => $self->vm ); @@ -2158,6 +2169,7 @@ sub info($self, $user) { ,id_vm => $self->_data('id_vm') ,auto_compact => $self->auto_compact ,date_changed => $self->_data('date_changed') + ,is_volatile => $self->_data('is_volatile') }; $info->{alias} = ( $self->_data('alias') or $info->{name} ); @@ -2947,6 +2959,7 @@ sub clone { my $alias = delete $args{alias}; my $options = delete $args{options}; my $storage = delete $args{storage}; + my $enable_host_devices = delete $args{enable_host_devices}; confess "ERROR: Unknown args ".join(",",sort keys %args) if keys %args; @@ -2982,6 +2995,8 @@ sub clone { push @args_copy, ( add_to_pool => $add_to_pool) if defined $add_to_pool; push @args_copy, ( storage => $storage) if $storage; push @args_copy, ( options => $options) if $options; + push @args_copy, ( enable_host_devices => $enable_host_devices) + if defined $enable_host_devices; if ( $self->volatile_clones && !defined $volatile ) { $volatile = 1; @@ -2998,14 +3013,29 @@ sub clone { } } - my $clone = $vm->create_domain( - name => $name - ,id_base => $self->id - ,id_owner => $uid - ,@args_copy - ); - $clone->is_pool(1) if $add_to_pool; - return $clone; + my $clone; + eval { + $clone = $vm->create_domain( + name => $name + ,id_base => $self->id + ,id_owner => $uid + ,@args_copy + ); + }; + my $err = $@; + + if ($clone) { + $clone->is_pool(1) if $add_to_pool; + + if (!defined $clone->{_data}->{is_volatile}) { + if (!defined $volatile) { + $volatile = $self->volatile_clones() || $user->is_temporary; + } + $clone->is_volatile($volatile or 0); + } + return $clone; + } + die $err if $err; } sub _clone_from_pool($self, %args) { @@ -3177,6 +3207,8 @@ sub _post_shutdown { my %arg = @_; my $timeout = delete $arg{timeout}; + my $force = delete $arg{force}; + if (!defined $timeout) { $timeout = ( $self->_data('shutdown_timeout') or $TIMEOUT_SHUTDOWN); } @@ -3191,7 +3223,7 @@ sub _post_shutdown { my $is_active = $self->is_active; if ( $self->is_known && !$self->is_volatile && !$is_active ) { - $self->_dettach_host_devices(); + $self->_dettach_host_devices() if $self->list_host_devices_attached; if ($self->is_hibernated) { $self->_data(status => 'hibernated'); } else { @@ -3200,7 +3232,7 @@ sub _post_shutdown { $self->_data(post_shutdown => 1); } - if ($self->is_known && $self->id_base) { + if ($self->is_known && $self->id_base && !$self->is_volatile) { my @disks = $self->list_disks(); if (grep /\.SWAP\./,@disks) { for ( 1 .. 5 ) { @@ -3234,7 +3266,7 @@ sub _post_shutdown { } $self->_unlock_host_devices() if !$is_active && $self->is_known; if ($self->is_volatile) { - $self->_remove_temporary_machine(); + $self->_remove_temporary_machine($force); return; } my $info = $self->_data('info'); @@ -3370,7 +3402,7 @@ sub _around_shutdown_now { if ($self->is_active) { $self->$orig($user); } - $self->_post_shutdown(user => $user) if $self->is_known(); + $self->_post_shutdown(user => $user, force => 1) if $self->is_known(); } sub _around_reboot_now { @@ -4135,11 +4167,41 @@ sub _test_iptables_jump { warn "Expecting 0 or 1 RAVADA iptables jump, got: " .($count or 0); } +sub _is_creating($self) { + return if !$self->is_known(); -sub _remove_temporary_machine($self) { + my $id_base = $self->id_base(); + return if !$id_base; + + my $sth = $self->_dbh->prepare( + "SELECT id,command,args FROM requests WHERE id_domain=? " + ." AND status='working' " + ." AND command = 'clone' " + + ); + $sth->execute($id_base); + my ($found, $command, $args) = $sth->fetchrow; + return $found; +} + +sub _remove_temporary_machine($self, $force=undef) { return if !$self->is_volatile; + my $id_req_locked = $self->is_locked(); + my $req; + $req = Ravada::Request->open($id_req_locked) if $id_req_locked; + my $command = ''; + $command = $req->command if $req; + + return if $command =~ /(clone|start)/; + + return if $self->_is_creating(); + + if ($self->is_known && !$force) { + return if $self->_volatile_active; + } + my $owner; $owner= Ravada::Auth::SQL->search_by_id($self->id_owner) if $self->is_known(); @@ -5069,7 +5131,7 @@ sub rsync($self, @args) { my $request = delete $args{request}; confess "ERROR: Unkown args ".Dumper(\%args) if keys %args; - + confess if $self->name =~ /aztest-m-/; if (!$files ) { my @files_base; if ($self->is_base) { @@ -5164,7 +5226,6 @@ sub _rsync_volumes_back($self, $node, $request=undef) { warn "$msg\n" if $DEBUG_RSYNC; my $t0 = time; $rsync->exec(src => 'root@'.$node->host.":".$file ,dest => $file ); - warn "Domain::rsync_volumes_back ".(time - $t0)." seconds $file" if $DEBUG_RSYNC; if ( $rsync->err ) { $request->status("done",join(" ",@{$rsync->err})) if $request; last; @@ -5192,7 +5253,6 @@ sub _pre_migrate($self, $node, $request = undef) { return unless $self->_check_all_parents_in_node($node); - $self->_set_base_vm_db($node->id,0) unless $node->is_local; } $node->_add_instance_db($self->id); } @@ -5409,7 +5469,7 @@ sub _pre_clone($self,%args) { confess "ERROR: Missing user owner of new domain" if !$user; for (qw(is_pool start add_to_pool from_pool with_cd volatile id_owner - alias storage options)) { + alias storage options enable_host_devices)) { delete $args{$_}; } confess "ERROR: Unknown arguments ".join(",",sort keys %args) if keys %args; @@ -5424,33 +5484,41 @@ Returns a list for virtual machine managers where this domain is base sub list_vms($self, $check_host_devices=0, $only_available=0) { confess "Domain is not base" if !$self->is_base; - my $sth = $$CONNECTOR->dbh->prepare("SELECT id_vm, id_request " - ." FROM bases_vm WHERE id_domain=? AND enabled = 1"); + $check_host_devices = 1 if !defined $check_host_devices; + + my $t0 = time; + my $sth = $$CONNECTOR->dbh->prepare( + " SELECT b.id_vm, v.name, b.id_request, v.is_active, v.enabled, v.cached_down " + ." FROM bases_vm b, vms v " + ." WHERE " + ." b.id_vm=v.id " + ." AND b.id_domain=? AND b.enabled = 1"); $sth->execute($self->id); my $sth_req = $$CONNECTOR->dbh->prepare( - "SELECT status FROM requests " + "SELECT command,status FROM requests " ." WHERE id=? " ); my @vms; - while (my ($id_vm, $id_request) = $sth->fetchrow) { + my @host_devices = $self->list_host_devices(); + while (my ($id_vm, $name_vm, $id_request, $is_active, $enabled, $cached_down) = $sth->fetchrow) { + next if $only_available && ( !$is_active || !$enabled); + my $t1 = time; + if ($only_available && $cached_down) { + next if time-$cached_down < $self->timeout_down_cache(); + } if ($id_request && $only_available) { $sth_req->execute($id_request); - my ($status) =$sth_req->fetchrow; + my ($command,$status) =$sth_req->fetchrow; next if $status && $status ne 'done'; } + next if $check_host_devices && !$self->_available_hds($id_vm, \@host_devices); my $vm; eval { $vm = Ravada::VM->open($id_vm) }; warn "id_domain: ".$self->id."\n".$@ if $@; push @vms,($vm) if $vm; } - my $vm_local = $self->_vm->new( host => 'localhost' ); - if ( !grep { $_->name eq $vm_local->name } @vms) { - push @vms,($vm_local); - $self->set_base_vm(vm => $vm_local, user => Ravada::Utils::user_daemon); - } - return @vms if !$check_host_devices; + return @vms; - return $self->_filter_vm_available_hd(@vms); } =head2 base_in_vm @@ -5483,7 +5551,7 @@ sub base_in_vm($self,$id_vm) { $sth->finish; # return 1 if !defined $enabled # && $id_vm == $self->_vm->id && $self->_vm->host eq 'localhost'; - return $enabled; + return ( $enabled or 0 ); } @@ -7278,8 +7346,8 @@ sub list_host_devices_attached($self, $only_locked=0) { $sth_locked->execute($self->id, $row->{name}); my ($is_locked) = $sth_locked->fetchrow(); $row->{is_locked} = 1 if $is_locked; - push @found,($row) unless $only_locked && !$is_locked; } + push @found,($row) unless $only_locked && !$row->{is_locked}; } return @found; @@ -7456,7 +7524,8 @@ sub _clean_old_hd_locks($self) { } -sub _unlock_host_devices($self, $time_changed=3) { +sub _unlock_host_devices($self, $time_changed=$TTL_REMOVE_VOLATILE) { + my $sth = $$CONNECTOR->dbh->prepare("DELETE FROM host_devices_domain_locked " ." WHERE id_domain=? AND time_changed<=?" ); @@ -7467,7 +7536,7 @@ sub _unlock_host_device($self, $name) { my $sth = $$CONNECTOR->dbh->prepare("DELETE FROM host_devices_domain_locked " ." WHERE id_domain=? AND name=? AND time_changedexecute($self->id, $name,time-60); + $sth->execute($self->id, $name,time-$TTL_REMOVE_VOLATILE); } @@ -7902,4 +7971,19 @@ sub is_in_bundle($self) { } +sub _volatile_active($self) { + return 0 if !$self->is_known(); + return 0 if !$self->is_volatile; + + my $date = DateTime::Format::DateParse->parse_datetime( $self->_data('date_status_change')); + return 1 if !$date; + + my $now = DateTime->from_epoch( epoch => time()-$TTL_REMOVE_VOLATILE + , time_zone => Ravada::Utils::TZ_SYSTEM()); + + return 0 if $date && DateTime->compare($date, $now) <0; + + return 1; + +} 1; diff --git a/lib/Ravada/Domain/Void.pm b/lib/Ravada/Domain/Void.pm index 50a964f67..5ef14344c 100644 --- a/lib/Ravada/Domain/Void.pm +++ b/lib/Ravada/Domain/Void.pm @@ -394,6 +394,7 @@ sub start($self, @args) { my $set_password = delete $args{set_password}; # unused my $user = delete $args{user}; delete $args{'id_vm'}; + delete $args{'is_volatile'}; confess "Error: unknown args ".Dumper(\%args) if keys %args; $listen_ip = $self->_vm->listen_ip($remote_ip) if !$listen_ip; @@ -1158,10 +1159,8 @@ sub add_config_node($self, $path, $content, $data) { for my $item (split m{/}, $path ) { next if !$item; - confess "Error, no $item in ".Dumper($found) - if !exists $found->{$item}; - $found = $found->{$item}; + $found = [] if !$found; } my $old; if (ref($found) eq 'ARRAY') { diff --git a/lib/Ravada/Front.pm b/lib/Ravada/Front.pm index 212217a47..81388f440 100644 --- a/lib/Ravada/Front.pm +++ b/lib/Ravada/Front.pm @@ -1026,6 +1026,7 @@ sub search_clone($self, %args) { my $sth = $CONNECTOR->dbh->prepare( "SELECT id,name FROM domains " ." WHERE id_base=? AND id_owner=? AND (is_base=0 OR is_base=NULL)" + ." AND is_volatile=0 " ." ORDER BY name" ); $sth->execute($id_base, $id_owner); diff --git a/lib/Ravada/HostDevice.pm b/lib/Ravada/HostDevice.pm index f32262652..4dba3883e 100644 --- a/lib/Ravada/HostDevice.pm +++ b/lib/Ravada/HostDevice.pm @@ -99,19 +99,18 @@ sub list_devices_nodes($self) { my %devices; for my $ndata (@nodes) { if (!$ndata->[2] || !$ndata->[3]) { - $devices{$ndata->[1]}=[]; + $devices{$ndata->[0]}=[]; next; } my $node = Ravada::VM->open($ndata->[0]); - next if !$node || !$node->vm; my @current_devs; eval { @current_devs = $self->list_devices($node->id) - if $node->is_active; + if $node && $node->is_active; }; warn $@ if $@; # push @devices, @current_devs; - $devices{$node->id}=\@current_devs; + $devices{$ndata->[0]}=\@current_devs; } $self->_data( devices_node => \%devices ); @@ -146,9 +145,13 @@ sub is_device($self, $device, $id_vm) { } +sub _ttl_remove_volatile() { + return $Ravada::Domain::TTL_REMOVE_VOLATILE; +} + sub _device_locked($self, $name, $id_vm=$self->id_vm) { my $sth = $$CONNECTOR->dbh->prepare( - "SELECT id,id_domain " + "SELECT id,id_domain,time_changed " ." FROM host_devices_domain_locked " ." WHERE id_vm=? AND name=? " ); @@ -161,10 +164,11 @@ sub _device_locked($self, $name, $id_vm=$self->id_vm) { "DELETE FROM host_devices_domain_locked " ." WHERE id=?" ); - while ( my ($id_lock, $id_domain)= $sth->fetchrow ) { + while ( my ($id_lock, $id_domain,$time_changed)= $sth->fetchrow ) { + return $id_lock if time - $time_changed < _ttl_remove_volatile() ; $sth_status->execute($id_domain); my ($status) = $sth_status->fetchrow; - return $id_domain if $status && $status ne 'down'; + return $id_domain if $status && $status ne 'shutdown'; $sth_unlock->execute($id_lock); } return 0; @@ -179,6 +183,29 @@ sub list_available_devices($self, $id_vm=$self->id_vm) { return @device; } +sub list_available_devices_cached($self, $id_vm=$self->id_vm) { + my @device; + my $dn = {}; + my $data_dn = $self->_data('devices_node'); + if (!$data_dn) { + my %data_dn = $self->list_devices_nodes(); + $dn = \%data_dn; + } else { + eval { $dn = decode_json($data_dn) }; + if ($@) { + warn "$@ ".($data_dn or ''); + return $self->list_available_devices($id_vm); + } + } + my $dnn = $dn->{$id_vm}; + for my $dev_entry ( @$dnn ) { + next if $self->_device_locked($dev_entry, $id_vm); + push @device, ($dev_entry); + } + return @device; +} + + sub remove($self) { _init_connector(); my $id = $self->id; @@ -303,6 +330,22 @@ sub list_domains_with_device($self) { sub _dettach_in_domains($self) { for my $id_domain ( $self->list_domains_with_device() ) { my $domain = Ravada::Domain->open($id_domain); + if (!$domain) { + warn "unlocking from domain $id_domain"; + my $sth = $$CONNECTOR->dbh->prepare( + "DELETE FROM host_devices_domain_locked " + ." WHERE id_domain=?" + ); + $sth->execute($id_domain); + + $sth = $$CONNECTOR->dbh->prepare( + "DELETE FROM host_devices_domain " + ." WHERE id_host_device=?" + ." AND id_domain=?" + ); + $sth->execute($self->id, $id_domain); + next; + } $domain->_dettach_host_device($self) if !$domain->is_active(); } } diff --git a/lib/Ravada/Request.pm b/lib/Ravada/Request.pm index dcb6e5765..60fe69dcf 100644 --- a/lib/Ravada/Request.pm +++ b/lib/Ravada/Request.pm @@ -9,7 +9,7 @@ Ravada::Request - Requests library for Ravada =cut -use Carp qw(confess); +use Carp qw(confess cluck); use Data::Dumper; use Hash::Util qw(lock_hash); use JSON::XS; @@ -107,6 +107,7 @@ our %VALID_ARG = ( ,with_cd => 2 ,storage => 2 ,options => 2 + ,enable_host_devices => 2 } ,change_owner => {uid => 1, id_domain => 1} ,add_hardware => {uid => 1, id_domain => 1, name => 1, number => 2, data => 2 } @@ -152,7 +153,7 @@ our %VALID_ARG = ( ,list_host_devices => { uid => 1 - ,id_host_device => 1 + ,id_host_device => 2 ,_force => 2 } ,remove_host_device => { diff --git a/lib/Ravada/Route.pm b/lib/Ravada/Route.pm index 10d4e14c0..2f172a2a7 100644 --- a/lib/Ravada/Route.pm +++ b/lib/Ravada/Route.pm @@ -109,6 +109,10 @@ sub requires_password { for my $network ( $self->list_networks ) { my ($ip,$mask) = $network->{address} =~ m{(.*)/(.*)}; + if ($ip !~ /^\d+\.\d+\.\d+\.\d+$/) { + warn "Wrong network $ip / $mask"; + next; + } if (!$ip ) { $ip = $network->{address}; $mask = 24; @@ -116,7 +120,7 @@ sub requires_password { my $netaddr; eval { $netaddr = NetAddr::IP->new($ip,$mask) }; if ($@ ) { - warn "Error with newtork $network->{address} [ $ip / $mask ] $@"; + warn "Error with network $network->{address} [ $ip / $mask ] $@"; return; } next if !$self->address->within($netaddr); diff --git a/lib/Ravada/Utils.pm b/lib/Ravada/Utils.pm index e2ad26a5e..d71f8b448 100644 --- a/lib/Ravada/Utils.pm +++ b/lib/Ravada/Utils.pm @@ -7,8 +7,7 @@ use Carp qw(confess); no warnings "experimental::signatures"; use feature qw(signatures); -no warnings "experimental::signatures"; -use feature qw(signatures); +use IPC::Run3 qw(run3); =head1 NAME @@ -18,6 +17,8 @@ Ravada::Utils - Misc util libraries for Ravada our $USER_DAEMON; our $USER_DAEMON_NAME = 'daemon'; +our $TZ; +our $TZ_SYSTEM; =head2 now @@ -174,4 +175,36 @@ sub search_user_id($dbh, $user) { return $id; } +sub default_time_zone() { + return $ENV{TZ} if exists $ENV{TZ}; + my $timedatectl = `which timedatectl`; + chomp $timedatectl; + if (!$timedatectl) { + warn "Warning: No time zone found, checked TZ, missing timedatectl"; + return 'UTC'; + } + my @cmd = ( $timedatectl, '-p', 'Timezone','show'); + my ($in, $out, $err); + run3(\@cmd,\$in,\$out,\$err); + my ($tz) = $out =~ /=(.*)/; + chomp $out; + if (!$tz) { + warn "Warning: No timezone found in @cmd\n$out"; + return 'UTC' + } + return $tz; +} + +sub TZ() { + return $TZ if defined $TZ; + $TZ = Ravada::Front->setting('/backend/time_zone'); +} + +sub TZ_SYSTEM() { + return $TZ_SYSTEM if defined $TZ_SYSTEM; + $TZ_SYSTEM = default_time_zone(); + + return $TZ_SYSTEM; +} + 1; diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index 776e9f37f..d93fb95a8 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -425,6 +425,11 @@ sub _connect_ssh($self) { $ssh = $SSH{$self->host} if exists $SSH{$self->host}; if (!$ssh || !$ssh->check_master) { + + if ($self->_data('cached_down') && time-$self->_data('cached_down')< $self->timeout_down_cache()) { + return; + } + delete $SSH{$self->host}; if ($self->_data('cached_down') && time-$self->_data('cached_down')< $self->timeout_down_cache()) { @@ -440,13 +445,13 @@ sub _connect_ssh($self) { ,kill_ssh_on_timeout => 1 ); last if !$ssh->error; - warn "RETRYING ssh ".$self->host." ".join(" ",$ssh->error); + # warn "RETRYING ssh ".$self->host." ".join(" ",$ssh->error); sleep 1; } if ( $ssh->error ) { $self->_cached_active(0); $self->_data('cached_down' => time); - warn "Error connecting to ".$self->host." : ".$ssh->error(); + # warn "Error connecting to ".$self->host." : ".$ssh->error(); return; } } @@ -499,11 +504,12 @@ sub _around_create_domain { my $request = delete $args{request}; delete $args{iso_file}; delete $args{id_template}; - delete @args{'description','remove_cpu','vm','start','options','id', 'alias','storage'}; + delete @args{'description','remove_cpu','vm','start','options','id', 'alias','storage' + ,'enable_host_devices'}; confess "ERROR: Unknown args ".Dumper(\%args) if keys %args; - $self->_check_duplicate_name($name); + $self->_check_duplicate_name($name, $volatile); if ($id_base) { my $vm_local = $self; $vm_local = $self->new( host => 'localhost') if !$vm_local->is_local; @@ -546,15 +552,20 @@ sub _around_create_domain { return $base->_search_pool_clone($owner) if $from_pool; - if ($self->is_local && $base && $base->is_base && $args_create{volatile}) { + if ($self->is_local && $base && $base->is_base && $args_create{volatile} && !$base->list_host_devices) { $request->status("balancing") if $request; - my $vm = $self->balance_vm($owner->id, $base) or die "Error: No free nodes available."; + my $vm = $self->balance_vm($owner->id, $base); + + if (!$vm) { + die "Error: No free nodes available.\n"; + } $request->status("creating machine on ".$vm->name) if $request; $self = $vm; $args_create{listen_ip} = $self->listen_ip($remote_ip); } my $domain = $self->$orig(%args_create); + $domain->_data('date_status_change'=>Ravada::Utils::now()); $self->_add_instance_db($domain->id); $domain->add_volume_swap( size => $swap ) if $swap; $domain->_data('is_compacted' => 1); @@ -586,22 +597,37 @@ sub _around_create_domain { $domain->_chroot_filesystems(); } my $user = Ravada::Auth::SQL->search_by_id($id_owner); - $domain->is_volatile(1) if $user->is_temporary() || $volatile || $args_create{volatile}; + + $volatile = $volatile || $user->is_temporary || $args_create{volatile}; my @start_args = ( user => $owner ); push @start_args, (remote_ip => $remote_ip) if $remote_ip; $domain->_post_start(@start_args) if $domain->is_active; - eval { - $domain->start(@start_args) if $active || ($domain->is_volatile && ! $domain->is_active); - }; - die $@ if $@ && $@ !~ /code: 55,/; + + if ( $active || ($volatile && ! $domain->is_active) ) { + $domain->_data('date_status_change'=>Ravada::Utils::now()); + $domain->status('starting'); + + eval { + $domain->start(@start_args, is_volatile => $volatile); + }; + my $err = $@; + + $domain->_data('date_status_change'=>Ravada::Utils::now()); + if ( $err && $err !~ /code: 55,/ ) { + $domain->is_volatile($volatile) if defined $volatile; + die $err; + } + } + $domain->is_volatile($volatile) if defined $volatile; $domain->info($owner); $domain->display($owner) if $domain->is_active; $domain->is_pool(1) if $add_to_pool; + return $domain; } @@ -1010,13 +1036,15 @@ sub _check_require_base { delete $args{start}; delete $args{remote_ip}; - delete @args{'_vm','name','vm', 'memory','description','id_iso','listen_ip','spice_password','from_pool', 'volatile', 'alias','storage', 'options', 'network'}; + delete @args{'_vm','name','vm', 'memory','description','id_iso','listen_ip','spice_password','from_pool', 'volatile', 'alias','storage', 'options', 'network','enable_host_devices'}; confess "ERROR: Unknown arguments ".join(",",keys %args) if keys %args; my $base = Ravada::Domain->open($id_base); - my %ignore_requests = map { $_ => 1 } qw(clone refresh_machine set_base_vm start_clones shutdown_clones shutdown force_shutdown refresh_machine_ports set_time open_exposed_ports manage_pools screenshot remove_clones); + my %ignore_requests = map { $_ => 1 } qw(clone refresh_machine set_base_vm start_clones shutdown_clones shutdown force_shutdown refresh_machine_ports set_time open_exposed_ports manage_pools screenshot remove_clones + list_cpu_models + ); my @requests; for my $req ( $base->list_requests ) { push @requests,($req) if !$ignore_requests{$req->command}; @@ -1373,6 +1401,7 @@ sub is_locked($self) { next if defined $at && $at < time + 2; next if !$args; my $args_d = decode_json($args); + warn Dumper($args_d) if $args_d->{id_vm} && $args_d->{id_vm} eq 'KVM'; if ( exists $args_d->{id_vm} && $args_d->{id_vm} == $self->id ) { warn "locked by $command\n"; return 1; @@ -1858,7 +1887,9 @@ sub run_command_nowait($self, @command) { =pod + warn "1 ".localtime(time) if $self->_data('hostname') =~ /192.168.122./; my $chan = $self->_ssh_channel() or die "ERROR: No SSH channel to host ".$self->host; + warn "2 ".localtime(time) if $self->_data('hostname') =~ /192.168.122./; my $command = join(" ",@command); $chan->exec($command);# or $self->{_ssh}->die_with_error; @@ -2096,7 +2127,7 @@ Arguments =cut -sub balance_vm($self, $uid, $base=undef, $id_domain=undef, $host_devices=undef) { +sub balance_vm($self, $uid, $base=undef, $id_domain=undef, $host_devices=1) { my @vms; if ($base) { @@ -2120,7 +2151,8 @@ sub balance_vm($self, $uid, $base=undef, $id_domain=undef, $host_devices=undef) } my $vm = $self->_balance_free_memory($base, \@vms_active); return $vm if $vm; - die "Error: No free nodes available.\n" if !$vm; + die "Error: No available devices or no free nodes.\n" if $host_devices; + die "Error: No free nodes available.\n"; } sub _balance_already_started($self, $uid, $id_domain, $vms) { @@ -2735,12 +2767,16 @@ sub add_host_device($self, %args) { _init_connector(); my $template = delete $args{template} or confess "Error: template required"; + my $name = delete $args{name}; + my $info = Ravada::HostDevice::Templates::template($self->type, $template); my $template_list = delete $info->{templates}; $info->{id_vm} = $self->id; $info->{name}.= " ".($self->_max_hd($info->{name})+1); + $info->{name} = $name if $name; + my $query = "INSERT INTO host_devices " ."( ".join(", ",sort keys %$info)." ) " ." VALUES ( ".join(", ",map { '?' } keys %$info)." ) " @@ -2750,7 +2786,7 @@ sub add_host_device($self, %args) { eval { $sth->execute(map { $info->{$_} } sort keys %$info ); }; - confess Dumper([$info,$@]) if $@; + die Dumper([$info,$@]) if $@; my $id = Ravada::Request->_last_insert_id( $$CONNECTOR ); diff --git a/lib/Ravada/VM/KVM.pm b/lib/Ravada/VM/KVM.pm index a1f5dc66a..f08d2060e 100644 --- a/lib/Ravada/VM/KVM.pm +++ b/lib/Ravada/VM/KVM.pm @@ -898,6 +898,7 @@ sub list_domains { } sub discover($self) { + return if !$self->vm; my @known = $self->list_domains(read_only => 1); my %known = map { $_->name => 1 } @known; @@ -1245,22 +1246,30 @@ sub _domain_create_from_base { confess "argument id_base or base required ".Dumper(\%args) if !$args{id_base} && !$args{base}; - confess "Domain $args{name} already exists in ".$self->name - if $self->search_domain($args{name}); - - my $base = $args{base}; - my $with_cd = delete $args{with_cd}; - my $vm_local = $self; $vm_local = $self->new( host => 'localhost') if !$vm_local->is_local; + + my $base = $args{base}; $base = $vm_local->_search_domain_by_id($args{id_base}) if $args{id_base}; + confess "Unknown base id: $args{id_base}" if !$base; + my $volatile; + $volatile = $base->volatile_clones if $base; + $volatile = delete $args{volatile} if exists $args{volatile} && defined $args{volatile}; + + if ( my $dom = $self->search_domain($args{name})) { + if (!$self->is_local) { + $dom->_insert_db(name=> $args{name}, id_base => $base->id, id_owner => $args{id_owner} + , id_vm => $self->id + ) if !$dom->is_known(); + return $dom; + } else { + confess "Domain $args{name} already exists in ".$self->name; + } + } - confess Dumper(\%args) if $args{name} eq 'tst_device_50_nodes_01' - && ( !exists $args{volatile} || !defined $args{volatile}); + my $with_cd = delete $args{with_cd}; - my $volatile = $base->volatile_clones; - $volatile = delete $args{volatile} if exists $args{volatile} && defined $args{volatile}; my $options = delete $args{options}; my $network = delete $options->{network}; @@ -3009,6 +3018,7 @@ sub can_list_cpu_models($self) { sub list_virtual_networks($self) { my @networks; + return if !$self->vm; for my $net ($self->vm->list_all_networks()) { my $doc = XML::LibXML->load_xml(string => $net->get_xml_description); my ($ip_doc) = $doc->findnodes("/network/ip"); diff --git a/public/js/ravada.js b/public/js/ravada.js index 147aa448b..550c3a2f6 100644 --- a/public/js/ravada.js +++ b/public/js/ravada.js @@ -413,6 +413,12 @@ $scope.node = response.data; }); }; + list_nodes=function() { + $http.get('/list_nodes_by_id.json') + .then(function(response) { + $scope.nodes_by_id = response.data; + }); + }; var subscribed_extra = false; var subscribe_machine_info= function(url) { @@ -619,6 +625,9 @@ = $scope.showmachine.auto_compact; load_balance_options(); get_node_info($scope.showmachine.id_vm); + if (is_admin) { + list_nodes(); + } $http.get('/list_storage_pools/'+$scope.showmachine.type+"?active=1") .then(function(response) { $scope.list_storage= response.data; @@ -1393,6 +1402,9 @@ var data = JSON.parse(event.data); $scope.$apply(function () { $scope.request = data; + if (/Unknown (domain|machine)/i.exec($scope.request.error)) { + $scope.domain=undefined; + } }); if ( data.id_domain && ! already_subscribed_to_domain ) { already_subscribed_to_domain = true; diff --git a/script/rvd_back b/script/rvd_back index 92ffd4052..3b7ca9c2f 100755 --- a/script/rvd_back +++ b/script/rvd_back @@ -334,6 +334,8 @@ sub start { autostart_machines($ravada); Ravada::Request->update_iso_urls(uid =>Ravada::Utils::user_daemon->id); Ravada::Request->refresh_storage(); + Ravada::Request->list_host_devices(uid =>Ravada::Utils::user_daemon->id); + } clean_old_requests(); for (;;) { diff --git a/script/rvd_front b/script/rvd_front index 41d495206..a4ddf271f 100644 --- a/script/rvd_front +++ b/script/rvd_front @@ -578,17 +578,26 @@ get '/machine/host_device/add/(#id_domain)/(#id_hd)' => sub($c) { post '/node/host_device/add' => sub($c) { my $arg = decode_json($c->req->body); my $id_vm = $arg->{id_vm} or die "Error: missing id_vm ".Dumper($arg); + my $name = $arg->{name}; + my $vm = Ravada::VM->open( readonly => 1 ,id => $id_vm ); - my $id = $vm->add_host_device(template => $arg->{template}); + my @args = ( template => $arg->{template} ); + push @args, ( name => $name ) if $name; + + my $id; + eval { $id = $vm->add_host_device(@args) }; + if ($@) { + return $c->render( json => { ok => 0, error => ''.$@ } ); + } Ravada::Request->list_host_devices( uid => Ravada::Utils::user_daemon->id ,id_host_device => $id ,_force => 1 - ); + ) if $id; - return $c->render( json => { ok => 1 } ); + return $c->render( json => { ok => $id } ); }; post '/node/host_device/update' => sub($c) { @@ -1178,6 +1187,7 @@ get '/machine/view/(:id).(:type)' => sub { my ($domain) = _search_requested_machine($c); return access_denied($c) if !$domain; + $c->stash(id_base => undef); return view_machine($c) if $USER->is_admin || $USER->can_view_all; @@ -3772,6 +3782,12 @@ sub base_id { sub provision_req($c, $id_base, $name, $ram=0, $disk=0) { + my $enable_host_devices = $c->req->param('enable_host_devices'); + $enable_host_devices=1 if !defined $enable_host_devices; + my @enable_host_devices; + @enable_host_devices = ( 'enable_host_devices' => $enable_host_devices ); + + my @after_req; if ( $RAVADA->domain_exists($name) ) { my $domain = $RAVADA->search_domain($name); if ( $domain->id_owner == $USER->id @@ -3794,11 +3810,20 @@ sub provision_req($c, $id_base, $name, $ram=0, $disk=0) { app->log->info($USER->name." start_domain ".$domain->name." from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log}; - return Ravada::Request->start_domain( + if ($domain->is_volatile && !$domain->is_active) { + my $req_remove = Ravada::Request->remove_domain( + uid => Ravada::Utils::user_daemon->id + ,name => $name + ); + @after_req = ( after_request => $req_remove ); + } else { + return Ravada::Request->start_domain( uid => $USER->id , id_domain => $domain->id , remote_ip => _remote_ip($c) - ) + , @enable_host_devices + ) + } } } @@ -3808,10 +3833,13 @@ sub provision_req($c, $id_base, $name, $ram=0, $disk=0) { my @create_args = ( start => 1, remote_ip => _remote_ip($c)); push @create_args, ( memory => $ram ) if $ram; push @create_args, ( disk => $disk) if $disk; + my $req = Ravada::Request->clone( id_domain => $id_base ,uid => $USER->id ,@create_args + ,@after_req + , @enable_host_devices ); return $req; @@ -4154,6 +4182,7 @@ sub clone_machine($c, $anonymous=0) { $c->stash( error => "Unknown base ") if !$c->stash('error'); return $c->render(template => 'main/fail'); }; + $c->stash(id_base => $base->id); return quick_start_domain($c, $base->id, $USER->name, $anonymous); } @@ -4163,12 +4192,17 @@ sub shutdown_machine { my ($domain, $type) = _search_requested_machine($c); - my $req; - $req = Ravada::Request->force_shutdown_domain(id_domain => $domain->id, uid => $USER->id) if ($c->param('force')); - $req = Ravada::Request->shutdown_domain(id_domain => $domain->id, uid => $USER->id) unless ($c->param('force')); - + my $id_req; + if ($domain) { + my $req; + $req = Ravada::Request->force_shutdown_domain(id_domain => $domain->id, uid => $USER->id) + if ($c->param('force')); + $req = Ravada::Request->shutdown_domain(id_domain => $domain->id, uid => $USER->id) + unless ($c->param('force')); + $id_req = $req->id if $req; + } return $c->redirect_to('/machines') if $type eq 'html'; - return $c->render(json => { req => $req->id }); + return $c->render(json => { req => $id_req }); } sub reboot_machine { diff --git a/t/device/00_host_device.t b/t/device/00_host_device.t index 8366d80b0..b8e30a8eb 100644 --- a/t/device/00_host_device.t +++ b/t/device/00_host_device.t @@ -20,6 +20,8 @@ use_ok('Ravada::HostDevice'); use_ok('Ravada::HostDevice::Templates'); my $N_DEVICE = 0; +$Ravada::Domain::TTL_REMOVE_VOLATILE=3; + ######################################################### sub _search_unused_device { @@ -311,14 +313,15 @@ sub test_host_device_usb($vm) { test_kvm_usb_template_args($device, $list_hostdev_c[0]); _check_hostdev($clone); - $clone->start(user_admin); + eval { $clone->start(user_admin) }; + is(''.$@,'') or die $@; _check_hostdev($clone, 1); shutdown_domain_internal($clone); _check_hostdev($clone, 1) or exit; eval { $clone->start(user_admin) }; - is(''.$@, '') or exit; - _check_hostdev($clone, 1) or exit; + is(''.$@, '') or die; + _check_hostdev($clone, 1) or die; #### it will fail in another clone @@ -329,7 +332,7 @@ sub test_host_device_usb($vm) { eval { $clone2->start(user_admin) }; last if $@; } - like ($@ , qr(No available devices)); + like ($@ , qr(No available devices)) or exit; $list_hostdev[0]->remove(); my @list_hostdev2 = $vm->list_host_devices(); @@ -429,9 +432,20 @@ sub test_host_device_usb_mock($vm, $n_hd=1) { sleep 1; $clones[0]->shutdown_now(user_admin); _check_hostdev($clones[0], 0); - my @devs_attached = $clones[0]->list_host_devices_attached(); + my @devs_attached; + my $locked=0; + for ( 1 .. 3 ) { + @devs_attached = $clones[0]->list_host_devices_attached(); + for (@devs_attached) { + $locked++ if $_->{is_locked}; + } + last if !$locked; + + sleep 1; + $clones[0]->shutdown_now(user_admin); + } is(scalar(@devs_attached), $n_hd); - is($devs_attached[0]->{is_locked},0) or die Dumper(\@devs_attached); + is($locked,0) or die Dumper(\@devs_attached); for (@list_hostdev) { $_->_data('enabled' => 0 ); diff --git a/t/device/10_templates.t b/t/device/10_templates.t index b0d8be1f8..0e55ec341 100644 --- a/t/device/10_templates.t +++ b/t/device/10_templates.t @@ -17,6 +17,8 @@ use feature qw(signatures); use_ok('Ravada::HostDevice::Templates'); +$Ravada::Domain::TTL_REMOVE_VOLATILE=3; + #################################################################### sub _set_hd_nvidia($hd) { diff --git a/t/device/40_mediated_device.t b/t/device/40_mediated_device.t index 04584feec..295c26afc 100644 --- a/t/device/40_mediated_device.t +++ b/t/device/40_mediated_device.t @@ -19,6 +19,8 @@ my $BASE; my $MOCK_MDEV; my $N_TIMERS; +$Ravada::Domain::TTL_REMOVE_VOLATILE=3; + #################################################################### sub _prepare_dir_mdev() { @@ -134,8 +136,8 @@ sub test_mdev($vm) { test_config_no_hd($domain); $domain->add_host_device($id); _req_start($domain); - is($hd->list_available_devices(), $n_devices-1); test_config($domain); + is($hd->list_available_devices(), $n_devices-1) or exit; sleep 3; _req_shutdown($domain); @@ -238,6 +240,8 @@ sub _change_timer($domain) { } else { die $domain->type; } + + $domain->_backup_config_no_hd(); } sub _base_timers_void($domain) { @@ -251,7 +255,8 @@ sub _base_timers_void($domain) { for my $timer (@timers) { push @$clock ,({timer => $timer }); } - $domain->_store(clock => $clock); + $config->{clock} = $clock; + $domain->reload_config($config); } sub _change_timer_void($domain) { @@ -261,7 +266,6 @@ sub _change_timer_void($domain) { $N_TIMERS = scalar(@$clock); - $domain->_store(clock => $clock); } sub _change_timer_kvm($domain) { @@ -537,6 +541,7 @@ sub test_xml_no_hd($domain) { sub test_base($domain) { + return if $domain->volatile_clones && $MOCK_MDEV && $domain->type ne 'Void'; my @args = ( uid => user_admin->id ,id_domain => $domain->id); Ravada::Request->shutdown_domain(@args); @@ -559,6 +564,9 @@ sub test_base($domain) { } sub test_volatile_clones($vm, $domain, $host_device) { + + $Ravada::Domain::TTL_REMOVE_VOLATILE = 1; + my @args = ( uid => user_admin->id ,id_domain => $domain->id); $domain->shutdown_now(user_admin) if $domain->is_active; @@ -603,7 +611,8 @@ sub test_volatile_clones($vm, $domain, $host_device) { for (1 .. 3 ) { $n_device = $host_device->list_available_devices(); - last if $n_device == $exp_avail; + my $clone_removed = rvd_back->search_domain($clone->name); + last if $n_device == $exp_avail && !$clone_removed; Ravada::Request->force_shutdown( uid => user_admin->id ,id_domain => $clone->id @@ -616,7 +625,7 @@ sub test_volatile_clones($vm, $domain, $host_device) { is($n_device,$exp_avail) or exit; my $clone_gone = rvd_back->search_domain($clone_data->{name}); - ok(!$clone_gone,"Expecting $clone_data->{name} removed on shutdown"); + ok(!$clone_gone,"Expecting [$clone_data->{id}] $clone_data->{name} removed on shutdown") or exit; my $clone_gone2 = $vm->search_domain($clone_data->{name}); ok(!$clone_gone2,"Expecting $clone_data->{name} removed on shutdown"); diff --git a/t/device/50_nodes.t b/t/device/50_nodes.t index 22253fd25..cfb2fec2e 100644 --- a/t/device/50_nodes.t +++ b/t/device/50_nodes.t @@ -124,7 +124,6 @@ sub _create_host_devices($node,$number, $type=undef) { $hd->remove; return; } - diag("creating mock devices because not enough found"); my ($list_command,$list_filter) = _create_mock_devices($node->[0], $number->[0], "USB" ); for my $i (1..scalar(@$node)-1) { die "Error, missing number[$i] ".Dumper($number) unless defined $number->[$i]; @@ -145,6 +144,8 @@ sub test_devices_v2($node, $number, $volatile=0, $type=undef) { return if $type && !$hd; die "Error: no hd found" if !$hd; + # iommu not configured still in test nodes for PCI in KVM + $MOCK_DEVICES=1 if $type && $type eq 'pci' && $node->[0]->type eq 'KVM'; test_assign_v2($hd,$node,$number, $volatile); @@ -202,9 +203,22 @@ sub test_assign_v2($hd, $node, $number, $volatile=0) { ,id_domain => $base->id ,name => 'usb controller' ); + my $new_mem = 2*1024; + Ravada::Request->change_hardware( + uid => user_admin->id + ,id_domain => $base->id + ,hardware=> 'memory' + ,data => { memory => $new_mem*1024,max_mem => ($new_mem+1)*1024 } + ); + wait_request(); $base->prepare_base(user_admin); for my $curr_node (@$node) { - $base->set_base_vm(id_vm => $curr_node->id, user => user_admin); + my $req=Ravada::Request->set_base_vm( + id_vm => $curr_node->id + ,id_domain => $base->id + ,uid => user_admin->id + ); + wait_request(debug => 0); } wait_request(debug=>0); @@ -221,8 +235,7 @@ sub test_assign_v2($hd, $node, $number, $volatile=0) { ,login => user_admin->name }; my $fd; - warn $n_expected; - for my $n (1 .. $n_expected) { + for my $n (1 .. $n_expected*2) { $fd = Ravada::WebSocket::_list_host_devices(rvd_front(),$ws); @@ -231,18 +244,21 @@ sub test_assign_v2($hd, $node, $number, $volatile=0) { is($domain->is_active,1) if $vm->type eq 'Void'; check_hd_from_node($domain,\%devices_nodes); my $hd_checked = check_host_device($domain); - next if $MOCK_DEVICES; push(@{$dupe{$hd_checked}},($domain->name." ".$base->id)); my $id_vm = $domain->_data('id_vm'); $found{$id_vm}++; - warn Dumper(\%found); + last if scalar(keys %found)>1; } - warn Dumper($fd); - ok(scalar(keys %found) > 1); + ok(scalar(keys %found) > 1) or exit; test_clone_nohd($hd, $base); - test_start_in_another_node($hd, $base); + my $more_than_one=0; + for (@$number) { + $more_than_one++ if $number>1; + } + + test_start_in_another_node($hd, $base) unless $more_than_one; remove_domain($base); } @@ -346,7 +362,7 @@ sub _req_clone($base, $name=undef) { ); wait_request(debug => 0, check_error => 0); if ($base->type eq 'KVM' && $MOCK_DEVICES) { - diag($req->error); + diag($req->error) if $req->error; } else { is($req->error, '') or confess; } @@ -374,7 +390,7 @@ sub _mock_start($domain) { } sub test_assign($vm, $node, $hd, $n_expected_in_vm, $n_expected_in_node) { - my $base = create_domain($vm); + my $base = create_domain_v2(vm=> $vm,memory => 333*1024); $base->add_host_device($hd); Ravada::Request->add_hardware( uid => user_admin->id @@ -451,7 +467,6 @@ sub test_clone_nohd($hd, $base) { my ($name, $req, $domain0); for ( 1 .. _count_devices($hd) ) { - diag("trying to overflow"); $name = new_domain_name(); my $req0 = Ravada::Request->clone( uid => user_admin->id @@ -557,9 +572,30 @@ sub check_host_device_void($domain) { sub check_host_device_kvm($domain) { my $doc = $domain->xml_description(); my $xml = XML::LibXML->load_xml(string => $doc); + my ($hd) = $xml->findnodes("/domain/devices/hostdev"); + + if ($hd->getAttribute('type') eq 'usb') { + return check_host_device_kvm_usb($xml); + } elsif ($hd->getAttribute('type') eq 'pci') { + return check_host_device_kvm_pci($xml); + } +} + +sub check_host_device_kvm_pci($xml) { + my ($address) = $xml->findnodes("/domain/devices/hostdev/source/address"); + my $domain = $address->getAttribute('domain'); + my $bus = $address->getAttribute('bus'); + my $slot = $address->getAttribute('slot'); + my $function = $address->getAttribute('function'); + + return ''."$domain-$bus-$slot-$function"; + +} + +sub check_host_device_kvm_usb($xml) { my ($hd_source) = $xml->findnodes("/domain/devices/hostdev/source"); - ok($hd_source) or return; my ($vendor) = $hd_source->findnodes("vendor"); + die $hd_source->toString() if !$vendor; my $vendor_id=$vendor->getAttribute('id'); my ($product) = $hd_source->findnodes("product"); my $product_id=$product->getAttribute('id'); @@ -619,6 +655,7 @@ for my $vm_name (vm_names() ) { test_devices_v2([$vm,$node1,$node2],[1,1,1], undef, 'pci'); } + # at least one 1,1,1 must be tested test_devices_v2([$vm,$node1,$node2],[1,1,1]); test_devices_v2([$vm,$node1,$node2],[1,3,1]); test_devices_v2([$vm,$node1,$node2],[1,1,3]); diff --git a/t/kvm/71_description_clones.t b/t/kvm/71_description_clones.t index ba5fc1aa1..ed25051fa 100644 --- a/t/kvm/71_description_clones.t +++ b/t/kvm/71_description_clones.t @@ -87,13 +87,17 @@ sub test_prepare_base { test_files_base($domain,0); $domain->shutdown_now($USER) if $domain->is_active(); + ok($domain->list_volumes); + is(scalar($domain->list_volumes), scalar($domain->list_volumes_info));; eval { $domain->prepare_base( user_admin ) }; is(''.$@, '', "[$vm_name] expecting no error preparing ".$domain->name); ok($domain->is_base); is($domain->is_active(),0); + test_files_base($domain,1); my $description = "This is a description test"; add_description($domain, $description); + ok($domain->list_volumes); eval { $domain->prepare_base( user_admin ) }; $@ = '' if !defined $@; diff --git a/t/kvm/n10_nodes.t b/t/kvm/n10_nodes.t index ec5dcae7c..e1e24ffed 100644 --- a/t/kvm/n10_nodes.t +++ b/t/kvm/n10_nodes.t @@ -566,8 +566,11 @@ sub test_rsync_newer { # on starting it should sync is($domain->_vm->host, $node->host); - $domain->start(user => user_admin); - is($domain->_vm->host, $node->host); + $domain->_data('id_vm' => $vm->id); + + my $domain2 = Ravada::Domain->open($domain->id); + $domain2->start(user => user_admin); + is($domain2->_vm->host, $node->host); { # syncs for start, so vols should be equal my $vol3 = $vm->search_volume($vol_name); @@ -594,7 +597,7 @@ sub test_bases_node { $domain->prepare_base(user_admin); is($domain->base_in_vm($domain->_vm->id), 1); - is($domain->base_in_vm($node->id), undef); + is($domain->base_in_vm($node->id), 0); $domain->migrate($node); is($domain->_vm->id, $node->id) or exit; @@ -654,7 +657,7 @@ sub test_clone_make_base { $domain->prepare_base(user_admin); is($domain->base_in_vm($domain->_vm->id), 1); - is($domain->base_in_vm($node->id), undef) or exit; + is($domain->base_in_vm($node->id), 0) or exit; $domain->set_base_vm(vm => $node, user => user_admin); is($domain->base_in_vm($node->id), 1); @@ -707,11 +710,11 @@ sub test_bases_different_storage_pools { $domain->prepare_base(user_admin); is($domain->base_in_vm($domain->_vm->id), 1); - is($domain->base_in_vm($node->id), undef); + is($domain->base_in_vm($node->id), 0); eval {$domain->migrate($node) }; like($@, qr'storage pool.*not found'i); - is($domain->base_in_vm($node->id), undef); + is($domain->base_in_vm($node->id), 0); _enable_storage_pools($node); @@ -737,6 +740,7 @@ sub test_clone_not_in_node { my @clones; for ( 1 .. 10 ) { my $clone1 = $domain->clone(name => new_domain_name, user => user_admin); + diag($clone1->name); push @clones,($clone1); is($clone1->_vm->host, 'localhost'); eval { $clone1->start(user_admin) }; @@ -898,7 +902,7 @@ sub test_node_inactive($vm_name, $node) { start_node($node); - hibernate_node($node); + shutdown_node($node); is($node->ping, 0); is($node->_do_is_active,0); is($node->_data('is_active'), 0); @@ -1164,9 +1168,6 @@ SKIP: { next; }; - # remove - test_clone_not_in_node($vm_name, $node); - is($node->is_local,0,"Expecting ".$node->name." ".$node->ip." is remote" ) or BAIL_OUT(); test_already_started_hibernated($vm_name, $node); diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index 4b2753f73..1ab20576a 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -2192,7 +2192,11 @@ sub shutdown_node($node) { } sleep 1; } - $domain_node->shutdown_now(user_admin) if $domain_node->is_active; + $domain_node->shutdown_now(user_admin);# if $domain_node->is_active; + for my $req ( $domain_node->list_requests) { + diag($req->command); + $req->_delete(); + } is($node->ping(undef,0),0); } @@ -2230,7 +2234,7 @@ sub start_node($node) { Ravada::Request->connect_node(uid => user_admin->id ,id_node => $node->id ); - wait_request(); + wait_request(check_error => 0); eval { $node->disconnect; $node->clear_netssh(); diff --git a/t/mojo/70_volatile.t b/t/mojo/70_volatile.t index 2c6393769..fe66c46c2 100644 --- a/t/mojo/70_volatile.t +++ b/t/mojo/70_volatile.t @@ -260,7 +260,8 @@ sub test_clone($vm_name, $n=undef) { $base->is_public(1); $base->volatile_clones(1); - is($base->_data('id_vm'), $id_vm) or die $base->name; + my $base2 = Ravada::Front::Domain->open($base->id); + is($base2->_data('id_vm'), $id_vm) or die $base->name; Ravada::Request->remove_clones( uid => user_admin->id @@ -451,6 +452,17 @@ sub _clean_old_known($vm_name) { } } +sub _id_vm_local($vm_name) { + my $sth = connector->dbh->prepare("SELECT id FROM vms " + ." WHERE vm_type=? AND hostname='localhost'" + ); + $sth->execute($vm_name); + my ($id) = $sth->fetchrow; + die "Error no id in vms for type=$vm_name" if !defined $id; + return $id; + +} + sub _clean_old_bases($vm_name, $wait=1) { my $sth = connector->dbh->prepare("SELECT name FROM domains " ." WHERE is_base=1 AND (id_base IS NULL or id_base=0)" @@ -475,6 +487,13 @@ sub _clean_old_bases($vm_name, $wait=1) { ,uid => user_admin->id ); } + + if ($base->_data('id_vm') != _id_vm_local($vm_name)) { + Ravada::Request->remove_domain( + name => $base->name + ,uid => user_admin->id + ); + } } wait_request() if$wait; } @@ -521,6 +540,7 @@ for my $vm_name (@{rvd_front->list_vm_types} ) { } remove_networks_req(); +remove_old_domains_req(0); # 0=do not wait for them +remove_old_users(); -end(); done_testing(); diff --git a/t/mojo/80_check_resources.t b/t/mojo/80_check_resources.t index 503c8f78e..f924e17a7 100644 --- a/t/mojo/80_check_resources.t +++ b/t/mojo/80_check_resources.t @@ -209,6 +209,6 @@ for my $vm_name (@{rvd_front->list_vm_types} ) { } remove_old_domains_req(0); # 0=do not wait for them +remove_old_users(); -end(); done_testing(); diff --git a/t/mojo/90_host_devices.t b/t/mojo/90_host_devices.t new file mode 100644 index 000000000..e665eb591 --- /dev/null +++ b/t/mojo/90_host_devices.t @@ -0,0 +1,203 @@ +use warnings; +use strict; + +use Carp qw(confess); +use Data::Dumper; +use Test::More; +use Test::Mojo; +use Mojo::File 'path'; +use Mojo::JSON qw(decode_json); + +use lib 't/lib'; +use Test::Ravada; + +no warnings "experimental::signatures"; +use feature qw(signatures); + +$ENV{MOJO_MODE} = 'development'; +my $SCRIPT = path(__FILE__)->dirname->sibling('../script/rvd_front'); + +my ($USERNAME, $PASSWORD); + +my $URL_LOGOUT = '/logout'; + +$Test::Ravada::BACKGROUND=1; +my $t; + +my $BASE_NAME="zz-test-base-alpine"; +my $BASE; + + +############################################################################### + +sub _import_base($vm_name) { + mojo_login($t,$USERNAME, $PASSWORD); + my $name = new_domain_name()."-".$vm_name."-$$"; + if ($vm_name eq 'KVM') { + my $base0 = rvd_front->search_domain($BASE_NAME); + mojo_request_url_post($t,"/machine/copy",{id_base => $base0->id, new_name => $name, copy_ram => 0.128, copy_number => 1}); + for ( 1 .. 90 ) { + $BASE= rvd_front->search_domain($name); + last if $BASE; + wait_request(); + } + + } else { + $BASE = mojo_create_domain($t, $vm_name); + } + + Ravada::Request->shutdown_domain(uid => user_admin->id + ,id_domain => $BASE->id); + my $req = Ravada::Request->prepare_base(uid => user_admin->id + ,id_domain => $BASE->id + ); + wait_request(); + is($req->error,''); + + $BASE->_data('shutdown_disconnected' => 1); + +} + +sub _list_host_devices($id_vm) { + $t->get_ok("/list_host_devices/$id_vm")->status_is(200); + + my $body = $t->tx->res->body; + my $hd0; + eval { $hd0 = decode_json($body) }; + is($@, '') or return; + return $hd0; +} + +sub create_hd($vm_name) { + my $id_vm = _id_vm($vm_name); + + my $hd0 = _list_host_devices($id_vm); + + $t->get_ok('/host_devices/templates/list/'.$id_vm)->status_is(200); + + my $body = $t->tx->res->body; + my $templates; + eval { $templates = decode_json($body) }; + is($@, '') or return; + my ($template) = $templates->[0]->{name}; + + $t->post_ok("/node/host_device/add", + json => { id_vm => $id_vm, template => $template ,name => new_domain_name() } + )->status_is(200); + + like($t->tx->res->code(),qr/^(200|302)$/) or die $t->tx->res->body; + + my $hd1 = _list_host_devices($id_vm); + + my $hd; + my %hd0; + for my $curr ( @$hd0 ) { + $hd0{$curr->{name}}++; + } + for my $curr ( @$hd1 ) { + $hd = $curr if !$hd0{$curr->{name}}; + } + + _rename_hd($hd); + + return $hd; +} + +sub _rename_hd($hd) { + $t->post_ok("/node/host_device/update", + json => { id => $hd->{id}, name => new_domain_name } + )->status_is(200); + +} + +sub _id_vm($vm_name) { + my $sth = connector->dbh->prepare( + "SELECT id FROM vms " + ." WHERE vm_type=?" + ." AND hostname='localhost'" + ); + $sth->execute($vm_name); + my ($id) = $sth->fetchrow; + die "Error: no $vm_name found in VMs" if !$id; + return $id; +} + +sub test_base_hd($vm_name, $hd) { +#get('/list_host_devices/'.$id_base); + + my $id_vm = _id_vm($vm_name); + + confess Dumper($hd) if !exists $hd->{id} || !$hd->{id}; + + $t->get_ok('/machine/host_device/add/'.$BASE->id + ."/".$hd->{id})->status_is(200); + + my $res; + eval { $res = decode_json($t->tx->res->body) }; + is($@, '') or return; + is($res->{error}, ''); + is($res->{ok}, 1); + + $t->get_ok('/machine/info/'.$BASE->id.".json")->status_is(200); + + my $info; + eval { $info = decode_json($t->tx->res->body) }; + ok($info->{host_devices}) or die $BASE->name; + is(scalar(@{$info->{host_devices}}),1) or die $BASE->name; + +} + +sub clean_hds() { + my $sth = connector->dbh->prepare( + "SELECT id FROM host_devices " + ." WHERE name like ?" + ); + $sth->execute(base_domain_name().'%'); + + while (my ($id) = $sth->fetchrow ) { + $t->get_ok('/node/host_device/remove/'.$id)->status_is(200); + } +} + +######################################################### +$ENV{MOJO_MODE} = 'development'; +init('/etc/ravada.conf',0); +my $connector = rvd_back->connector; +like($connector->{driver} , qr/mysql/i) or BAIL_OUT; + +if (!ping_backend()) { + diag("SKIPPED: no backend"); + done_testing(); + exit; +} +$Test::Ravada::BACKGROUND=1; + +$t = Test::Mojo->new($SCRIPT); +$t->ua->inactivity_timeout(900); +$t->ua->connect_timeout(60); + +#remove_old_domains_req(0); + + +$USERNAME = user_admin->name; +$PASSWORD = "$$ $$"; + +mojo_login($t,$USERNAME, $PASSWORD); + +clean_hds(); + +for my $vm_name (reverse @{rvd_front->list_vm_types} ) { + diag("Testing host devices in $vm_name"); + + _import_base($vm_name); + + my $hd = create_hd($vm_name); + test_base_hd($vm_name, $hd) if $hd; + +} + +remove_old_domains_req(0); # 0=do not wait for them +clean_hds(); +remove_old_users(); + +done_testing(); diff --git a/t/nodes/10_basic.t b/t/nodes/10_basic.t index 80b8976f0..4a933a600 100644 --- a/t/nodes/10_basic.t +++ b/t/nodes/10_basic.t @@ -440,7 +440,8 @@ sub test_removed_base_file_and_swap_remote($vm, $node) { } ok(grep { $_->command eq 'set_base_vm' } $base->list_requests) or die $vm->type." ".Dumper([$base->list_requests]); - is(scalar($base->list_vms),1) or exit; + is(scalar($base->list_vms(0,1)),1) #hostdev=0 , only_avail=1 + or exit; wait_request(debug => 0); is($base->base_in_vm($node->id),1); my $node2 = Ravada::VM->open($node->id); @@ -660,6 +661,7 @@ sub test_volatile_req($vm, $node) { $clone = rvd_back->search_domain($clone_name); is($clone->is_active(),1,"[".$vm->type."] expecting clone ".$clone->name ." active on node ".$clone->_vm->name); + is($clone->is_volatile,1); push @clones,($clone); last if $clone->_vm->id == $node->id; } @@ -669,7 +671,7 @@ sub test_volatile_req($vm, $node) { rvd_back->_cmd_refresh_vms(); for my $vol ( $clone->list_volumes ) { ok(!$vm->file_exists($vol),$vol) or exit; - ok(!$node->file_exists($vol),$vol) or exit; + ok(!$node->file_exists($vol),$vol." in ".$node->name) or exit; } _remove_domain($base); } @@ -693,6 +695,7 @@ sub test_domain_gone($vm, $node) { } sub test_volatile_req_clone($vm, $node, $machine='pc-i440fx') { + start_node($node); if ($vm->type eq 'KVM') { my $id_iso = search_id_iso('Alpine%64'); my $iso = $vm->_search_iso($id_iso); @@ -1224,6 +1227,8 @@ sub test_fill_memory($vm, $node, $migrate) { sub test_migrate($vm, $node) { diag("Test migrate"); + + start_node($node); my $domain = create_domain($vm); $domain->migrate($node); @@ -1534,6 +1539,7 @@ sub test_display_ip($vm, $node, $set_localhost_dp=0) { } sub test_nat($vm, $node, $set_localhost_natip=0) { + start_node($node); my $nat_ip_1 = "5.6.7.8"; $node->nat_ip($nat_ip_1); @@ -1714,6 +1720,8 @@ for my $vm_name (reverse vm_names() ) { start_node($node); + test_volatile_req($vm, $node); + test_domain_gone($vm, $node); if ($vm_name eq 'KVM') { @@ -1780,7 +1788,6 @@ for my $vm_name (reverse vm_names() ) { } test_clone_remote($vm, $node); - test_volatile_req($vm, $node); test_volatile_tmp_owner($vm, $node); test_reuse_vm($node); diff --git a/t/vm/20_base.t b/t/vm/20_base.t index 20274f745..6653d502e 100644 --- a/t/vm/20_base.t +++ b/t/vm/20_base.t @@ -532,10 +532,13 @@ sub test_display_info($vm) { is($display_h->[1+$TLS]->{ip}, $display_h->[0]->{ip}) or exit; is($display_h->[1+$TLS]->{listen_ip}, $display_h->[0]->{listen_ip}); is($display_h->[1+$TLS]->{id_domain_port},$exposed_port->{id}); # rdp needs exposed port - $domain->shutdown_now(user_admin()); + + my $domain2 = Ravada::Domain->open($domain->id); + + $domain2->shutdown_now(user_admin()); $domain_f = Ravada::Front::Domain->open($domain->id); - $domain->info(user_admin); + $domain2->info(user_admin); $info = $domain_f->info(user_admin); $display_h = $info->{hardware}->{display}; is($display_h->[0]->{is_active}, 0); @@ -2112,7 +2115,6 @@ for my $vm_name ( vm_names() ) { test_change_display_settings($vm); test_display_drivers($vm,0); test_display_drivers($vm,1); #remove after testing display type - test_display_info($vm); test_display_conflict_next($vm); test_display_iptables($vm); diff --git a/t/vm/p10_pools.t b/t/vm/p10_pools.t index 8804ed15f..7c9ce0aac 100644 --- a/t/vm/p10_pools.t +++ b/t/vm/p10_pools.t @@ -18,6 +18,8 @@ use feature qw(signatures); my $BASE_NAME = "zz-test-base-alpine"; my $BASE; +$Ravada::Domain::TTL_REMOVE_VOLATILE=1; + sub test_duplicate_req { my $req = Ravada::Request->manage_pools(uid => user_admin->id); my $req_dupe = Ravada::Request->manage_pools(uid => user_admin->id); @@ -671,6 +673,7 @@ sub test_create_more_clones_in_pool($base) { my @clones = grep { !$_->{is_base} } $base->clones ; my $n_clones = scalar(@clones); for my $clone ( @clones ) { + sleep 1 if $clone->{is_volatile}; Ravada::Request->shutdown_domain(uid => user_admin->id ,id_domain => $clone->{id} ); diff --git a/t/vm/v10_volatile.t b/t/vm/v10_volatile.t index a3efdc752..3d8c77280 100644 --- a/t/vm/v10_volatile.t +++ b/t/vm/v10_volatile.t @@ -22,7 +22,9 @@ init(); my $IP = "10.0.0.1"; my $NETWORK = $IP; -$NETWORK =~ s{(.*\.).*}{$1.0/24}; +$NETWORK =~ s{(.*)\..*}{$1.0/24}; + +$Ravada::Domain::TTL_REMOVE_VOLATILE = 1; ################################################################################ @@ -143,11 +145,14 @@ sub test_volatile { is($user->is_temporary,1); $user_id = $user->id; - my $clone = $base->clone( - user => $user + my $req = Ravada::Request->clone( + uid => $user->id + , id_domain => $base->id , name => $name + ,remote_ip => '192.0.9.1/32' ); - $clone->start($user) if !$clone->is_active; + wait_request(debug => 0); + my $clone = $vm->search_domain($name); is($clone->is_active,1,"[$vm_name] Expecting clone active"); like($clone->spice_password,qr{..+},"[$vm_name] ".$clone->name) @@ -164,6 +169,7 @@ sub test_volatile { my @volumes = $clone->list_volumes(); is($clone->is_active, 1); + sleep 1; eval { $clone->shutdown_now(user_admin) if $clone->is_active}; is(''.$@,'',"[$vm_name] Expecting no error after shutdown"); @@ -279,14 +285,31 @@ sub test_volatile_auto_kvm { sleep 1; } ok(!$domain2,"[$vm_name] Expecting domain $name removed after shutdown"); + wait_request(debug => 0); - rvd_back->_clean_volatile_machines(); - - rvd_back->_refresh_volatile_domains(); my $domain_f; - $domain_f = rvd_front->search_domain($name) if rvd_front->domain_exists($name); - ok(!$domain_f,"[$vm_name] Expecting domain $name removed after shutdown " - .Dumper($domain_f)) or exit; + for my $try ( 1 .. 3 ) { + + rvd_back->_clean_volatile_machines(); + rvd_back->_refresh_volatile_domains(); + + $domain_f = rvd_front->search_domain($name) if rvd_front->domain_exists($name); + last if !$domain_f; + warn $try; + + my $domain_real; + eval { $domain_real = Ravada::Domain->open($domain_f->id) }; + + warn Dumper([ $domain_real->is_active + ,$domain_real->is_locked + ,$domain_real->_volatile_active + ]) if $domain_real; + + sleep 1; + + wait_request(debug => 1); + } + ok(!$domain_f,"[$vm_name] Expecting domain $name removed after shutdown ") or exit; my $domain_b = rvd_back->search_domain($name); ok(!$domain_b,"[$vm_name] Expecting domain removed after shutdown"); diff --git a/t/vm/v20_volatile_clones.t b/t/vm/v20_volatile_clones.t index ffb22cebe..b7da72ec0 100644 --- a/t/vm/v20_volatile_clones.t +++ b/t/vm/v20_volatile_clones.t @@ -17,6 +17,8 @@ use Test::Ravada; init(); +$Ravada::Domain::TTL_REMOVE_VOLATILE = 1; + ######################################################################3 sub test_volatile_clone_req { my $vm = shift; @@ -204,6 +206,17 @@ sub test_enforce_limits { or exit; is($clone2->is_active,1 ); + for ( 1 .. 3 ) { + my $clone0_2 = $vm->search_domain($clone_name); + + my $clone0_f; + eval { $clone0_f = rvd_front->search_domain($clone_name) }; + + last if !$clone0_2 && !$clone0_f; + Ravada::Request->refresh_machine(uid => user_admin->id + ,id_domain => $clone->id, _force => 1); + wait_request(debug => 1); + } my $clone0_2 = $vm->search_domain($clone_name); is($clone0_2, undef); $clone0_2 = rvd_back->search_domain($clone_name); @@ -222,9 +235,7 @@ sub test_enforce_limits { eval { $clone2->remove(user_admin) }; is(''.$@,''); - eval { $clone->remove(user_admin) if !$clone->is_removed() }; - is(''.$@,''); - $domain->remove(user_admin); + remove_domain($domain); $user->remove(); } @@ -240,23 +251,35 @@ sub test_internal_shutdown { my $clone_name = new_domain_name(); my $user = create_user('Roland','Pryzbylewski'); my $clone = $domain->clone( user => $user , name => $clone_name); + is($clone->is_volatile,1); my @volumes = $clone->list_volumes(); + sleep 1; shutdown_domain_internal($clone); rvd_back->_cmd_refresh_vms(); - my $clone0_f; + for ( 1 .. 5 ) { + my $clone0_2 = $vm->search_domain($clone_name); + eval { $clone0_f = rvd_front->search_domain($clone_name) }; + + last if !$clone0_2 && !$clone0_f; + + Ravada::Request->refresh_machine(uid => user_admin->id + ,id_domain => $clone->id, _force => 1); + wait_request(debug => 1); + } + eval { $clone0_f = rvd_front->search_domain($clone_name) }; - is($clone0_f, undef); + is($clone0_f, undef) or die $clone_name; my $list_domains = rvd_front->list_domains(); ($clone0_f) = grep { $_->{name} eq $clone_name } @$list_domains; is($clone0_f, undef); for my $vol ( @volumes ) { - ok(!-e $vol,"Expecting $vol removed"); + ok(!-e $vol,"Expecting $vol removed") or exit; } my $domain2 = rvd_back->search_domain($clone_name); @@ -542,6 +565,8 @@ for my $vm_name ( vm_names() ) { skip($msg,10) if !$vm; diag("Testing volatile clones for $vm_name"); + test_internal_shutdown($vm); + test_cleanup($vm); test_req_volatile($vm); diff --git a/templates/bootstrap/requests.html.ep b/templates/bootstrap/requests.html.ep index 1ca83662b..b4c18491b 100644 --- a/templates/bootstrap/requests.html.ep +++ b/templates/bootstrap/requests.html.ep @@ -64,6 +64,7 @@ >
{{request.date}} + {{request.id}} - {{request.command}} {{request.domain}} diff --git a/templates/main/run_request.html.ep b/templates/main/run_request.html.ep index 3ff5f921f..70cbbe7fd 100644 --- a/templates/main/run_request.html.ep +++ b/templates/main/run_request.html.ep @@ -12,7 +12,9 @@ >
-

<%=l 'Running' %> {{domain.alias}}

+

<%=l 'Running' %> {{domain.alias}} + <%=l 'Volatile' %> +

<%=l 'A viewer is required to run the virtual machines.' %> <%=l 'Read more.' %> @@ -22,7 +24,7 @@ || (domain && domain.is_base ) || (domain && domain.is_active==0 && count_start<=2)" ><%=l 'Waiting for machine to start' %>
-
+
{{domain.description}} @@ -103,14 +105,26 @@
+
{{request.error}}
+ +
diff --git a/templates/main/vm_hostdev.html.ep b/templates/main/vm_hostdev.html.ep index 4759d6189..d1eadebc4 100644 --- a/templates/main/vm_hostdev.html.ep +++ b/templates/main/vm_hostdev.html.ep @@ -13,7 +13,7 @@ ng-click="toggle_host_device(hdev.id)"/> {{hdev.name}}
  • - {{node}} + {{nodes_by_id[node]}}
    • <%=l 'No devices found'%> @@ -39,6 +39,6 @@
Manage Host Devices + href="/admin/hostdev/{{showmachine.type}}">Manage Host Devices %= include "/main/pending_request"