diff --git a/lib/Ravada.pm b/lib/Ravada.pm index 3b2898c66..ec51a0b96 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -1665,6 +1665,10 @@ sub _add_indexes_generic($self) { "index(id_domain)" ,"unique (id_bundle, id_domain)" ] + ,vm_which => [ + "index(id_vm)" + ,"unique(id_vm,command,path)" + ] ); my $if_not_exists = ''; $if_not_exists = ' IF NOT EXISTS ' if $CONNECTOR->dbh->{Driver}{Name} =~ /sqlite|mariadb/i; @@ -2457,6 +2461,14 @@ sub _sql_create_tables($self) { ,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' } ] + , + [vm_which => { + id => 'integer PRIMARY KEY AUTO_INCREMENT', + ,id_vm => 'integer NOT NULL references `vms` (`id`) ON DELETE CASCADE', + ,command => 'char(40)' + ,path => 'varchar(255)' + } + ] ); for my $new_table (@tables ) { diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index eb712a677..446521a0b 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -213,8 +213,7 @@ after 'screenshot' => \&_post_screenshot; after '_select_domain_db' => \&_post_select_domain_db; -before 'migrate' => \&_pre_migrate; -after 'migrate' => \&_post_migrate; +around 'migrate' => \&_around_migrate; around 'get_info' => \&_around_get_info; around 'set_max_mem' => \&_around_set_max_mem; @@ -329,7 +328,7 @@ sub _around_start($orig, $self, @arg) { $enable_host_devices = 1 if !defined $enable_host_devices; my $is_volatile = ($self->is_volatile || $arg{is_volatile}); - for (1 .. 5) { + for (1 .. 2) { eval { $self->_start_checks(%arg, enable_host_devices => $enable_host_devices) }; my $error = $@; if ($error) { @@ -375,6 +374,8 @@ sub _around_start($orig, $self, @arg) { warn $error if $error; last if !$error; + $self->_dettach_host_devices() if $enable_host_devices && !$self->is_active(); + my $vm_name = Ravada::VM::_get_name_by_id($self->_data('id_vm')); die "Error: starting ".$self->name." on ".$vm_name." $error" @@ -389,12 +390,10 @@ sub _around_start($orig, $self, @arg) { && $error !~ /Could not run .*swtpm/i && $error !~ /virtiofs/ && $error !~ /child process/i + && $error !~ /host doesn.t support/ + && $error !~ /device not found/ ; - if ($error && $self->is_known && $self->id_base && !$self->is_local && $self->_vm->enabled) { - $self->_request_set_base(); - next; - } die $error; } $self->_post_start(%arg); @@ -579,16 +578,11 @@ sub _start_checks($self, @args) { 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($id_base,$self->_vm->id) - || !$self->_vm->ping) ) { - $self->_set_vm($vm_local, 1); - } if ( !$vm->is_alive ) { $vm->disconnect(); $vm->connect; $vm = $vm_local if !$vm->is_local && !$vm->is_alive; + die "Error: node ".$vm->name." is not alive" if !$vm->is_alive; }; if ($id_vm) { $self->_set_vm($vm); @@ -1185,22 +1179,19 @@ sub _check_free_vm_memory { 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); - $vol->delete(); + my $vm = $self->_vm; - my $base = Ravada::Domain->open($self->id_base); - my @volumes = $base->list_files_base_target; + my $base = Ravada::Domain->open($self->id_base); + my @volumes = $base->list_files_base_target; + for my $vol ( $self->list_volumes_info) { + next unless $vol->file && $vol->file =~ /\.(TMP|SWAP)\.\w+$/; 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); - } + next if !$file_base; + + $vol->delete(); my $vol_base = Ravada::Volume->new( file => $file_base->[0] , is_base => 1 - , vm => $vm_local + , vm => $vm ); $vol_base->clone(file => $vol->file); } @@ -5337,6 +5328,14 @@ sub _post_migrate($self, $node, $request = undef) { } +sub _around_migrate($orig, $self, $node, $request=undef) { + return if $self->_vm->id == $node->id; + + $self->_pre_migrate($node, $request); + $self->$orig($node, $request); + $self->_post_migrate($node, $request); +} + sub _id_base_in_vm($self, $id_vm) { my $sth = $$CONNECTOR->dbh->prepare( "SELECT id FROM bases_vm " diff --git a/lib/Ravada/Domain/KVM.pm b/lib/Ravada/Domain/KVM.pm index 96fd9045e..80d5ba05b 100644 --- a/lib/Ravada/Domain/KVM.pm +++ b/lib/Ravada/Domain/KVM.pm @@ -694,7 +694,8 @@ sub post_resume_aux($self, %args) { # 55: domain is not running # 74: not configured # 86: no agent - die "$@\n" if $@ && $@ !~ /libvirt error code: (55|74|86),/; + # 84: qemu doesn't support rtc-reset-reinjection command + die "$@\n" if $@ && $@ !~ /libvirt error code: (55|74|84|86),/; } sub set_time($self) { diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index 65e02f158..44cea7ca4 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -318,6 +318,7 @@ sub BUILD { } $self->id; + $self->_which_cache_fetch(); } sub _open_type { @@ -552,15 +553,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} && !$base->list_host_devices) { + 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); if (!$vm) { die "Error: No free nodes available.\n"; } + if (!$vm->is_local) { + if ( $base->_base_files_in_vm($vm) + && $base->_check_all_parents_in_node($vm)) { + $self = $vm; + } + } $request->status("creating machine on ".$vm->name) if $request; - $self = $vm; $args_create{listen_ip} = $self->listen_ip($remote_ip); } @@ -1405,8 +1411,9 @@ sub is_locked($self) { next if defined $at && $at < time + 2; next if !$args; my $args_d = decode_json($args); - if ( exists $args_d->{id_vm} && $args_d->{id_vm} == $self->id ) { - warn "locked by $command\n"; + if ( exists $args_d->{id_vm} + && $args_d->{id_vm} =~ /^\d+$/ + && $args_d->{id_vm} == $self->id ) { return 1; } } @@ -1776,6 +1783,7 @@ sub _do_is_active($self, $force=undef) { } sub _cached_active($self, $value=undef) { + $self->_which_cache_flush() if defined $value && $value && !$self->_data('is_active'); return $self->_data('is_active', $value); } @@ -2740,8 +2748,50 @@ sub _list_qemu_bridges($self) { return keys %bridge; } -sub _which($self, $command) { +sub _which_cache_fetch($self) { + my $sth = $self->_dbh->prepare( + "SELECT command,path FROM vm_which " + ." WHERE id_vm=?" + ); + $sth->execute($self->id); + while (my ($command, $path)) { + $self->{_which}->{$command} = $path; + } + $sth->finish; +} + + +sub _which_cache_get($self, $command) { return $self->{_which}->{$command} if exists $self->{_which} && exists $self->{_which}->{$command}; +} + +sub _which_cache_set($self, $command, $path) { + $self->{_which}->{$command} = $path; + + eval { + my $sth = $self->_dbh->prepare( + "INSERT INTO vm_which (id_vm, command, path)" + ." VALUES (?,?,?) " + ); + $sth->execute($self->id, $command, $path); + }; + warn("Warning: $@ vm_which = ( ".$self->id.", $command, $path )") + if $@ && $@ !~ /Duplicate entry/i + && $@ !~ /UNIQUE constraint failed/i + ; +} + +sub _which_cache_flush($self) { + my $sth = $self->_dbh->prepare( + "DELETE FROM vm_which where id_vm=?" + ); + $sth->execute($self->id); +} + +sub _which($self, $command) { + + my $cached = $self->_which_cache_get($command); + return $cached if $cached; my $bin_which = $self->{_which}->{which}; if (!$bin_which) { @@ -2757,7 +2807,9 @@ sub _which($self, $command) { my @cmd = ( $bin_which,$command); my ($out,$err) = $self->run_command(@cmd); chomp $out; - $self->{_which}->{$command} = $out; + + $self->_which_cache_set($command,$out); + return $out; } diff --git a/lib/Ravada/VM/KVM.pm b/lib/Ravada/VM/KVM.pm index 919ed7dbb..df27f539a 100644 --- a/lib/Ravada/VM/KVM.pm +++ b/lib/Ravada/VM/KVM.pm @@ -615,6 +615,7 @@ sub file_exists($self, $file) { } sub _file_exists_remote($self, $file) { + return 1 if $self->search_volume($file); $file = $self->_follow_link($file) unless $file =~ /which$/; return if !$self->vm; for my $pool ($self->vm->list_all_storage_pools ) { diff --git a/lib/Ravada/Volume.pm b/lib/Ravada/Volume.pm index 9105ca61d..bc9fc44ad 100644 --- a/lib/Ravada/Volume.pm +++ b/lib/Ravada/Volume.pm @@ -95,7 +95,7 @@ sub _type_from_extension($file) { } sub _type($file,$vm = undef) { - return _type_from_file($file,$vm) if $vm; + return _type_from_file($file,$vm) if $vm && $vm->is_local; return (_type_from_extension($file) or 'QCOW2'); } diff --git a/lib/Ravada/Volume/QCOW2.pm b/lib/Ravada/Volume/QCOW2.pm index a7a929120..9d718039c 100644 --- a/lib/Ravada/Volume/QCOW2.pm +++ b/lib/Ravada/Volume/QCOW2.pm @@ -17,7 +17,7 @@ has 'capacity' => ( ,builder => '_get_capacity' ); -our $QEMU_IMG = "/usr/bin/qemu-img"; +our $QEMU_IMG = "qemu-img"; sub prepare_base($self) { diff --git a/t/device/40_mediated_device.t b/t/device/40_mediated_device.t index 39c16370a..887d83f7a 100644 --- a/t/device/40_mediated_device.t +++ b/t/device/40_mediated_device.t @@ -447,6 +447,9 @@ sub test_mdev_kvm_state($vm) { test_old_in_locked($domain); test_timer($domain,'present' => 'yes'); + # TODO: + # test_locked_hd($domain); + _req_shutdown($domain); $domain->_dettach_host_devices(); @@ -459,6 +462,20 @@ sub test_mdev_kvm_state($vm) { } +sub test_locked_hd($domain) { + my $sth = connector->dbh->prepare("DELETE FROM host_devices_domain_locked WHERE id_domain=?"); + $sth->execute($domain->id); + + my @domain_hds = $domain->list_host_devices_attached(); + Ravada::Request->refresh_machine( + uid => user_admin->id + ,id_domain => $domain->id + ); + wait_request(); + @domain_hds = $domain->list_host_devices_attached(); + is($domain_hds[0]->{is_locked},1); +} + sub test_old_in_locked($domain) { my $sth = connector->dbh->prepare("SELECT config_no_hd FROM domains d " ." WHERE id=?" @@ -883,6 +900,28 @@ sub test_volatile_clones($vm, $domain, $host_device) { is($host_device->list_available_devices(), $max_n_device) or exit; } +sub test_start_failed($vm) { + return if $vm->type eq 'Void'; # Void HDs will not fail + my $hd = _create_mdev($vm); + + my $dir = _prepare_dir_mdev(); + $hd->_data('list_command' => "ls $dir"); + + my $domain = create_domain($vm); + $domain->add_host_device($hd->id); + + my $req = Ravada::Request->start_domain(uid => user_admin->id + ,id_domain => $domain->id + ); + wait_request( check_error => 0); + + test_xml_no_hd($domain); + + remove_domain($domain); + $hd->remove(); +} + + #################################################################### clean(); diff --git a/t/mojo/30_settings.t b/t/mojo/30_settings.t index 21c399ca8..eb86d5bd5 100644 --- a/t/mojo/30_settings.t +++ b/t/mojo/30_settings.t @@ -27,7 +27,7 @@ my %FILES; my %HREFS; my %MISSING_LANG = map {$_ => 1 } - qw(ca-valencia he ko); + qw(ca-valencia cs he ko); my $ID_DOMAIN; @@ -503,7 +503,7 @@ sub test_languages() { while (my $file = readdir $ls) { next if $file !~ /(.*)\.po$/; next if $MISSING_LANG{$1}; - ok($lang->{$1},"Expecting $1 in select"); + ok($lang->{$1},"Expecting $1 in language select"); } }