diff --git a/etc/xml/windows_11.xml b/etc/xml/windows_11.xml index 8e4db065d..12e575d3f 100644 --- a/etc/xml/windows_11.xml +++ b/etc/xml/windows_11.xml @@ -23,7 +23,7 @@ - + diff --git a/lib/Ravada.pm b/lib/Ravada.pm index d64765f01..bf8e8cc01 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -494,7 +494,8 @@ sub _update_isos { ,file_re => 'alpine-standard-3.16.*-x86_64.iso' ,sha256_url => '$url/alpine-standard-3.16.*.iso.sha256' ,min_disk_size => '2' - ,options => { machine => 'pc-q35', bios => 'UEFI' } + ,options => { machine => 'pc-q35', bios => 'UEFI' + } } ,alpine381_32 => { name => 'Alpine 3.16 32 bits' @@ -866,7 +867,9 @@ sub _update_isos { ,min_ram => 4 ,arch => 'x86_64' ,extra_iso => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.2\d+-\d+/virtio-win-0.1.2\d+.iso' - ,options => { machine => 'pc-q35', bios => 'UEFI' } + ,options => { machine => 'pc-q35', bios => 'UEFI' + ,hardware => { cpu => { cpu => { topology => { threads => 2, cores => 2}}}} + } } ,empty_32bits => { name => 'Empty Machine 32 bits' @@ -5157,6 +5160,51 @@ sub _cmd_reboot { } +sub _cmd_shutdown_start($self, $request) { + my $uid = $request->args('uid'); + my $id_domain = $request->args('id_domain'); + my $id_vm = $request->defined_arg('id_vm'); + + my $domain; + if ($id_vm) { + my $vm = Ravada::VM->open($id_vm); + $domain = $vm->search_domain_by_id($id_domain); + } else { + $domain = $self->search_domain_by_id($id_domain); + } + die "Unknown domain '$id_domain'\n" if !$domain; + + my $user = Ravada::Auth::SQL->search_by_id( $uid); + + die "USER $uid not authorized to restart machine ".$domain->name + unless $domain->_data('id_owner') == $user->id || $user->is_operator; + + my $timeout = ($request->defined_arg('timeout') or $domain->_timeout_shutdown() or 60); + + for my $try ( 0 .. 1 ) { + $domain->shutdown(timeout => $timeout, user => $user + , request => $request); + + for ( 0 .. $timeout+1 ) { + last if !$domain->is_active; + sleep 1; + } + last if !$domain->is_active; + } + + my $req_shutdown = Ravada::Request->force_shutdown_domain( + uid => $user->id + ,id_domain => $domain->id + ,after_request => $request->id + ); + + Ravada::Request->start_domain( + uid => $user->id + ,id_domain => $domain->id + ,after_request => $req_shutdown->id + ); +} + sub _cmd_force_reboot { my $self = shift; my $request = shift; @@ -6098,6 +6146,7 @@ sub _req_method { ,enforce_limits => \&_cmd_enforce_limits ,force_shutdown => \&_cmd_force_shutdown ,force_reboot => \&_cmd_force_reboot +,shutdown_start => \&_cmd_shutdown_start ,rebase => \&_cmd_rebase ,refresh_storage => \&_cmd_refresh_storage @@ -6468,6 +6517,8 @@ sub _cmd_close_exposed_ports($self, $request) { my $user = Ravada::Auth::SQL->search_by_id( $uid ) or die "Error: user $uid not found"; my $domain = Ravada::Domain->open($request->id_domain); + return if !$domain; + die "Error: user ".$user->name." not authorized to delete iptables rule" unless $user->is_admin || $domain->_data('id_owner') == $uid; diff --git a/lib/Ravada/Auth.pm b/lib/Ravada/Auth.pm index 10109c4f0..6fcb8a071 100644 --- a/lib/Ravada/Auth.pm +++ b/lib/Ravada/Auth.pm @@ -6,6 +6,7 @@ use strict; our $LDAP_OK; our $SSO_OK; +use Data::Dumper; use Ravada::Auth::SQL; =head1 NAME diff --git a/lib/Ravada/Auth/SSO.pm b/lib/Ravada/Auth/SSO.pm index 240cd1aa4..c01043ed6 100644 --- a/lib/Ravada/Auth/SSO.pm +++ b/lib/Ravada/Auth/SSO.pm @@ -72,6 +72,7 @@ sub _get_session_userid_by_ticket my ($cookie) = @_; my $result; die 'Can\'t read pubkey file (sso->cookie->pub_key value at ravada.conf file)' if (! -r $$CONFIG->{sso}->{cookie}->{pub_key}); + eval { $result = Authen::ModAuthPubTkt::pubtkt_verify(publickey => $$CONFIG->{sso}->{cookie}->{pub_key}, keytype => $$CONFIG->{sso}->{cookie}->{type}, ticket => $cookie); }; die $@ ? $@ : 'Cannot validate ticket' if ((! $result) || ($@)); my %data = Authen::ModAuthPubTkt::pubtkt_parse($cookie); @@ -123,6 +124,11 @@ sub init { return 0; } } + if (!$$CONFIG->{sso}->{cookie}->{type}) { + $ERR = "Error: missing sso / cookie / type in config file\n"; + warn $ERR unless $warn++; + return 0; + } for my $field (qw(priv_key pub_key)) { if ( !exists $$CONFIG->{sso}->{cookie}->{$field} || ! $$CONFIG->{sso}->{cookie}->{$field}) { diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index f3ab5d9ac..eb3f35758 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -303,6 +303,8 @@ sub _around_start($orig, $self, @arg) { $self->_start_preconditions(@arg); + $self->_pre_start_internal(); + $self->_data( 'post_shutdown' => 0); $self->_data( 'post_hibernated' => 0); @@ -702,7 +704,9 @@ sub _around_add_volume { ($name) = $file =~ m{.*/(.*)} if !$name && $file; $name = $self->name if !$name; - $name .= "-".$args{target}."-".Ravada::Utils::random_name(4); + $name .= "-".$args{target}."-".Ravada::Utils::random_name(4) + if $name !~ /\.iso$/; + $args{name} = $name; } @@ -715,10 +719,12 @@ sub _around_add_volume { $args{allocation} = Ravada::Utils::size_to_number($args{allocation}) if exists $args{allocation} && defined $args{allocation}; - my $free = $self->_vm->free_disk(); + my $storage = $args{storage}; + + my $free = $self->_vm->free_disk($storage); my $free_out = int($free / 1024 / 1024 / 1024 ) * 1024 *1024 *1024; - confess "Error creating volume, out of space $size . Disk free: " + die "Error creating volume, out of space $size . Disk free: " .Ravada::Utils::number_to_size($free_out) ."\n" if exists $args{size} && $args{size} && $args{size} >= $free; @@ -1661,6 +1667,10 @@ sub _data($self, $field, $value=undef, $table='domains') { sub _data_extra($self, $field, $value=undef) { $self->_insert_db_extra() if !$self->is_known_extra(); + if (defined $value) { + my $old = $self->_data_extra($field); + return if defined $old && $old eq $value; + } return $self->_data($field, $value, "domains_".lc($self->type)); } @@ -1941,9 +1951,6 @@ sub display($self, $user) { my ($display_info) = grep { $_->{driver} !~ /-tls$/ } @display_info; - confess "Error: I can't find builtin display info for ".$self->name." ".ref($self)."\n".Dumper($display_info) - if !exists $display_info->{port}; - return '' if !$display_info->{driver} || !$display_info->{ip} || !$display_info->{port}; @@ -3042,9 +3049,17 @@ sub _remove_start_requests($self) { } } +# it may be superceeded in child class +sub _post_shutdown_internal {} + +# it may be superceeded in child class +sub _pre_start_internal {} + sub _post_shutdown { my $self = shift; + $self->_post_shutdown_internal(); + my %arg = @_; my $timeout = delete $arg{timeout}; if (!defined $timeout) { @@ -4023,7 +4038,7 @@ sub _post_resume { return $self->_post_start(@_); } -sub _timeout_shutdown($self, $value) { +sub _timeout_shutdown($self, $value=undef) { $TIMEOUT_SHUTDOWN = $value if defined $value; return $TIMEOUT_SHUTDOWN; } @@ -4519,6 +4534,7 @@ Check if the domain has swap volumes defined, and clean them sub clean_swap_volumes { my $self = shift; + return if $self->is_active(); for my $vol ( $self->list_volumes_info) { confess if !$vol->domain; if ($vol->file && $vol->file =~ /\.SWAP\.\w+$/) { @@ -5667,6 +5683,7 @@ hardware change can be applied. =cut sub needs_restart($self, $value=undef) { + return $self->_data('needs_restart') if !defined $value; return $self->_data('needs_restart',$value); } @@ -5690,7 +5707,7 @@ sub _post_change_hardware($self, $hardware, $index, $data=undef) { } $self->info(Ravada::Utils->user_daemon) if $self->is_known(); - $self->needs_restart(1) if $self->is_known && $self->_data('status') eq 'active' && $hardware ne 'memory'; + $self->needs_restart(1) if $self->is_known && $self->_data('status') eq 'active' && $hardware ne 'memory' && $hardware !~ /cpu/; $self->post_prepare_base() if $self->is_base(); } diff --git a/lib/Ravada/Domain/KVM.pm b/lib/Ravada/Domain/KVM.pm index d42c912a4..aeb5e05b2 100644 --- a/lib/Ravada/Domain/KVM.pm +++ b/lib/Ravada/Domain/KVM.pm @@ -153,7 +153,10 @@ sub list_disks { for my $child ($disk->childNodes) { if ($child->nodeName eq 'source') { my $file = $child->getAttribute('file'); - next if $file =~ /\.iso$/; + if (!$file) { + $file = $child->getAttribute('name'); + } + next if !$file || $file =~ /\.iso$/; push @disks,($file); } } @@ -376,7 +379,9 @@ sub _disk_device($self, $with_info=undef, $attribute=undef, $value=undef) { for my $disk ($doc->findnodes('/domain/devices/disk')) { my ($source_node) = $disk->findnodes('source'); my $file; - $file = $source_node->getAttribute('file') if $source_node; + if ( $source_node ) { + $file = $self->_get_volume_file($source_node); + } my ($target_node) = $disk->findnodes('target'); my ($driver_node) = $disk->findnodes('driver'); @@ -571,7 +576,7 @@ sub _set_volumes_backing_store($self) { for my $disk ($doc->findnodes('/domain/devices/disk')) { next if $disk->getAttribute('device') ne 'disk'; for my $source( $disk->findnodes('source')) { - my $file = $source->getAttribute('file'); + my $file = $self->_get_volume_file($source); my $backing_file = $vol{$file}->backing_file(); $self->_set_backing_store($disk, $backing_file); @@ -581,6 +586,18 @@ sub _set_volumes_backing_store($self) { $self->reload_config($doc); } +sub _get_volume_file($self, $source) { + return $source->getAttribute('file') if $source->getAttribute('file'); + + my $pool_name = $source->getAttribute('pool') or die "Error: I need pool or file in ".$source->toString(); + my $volume = $source->getAttribute('volume') or die "Error: I need pool or file in ".$source->toString(); + my $pool = $self->_vm->vm->get_storage_pool_by_name($pool_name) + or die "Error: no pool $pool_name"; + my $vol = $pool->get_volume_by_name($volume); + return $vol->get_path; + +} + sub _store_xml($self) { my $xml = $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE); @@ -646,6 +663,7 @@ sub _detect_disks_driver($self) { my ( $source ) = $disk->findnodes('source'); my $file = $source->getAttribute('file'); + next if !$file; next if $file =~ /iso$/; next unless $self->_vm->file_exists($file); @@ -878,7 +896,7 @@ sub start { $self->status('starting'); my $error; - for ( ;; ) { + for ( 1 .. 60 ) { eval { $self->domain->create() }; $error = $@; next if $error && $error =~ /libvirt error code: 1, .* pool .* asynchronous/; @@ -970,6 +988,33 @@ sub shutdown { } +sub _pre_start_internal($self,@args) { + # remove current CPU before start because we want max cpu the next start + $self->_remove_current_cpu(); +} + +sub _post_shutdown_internal($self,@args) { + # remove current CPU after shutdown because we want max cpu the next start + $self->_remove_current_cpu(); +} + +sub _remove_current_cpu($self) { + my ($is_active,$doc); + eval { + $is_active = $self->is_active if $self->domain; + $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE)) if $self->domain; + }; + warn $@ if $@; + return if $is_active || !$doc; + + $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE)); + my ($cpu_node) = $doc->findnodes('/domain/vcpu'); + $cpu_node->removeAttribute('current'); + + $self->reload_config($doc); +} + + sub _do_shutdown { my $self = shift; return if !$self->domain->is_active; @@ -1644,8 +1689,16 @@ sub get_info { $info->{max_mem} = $mem_xml if $mem_xml ne $info->{max_mem}; $info->{cpu_time} = $info->{cpuTime}; - $info->{n_virt_cpu} = $info->{nrVirtCpu}; - confess Dumper($info) if !$info->{n_virt_cpu}; + + my ($cpu_text) = $doc->findnodes('/domain/vcpu/text()'); + $info->{max_virt_cpu} = 0+$cpu_text->getData(); + my ($cpu_node) = $doc->findnodes('/domain/vcpu'); + + if ($cpu_node->getAttribute('current')) { + $info->{n_virt_cpu} = 0+$cpu_node->getAttribute('current') + } else { + $info->{n_virt_cpu} = $info->{max_virt_cpu}; + } if ( $self->is_active() ) { $info->{ip} = $self->ip(); @@ -1655,6 +1708,13 @@ sub get_info { eval { @interfaces2 = $self->domain->get_interface_addresses(Sys::Virt::Domain::INTERFACE_ADDRESSES_SRC_AGENT) }; @interfaces = @interfaces2 if !scalar(@interfaces); $info->{interfaces} = \@interfaces; + + eval { + $info->{n_virt_cpu} + = $self->domain->get_vcpus(Sys::Virt::Domain::VCPU_GUEST); + }; + # warn error unless it is agent not responding + warn $@ if $@ && $@ !~ / error code: 86, / } lock_keys(%$info); @@ -2976,29 +3036,48 @@ sub _change_hardware_display($self, $index, $data) { sub _change_hardware_vcpus($self, $index, $data) { + confess "Error: I don't understand vcpus index = '$index' , only 0" if defined $index && $index != 0; - my $n_virt_cpu = delete $data->{n_virt_cpu}; + my $req_max = delete $data->{max_virt_cpu}; + my $req_current = delete $data->{n_virt_cpu}; + confess "Error: Unkown args ".Dumper($data) if keys %$data; - if ($self->domain->is_active) { + my $doc = XML::LibXML->load_xml(string => $self->xml_description); + my $changed =0; + + if ($req_current) { eval { - $self->domain->set_vcpus($n_virt_cpu, Sys::Virt::Domain::VCPU_GUEST); + $self->domain->set_vcpus($req_current, Sys::Virt::Domain::VCPU_GUEST) if $self->is_active; + }; if ($@) { warn $@; - $self->_data('needs_restart' => 1); + $self->_data('needs_restart' => 1) if $self->is_active; + } + my ($vcpus) = $doc->findnodes('/domain/vcpu'); + if (!defined $vcpus->getAttribute('current') + || $vcpus->getAttribute('current') != $req_current) { + $vcpus->setAttribute(current => $req_current); + $changed++; } } - my $doc = XML::LibXML->load_xml(string => $self->xml_description); my ($cpu) = $doc->findnodes('/domain/cpu'); my ($topology) = $cpu->findnodes('topology'); $cpu->removeChild($topology) if $topology; - my ($vcpus) = ($doc->findnodes('/domain/vcpu/text()')); - $vcpus->setData($n_virt_cpu); - $self->reload_config($doc); + if ($req_max) { + my ($vcpus_max) = ($doc->findnodes('/domain/vcpu/text()')); + if ( $vcpus_max ne $req_max ) { + $vcpus_max->setData($req_max); + $self->needs_restart(1) if $self->is_active; + $changed++; + } + } + + $self->reload_config($doc) if $changed; } @@ -3157,6 +3236,9 @@ sub _default_cpu($self) { } sub _fix_vcpu_from_topology($self, $data) { + + $data->{vcpu} = {} if !exists $data->{vcpu}; + if (!exists $data->{cpu}->{topology} || !defined($data->{cpu}->{topology})) { @@ -3182,6 +3264,7 @@ sub _fix_vcpu_from_topology($self, $data) { } sub _change_hardware_cpu($self, $index, $data) { + $data = $self->_default_cpu() if !keys %$data; @@ -3189,36 +3272,76 @@ sub _change_hardware_cpu($self, $index, $data) { if !$data->{cpu}->{'model'}->{'#text'}; delete $data->{cpu}->{model}->{'$$hashKey'}; - - my $doc = XML::LibXML->load_xml(string => $self->xml_description); + my @flags = (Sys::Virt::Domain::XML_INACTIVE); + my $doc = XML::LibXML->load_xml( string => $self->domain->get_xml_description( @flags )); my $count = 0; my $changed = 0; my ($n_vcpu) = $doc->findnodes('/domain/vcpu/text()'); + my ($cpu0) = $doc->findnodes('/domain/cpu'); $self->_fix_vcpu_from_topology($data); + lock_hash(%$data); + my ($data_n_cpus, $data_current_cpus); + $data_n_cpus = delete $data->{vcpu}->{'#text'} if exists $data->{vcpu}->{'#text'}; + + $data_current_cpus = delete $data->{vcpu}->{'current'} if exists $data->{vcpu}->{'current'}; + $data_n_cpus = $data_current_cpus if !defined $data_n_cpus && defined $data_current_cpus; + my ($vcpu) = $doc->findnodes('/domain/vcpu'); - if (exists $data->{vcpu} && $n_vcpu ne $data->{vcpu}->{'#text'}) { + if (defined $data_n_cpus && exists $data->{vcpu} && $n_vcpu ne $data_n_cpus) { $vcpu->removeChildNodes(); - $vcpu->appendText($data->{vcpu}->{'#text'}); + $vcpu->appendText($data_n_cpus); + $changed++; } + for my $key ( keys %{$data->{vcpu}} ) { + next if $vcpu->getAttribute($key) + && exists $data->{vcpu}->{$key} + && defined $data->{vcpu}->{$key} + && $vcpu->getAttribute($key) eq $data->{vcpu}->{$key}; + + $vcpu->setAttribute($key => $data->{vcpu}->{$key}); + $changed++ if $key ne 'current'; + } + for my $attrib ($vcpu->attributes) { + next if exists $data->{vcpu}->{$attrib->name}; + $vcpu->removeAttribute($attrib->name); + $changed++ if $attrib->name ne 'current'; + } + my ($domain) = $doc->findnodes('/domain'); my ($cpu) = $doc->findnodes('/domain/cpu'); + my $cpu_string = ''; + $cpu_string = $cpu->toString(); + $cpu_string = join("",split(/\n/,$cpu->toString)) if $cpu; if (!$cpu) { $cpu = $domain->addNewChild(undef,'cpu'); } - my $feature = delete $data->{cpu}->{feature}; + my $feature = $data->{cpu}->{feature}; - $changed += _change_xml($domain, 'cpu', $data->{cpu}); + _change_xml($domain, 'cpu', $data->{cpu}); if ( $feature ) { _change_xml_list($cpu, 'feature', $feature, 'name'); - $changed++; + } + $cpu_string =~ s/\s\s+/ /g; + my $cpu_string2 = join("",grep(/./,split(/\n/,$cpu->toString))); + $cpu_string2 =~ s/\s\s+/ /g; + + if ( $cpu_string ne $cpu_string2 || $changed ) { + $self->needs_restart(1) if $self->is_active; + $self->reload_config($doc); + } + if ($self->is_active && $data_current_cpus) { + eval { + $self->domain->set_vcpus($data_current_cpus + , Sys::Virt::Domain::VCPU_GUEST); + }; + warn $@ if $@; } - $self->reload_config($doc) if $changed; } @@ -3366,9 +3489,10 @@ sub _change_xml_list($xml,$name, $data, $field='name') { $node = $curr if $curr->getAttribute($field) eq $entry->{$field}; } $node = $xml->addNewChild(undef, $name) if !$node; - for my $field (keys %$entry) { - next if $field eq '$$hashKey'; - $node->setAttribute($field, $entry->{$field}); + $node->setAttribute($field, $entry->{$field}); + for my $field2 (keys %$entry) { + next if $field2 eq '$$hashKey' || $field2 eq $field; + $node->setAttribute($field2, $entry->{$field2}); } } @@ -3379,8 +3503,10 @@ sub _change_xml_list($xml,$name, $data, $field='name') { } sub _change_xml($xml, $name, $data) { + return 0 if ref($data) eq 'ARRAY'; + confess Dumper([$name, $data]) - if !ref($data) || ( ref($data) ne 'HASH' && ref($data) ne 'ARRAY'); + if !ref($data) || ( ref($data) ne 'HASH' ); my $changed = 0; @@ -3420,12 +3546,13 @@ sub _change_xml($xml, $name, $data) { && $node->getAttribute($field) eq $data->{$field}; $node->setAttribute($field, $data->{$field}); + $changed++; } } for my $child ( $node->childNodes() ) { my $name = $child->nodeName(); - if (!exists $data->{$name} || !defined $data->{$name} ) { + if ($name ne '#text' && (!exists $data->{$name} || !defined $data->{$name}) ) { $node->removeChild($child); $changed++; } @@ -3520,8 +3647,19 @@ sub _validate_xml($self, $doc) { sub reload_config($self, $doc) { $self->_validate_xml($doc) if $self->_vm->vm->get_major_version >= 4; - my $new_domain = $self->_vm->vm->define_domain($doc->toString); + + my $new_domain; + + eval { + $new_domain = $self->_vm->vm->define_domain($doc->toString); + }; + + cluck ''.$@ if $@; + $self->domain($new_domain); + + $self->_data_extra('xml', $doc->toString) if $self->is_known && $self->is_local; + } sub _save_xml_tmp($self,$doc) { diff --git a/lib/Ravada/Domain/Void.pm b/lib/Ravada/Domain/Void.pm index f29f4aee2..24ad2532e 100644 --- a/lib/Ravada/Domain/Void.pm +++ b/lib/Ravada/Domain/Void.pm @@ -1034,11 +1034,15 @@ sub _change_hardware_disk($self, $index, $data_new) { sub _change_hardware_vcpus($self, $index, $data) { my $n = delete $data->{n_virt_cpu}; + my $max = delete $data->{max_virt_cpu}; confess "Error: unknown args ".Dumper($data) if keys %$data; my $info = $self->_value('info'); - $info->{n_virt_cpu} = $n; + $info->{n_virt_cpu} = $n if defined $n; + $info->{max_virt_cpu} = $max if defined $max; $self->_store(info => $info); + + $self->needs_restart(1); } sub _change_hardware_memory($self, $index, $data) { diff --git a/lib/Ravada/Front/Domain/KVM.pm b/lib/Ravada/Front/Domain/KVM.pm index 5093ff533..ea290bace 100644 --- a/lib/Ravada/Front/Domain/KVM.pm +++ b/lib/Ravada/Front/Domain/KVM.pm @@ -176,7 +176,9 @@ sub _get_controller_generic($self,$type) { } sub _get_controller_cpu($self) { - my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml')); + my $xml = $self->_data_extra('xml'); + return if !$xml; + my $doc = XML::LibXML->load_xml(string => $xml); my $item = { _name => 'cpu' ,_order => 0 @@ -192,6 +194,15 @@ sub _get_controller_cpu($self) { my ($xml_vcpu) = $doc->findnodes("/domain/vcpu"); _xml_elements($xml_vcpu, $item->{vcpu}); + $item->{vcpu}->{current} = $item->{vcpu}->{'#text'} + if exists $item->{vcpu}->{'#text'} && ! defined $item->{vcpu}->{'current'}; + + if ($self->is_active) { + my $info = $self->get_info(); + $item->{vcpu}->{current} = $info->{n_virt_cpu} + if exists $info->{n_virt_cpu}; + + } if (exists $item->{cpu}->{feature} && ref($item->{cpu}->{feature}) ne 'ARRAY') { $item->{cpu}->{feature} = [ $item->{cpu}->{feature} ]; } diff --git a/lib/Ravada/Request.pm b/lib/Ravada/Request.pm index 890b63fc8..4b4ecef6b 100644 --- a/lib/Ravada/Request.pm +++ b/lib/Ravada/Request.pm @@ -74,6 +74,8 @@ our %VALID_ARG = ( ,reboot_domain => { name => 2, id_domain => 2, uid => 1, timeout => 2, at => 2 , id_vm => 2 } ,force_reboot_domain => { id_domain => 1, uid => 1, at => 2, id_vm => 2 } + ,shutdown_start =>{ name => 2, id_domain => 2, uid => 1, timeout => 2 + , at => 2 , id_vm => 2 } ,screenshot => { id_domain => 1 } ,domain_autostart => { id_domain => 1 , uid => 1, value => 2 } ,copy_screenshot => { id_domain => 1 } diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index 82c2b52a2..8e44ec4a0 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -410,6 +410,7 @@ sub _around_create_domain { my %args = @_; my $remote_ip = delete $args{remote_ip}; my $add_to_pool = delete $args{add_to_pool}; + my $hardware = delete $args{options}->{hardware}; my %args_create = %args; my $id_owner = delete $args{id_owner} or confess "ERROR: Missing id_owner"; @@ -496,6 +497,7 @@ sub _around_create_domain { $domain->_data('is_compacted' => 1); $domain->_data('alias' => $alias) if $alias; $domain->_data('date_status_change', Ravada::Utils::now()); + $self->_change_hardware_install($domain,$hardware) if $hardware; if ($id_base) { $domain->run_timeout($base->run_timeout) @@ -539,6 +541,19 @@ sub _around_create_domain { return $domain; } +sub _change_hardware_install($self, $domain, $hardware) { + + for my $item (sort keys %$hardware) { + Ravada::Request->change_hardware( + uid => Ravada::Utils::user_daemon->id + ,id_domain=> $domain->id + ,hardware => $item + ,data => $hardware->{$item} + ); + } + +} + sub _set_ascii_name($self, $name) { my $length = length($name); $name =~ tr/.âêîôûáéíóúàèìòùäëïöüçñ'/-aeiouaeiouaeiouaeioucn_/; @@ -931,7 +946,7 @@ 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'}; + delete @args{'_vm','name','vm', 'memory','description','id_iso','listen_ip','spice_password','from_pool', 'volatile', 'alias','storage', 'options'}; confess "ERROR: Unknown arguments ".join(",",keys %args) if keys %args; diff --git a/lib/Ravada/VM/KVM.pm b/lib/Ravada/VM/KVM.pm index def125b01..01a03033d 100644 --- a/lib/Ravada/VM/KVM.pm +++ b/lib/Ravada/VM/KVM.pm @@ -1078,6 +1078,8 @@ sub _domain_create_from_iso { if $spice_password; $domain->xml_description(); + $self->_change_hardware_install($domain,$iso->{options}->{hardware}); + return $domain; } @@ -1831,12 +1833,14 @@ sub _xml_modify_options($self, $doc, $options=undef) { my $machine = delete $options->{machine}; my $arch = delete $options->{arch}; my $bios = delete $options->{'bios'}; + confess "Error: bios=$bios and uefi=$uefi clash" if defined $uefi && defined $bios && ($bios !~/uefi/i && $uefi || $bios =~/uefi/i && !$uefi); $uefi = 1 if $bios && $bios =~ /uefi/i; + confess "Arguments unknown ".Dumper($options) if keys %$options; if ($machine) { $self->_xml_set_machine($doc, $machine); diff --git a/lib/Ravada/Volume.pm b/lib/Ravada/Volume.pm index 6a414cbe0..d0d450c3c 100644 --- a/lib/Ravada/Volume.pm +++ b/lib/Ravada/Volume.pm @@ -76,12 +76,13 @@ sub _type_from_file($file, $vm) { my ($out, $err) = $vm->run_command("file","-L",$file); return 'QCOW2' if $out =~ /QEMU QCOW/; + return 'Void' if $out =~ /ASCII text/; return 'RAW'; } sub _type_from_extension($file) { my ($ext) = $file =~ m{.*\.(.*)}; - confess if !defined $ext; + return if !defined $ext; confess if $ext =~ /-/; my %type = ( void => 'Void' @@ -95,7 +96,7 @@ sub _type_from_extension($file) { sub _type($file,$vm = undef) { return _type_from_file($file,$vm) if $vm; - return _type_from_extension($file); + return (_type_from_extension($file) or 'QCOW2'); } sub BUILD($self, $arg) { @@ -111,6 +112,10 @@ sub BUILD($self, $arg) { } elsif (exists $arg->{info}) { if (exists $arg->{info}->{device} && $arg->{info}->{device} eq 'cdrom') { $class = "Ravada::Volume::ISO"; + } elsif(exists $arg->{info}->{driver} && exists $arg->{info}->{driver}->{type}) { + my $name = 'unknown'; + $name = $arg->{info}->{name} if exists $arg->{info}->{name}; + $class = "Ravada::Volume::"._type_from_extension("$name.".$arg->{info}->{driver}->{type}); } else { confess "I can't guess class from ".Dumper($arg); } diff --git a/lib/Ravada/Volume/Void.pm b/lib/Ravada/Volume/Void.pm index 83cd1fb55..33a688ed0 100644 --- a/lib/Ravada/Volume/Void.pm +++ b/lib/Ravada/Volume/Void.pm @@ -33,7 +33,9 @@ sub prepare_base($self) { } sub capacity($self) { - my $info = $self->_load(); + my $info = {}; + eval { $info = $self->_load(); }; + warn $@ if $@; confess "Unknown capacity for ".$self->file.Dumper($info) if !exists $info->{capacity}; diff --git a/public/js/admin.js b/public/js/admin.js index 62d6a6a72..13181bdf7 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -215,9 +215,14 @@ ravadaApp.directive("solShowMachine", swMach) } } } - if ($scope.id_iso && $scope.id_iso.options - && $scope.id_iso.options['bios']) { - $scope.bios = $scope.id_iso.options['bios']; + if ($scope.id_iso && $scope.id_iso.options) { + if( $scope.id_iso.options['bios']) { + $scope.bios = $scope.id_iso.options['bios']; + } + if( $scope.id_iso.options['hardware']) { + $scope.hardware = $scope.id_iso.options['hardware']; + } + } }; diff --git a/public/js/ravada.js b/public/js/ravada.js index 7133a2bb1..b37a25c85 100644 --- a/public/js/ravada.js +++ b/public/js/ravada.js @@ -363,6 +363,7 @@ * item.cores * item.threads; + cpu.vcpu['current']=undefined; $scope.topology = true; }; @@ -393,7 +394,9 @@ } $scope.$apply(function () { if ($scope.lock_info) { - $scope.showmachine.requests = data.requests; + if(data.requests) { + $scope.showmachine.requests = data.requests; + } return; } $scope.hardware = Object.keys(data.hardware); @@ -420,13 +423,14 @@ if ($scope.showmachine.is_base) { $scope.copy_is_volatile = $scope.showmachine.volatile_clones; } + $scope.topology_changed(); if (!subscribed_extra) { subscribed_extra = true; subscribe_nodes(url,data.type); //subscribe_bases(url); } if ($scope.edit) { $scope.lock_info = true } - $scope.topology_changed(); + update_info_settings(); }); _select_new_base(); } @@ -545,6 +549,14 @@ }; var url_ws; + + var update_info_settings = function() { + $scope.new_n_virt_cpu= 0+$scope.showmachine.n_virt_cpu; + $scope.new_max_virt_cpu= 0+$scope.showmachine.max_virt_cpu; + $scope.new_memory = ($scope.showmachine.memory / 1024); + $scope.new_max_mem = ($scope.showmachine.max_mem / 1024); + }; + $scope.init = function(id, url,is_admin) { url_ws = url; $scope.showmachineId=id; @@ -561,10 +573,7 @@ if (typeof $scope.new_name == 'undefined' ) { $scope.new_name=$scope.showmachine.alias+"-2"; $scope.validate_new_name($scope.showmachine.alias); - $scope.new_n_virt_cpu= $scope.showmachine.n_virt_cpu; - $scope.new_memory = ($scope.showmachine.memory / 1024); - $scope.new_max_mem = ($scope.showmachine.max_mem / 1024); - + update_info_settings(); $scope.new_run_timeout = ($scope.showmachine.run_timeout / 60); if (!$scope.new_run_timeout) $scope.new_run_timeout = undefined; @@ -844,6 +853,9 @@ } }; $scope.check_access = function() { + if (!$scope.user_name) { + return; + } $http.get('/machine/check_access/'+$scope.showmachine.id+"/"+$scope.user_name).then(function(response) { $scope.check_allowed=response.data.ok; }); @@ -1060,7 +1072,6 @@ var _host_device_in_machine = function(id_hd) { for ( var i=0;i<$scope.showmachine.host_devices.length; i++ ) { var hd = $scope.showmachine.host_devices[i]; - console.log(hd.id+ " "+id_hd); if (hd.id_host_device == id_hd) { return true; } @@ -1099,10 +1110,19 @@ }); }); }; - + $scope.req_change_current = function(n_cpu) { + if (!$scope.topology && $scope.showmachine.is_active) { + request('change_hardware',{ + 'id_domain': $scope.showmachine.id + ,'hardware': 'vcpus' + ,'data': { 'n_virt_cpu': n_cpu} + }) + } + }; $scope.request = function(request, args) { $scope.showmachine.requests++; $scope.pending_request = undefined; + $scope.lock_info = false; $http.post('/request/'+request+'/' ,JSON.stringify(args) ).then(function(response) { @@ -1151,8 +1171,11 @@ }); }; - $scope.reboot = function() { - $http.get("/machine/stop_start/"+$scope.showmachine.id+".json") + $scope.shutdown_start = function() { + $scope.set_edit(); + $scope.lock_info=false; + $scope.showmachine.needs_restart = 0; + $http.get("/machine/shutdown_start/"+$scope.showmachine.id+".json") .then(function(response) { }); }; @@ -1235,10 +1258,8 @@ try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; - console.log('Copying text command was ' + msg); $scope.password_clipboard=successful; } catch (err) { - console.log('Oops, unable to copy'); } } diff --git a/script/rvd_front b/script/rvd_front index 8a6df0d5c..998ea11b0 100644 --- a/script/rvd_front +++ b/script/rvd_front @@ -897,9 +897,9 @@ get '/machine/reboot/(:id).(:type)' => sub { return reboot_machine($c); }; -get '/machine/stop_start/(:id).(:type)' => sub($c) { +get '/machine/shutdown_start/(:id).(:type)' => sub($c) { return access_denied($c) if !$USER ->can_reboot($c->stash('id')); - return stop_start($c); + return shutdown_start($c); }; @@ -2836,6 +2836,7 @@ sub req_new_domain { my $vm = ( $c->param('backend') or 'KVM'); $swap = int($swap * 1024*1024*1024); my $bios = $c->param('bios'); + my $hardware = $c->param('hardware'); my $machine = ($c->param('machine') or ''); $machine =~ s/^string://; $machine = '' if $machine eq '?'; @@ -3375,20 +3376,15 @@ sub reboot_machine { return $c->render(json => { req => $req->id }); } -sub stop_start($c) { +sub shutdown_start($c) { return login($c) if !_logged_in($c); my ($domain, $type) = _search_requested_machine($c); - my $req = Ravada::Request->shutdown_domain(name => $domain->name + my $req = Ravada::Request->shutdown_start(id_domain => $domain->id ,uid => $USER->id ); - my $req2 = Ravada::Request->start_domain(id_domain => $domain->id - ,uid => $USER->id - ,after_request => $req->id - ); - - return $c->render(json => { req => $req2->id }); + return $c->render(json => { req => $req->id }); } sub force_shutdown_machine { diff --git a/t/20_volumes.t b/t/20_volumes.t index 779d671ca..76daaed9d 100644 --- a/t/20_volumes.t +++ b/t/20_volumes.t @@ -363,11 +363,83 @@ sub test_defaults($vm, $volume_type=undef) { $domain->remove(user_admin); } +sub remove_extension($domain, $vol) { + if ($domain->type eq 'KVM') { + _remove_extension_kvm($domain,$vol); + } elsif ($domain->type eq 'Void') { + _remove_extension_void($domain,$vol); + } else { + die "I don't know how to rename ".$domain->type; + } + +} + +sub _remove_extension_void($domain, $vol) { + my $yml = $domain->_load(); + my $hw = $yml->{hardware}; + for my $dev ( @{$yml->{hardware}->{device}}) { + if ($dev->{file} eq $vol) { + $dev->{file} =~ s/(.*)\.\w+/$1/; + $dev->{file} =~ s/\./_/g; + $dev->{name} =~ s/(.*)\.\w+/$1/; + $domain->_store(hardware => $yml->{hardware}); + rename($vol, $dev->{file}) + or die "$! $vol -> $dev->{file}"; + } + } +} + +sub _remove_extension_kvm($domain, $vol) { + my $doc = XML::LibXML->load_xml(string => $domain->domain->get_xml_description); + for my $disk ($doc->findnodes('/domain/devices/disk')) { + my ($source_node) = $disk->findnodes('source'); + my $file; + if ( $source_node ) { + my $file = $source_node->getAttribute('file'); + if ($file && $file eq $vol) { + $file =~ s/(.*)\.\w+/$1/; + $file =~ s/\./_/g; + + $source_node->setAttribute('file' => $file); + rename($vol, $file) + or die "$! $vol -> $file"; + } + } + } + $domain->reload_config($doc); +} + +sub test_no_extension($vm) { + my $base = create_domain($vm); + $base->add_volume(type => 'swap', size => 1024*1024); + $base->add_volume(type => 'data', size => 1024*1024); + + my @vols = $base->list_volumes(); + + for my $vol ( @vols ) { + next if $vol =~ /\.iso$/; + remove_extension($base, $vol); + } + my $sth = connector->dbh->prepare( + "DELETE FROM volumes WHERE id_domain=?" + ); + $sth->execute($base->id); + + my $info = $base->info(user_admin); + my @vols2 = $base->list_volumes_info(); + + for my $vol (@vols2) { + my $ref = ref($vol); + unlike($ref,qr/HASH|RAW$/); + } +} + sub test_qcow_format($vm) { return if $vm->type ne 'KVM'; my $base = create_domain($vm); $base->add_volume(type => 'swap', size => 1024*1024); $base->add_volume(type => 'data', size => 1024*1024); + wait_request(); my $clone = $base->clone( name => new_domain_name @@ -440,6 +512,8 @@ for my $vm_name (reverse vm_names() ) { diag("Testing volumes in $vm_name"); init_vm($vm); + test_no_extension($vm); + test_qcow_format($vm); test_raw($vm); diff --git a/t/kvm/40_import.t b/t/kvm/40_import.t index 36ec2d4de..37a1910fe 100644 --- a/t/kvm/40_import.t +++ b/t/kvm/40_import.t @@ -2,6 +2,7 @@ use warnings; use strict; use Data::Dumper; +use IPC::Run3 qw(run3); use Test::More; use lib 't/lib'; @@ -151,6 +152,165 @@ sub test_import_spinoff { } +sub _create_vol($vm, $name) { + my $sp = $vm->vm->get_storage_pool_by_name('default'); + + my $old_vol = $sp->get_volume_by_name($name); + $old_vol->delete() if $old_vol; + + my $xml = < + $name + /var/lib/libvirt/images/$name + 21474836480 + 3485696 + 21478375424 + + /var/lib/libvirt/images/$name + + + 0644 + 0 + 0 + + + 1538407038.168298505 + 1538406915.308849295 + 1538407050.036621775 + + 1.1 + + + + + +EOT + + $sp->create_volume($xml); +} + +sub test_volume($vm) { + + return if $vm->type ne 'KVM'; + my $dom_name = new_domain_name(); + my $vol_name = new_domain_name(); + _create_vol($vm, $vol_name); + $vm->refresh_storage_pools(); + return if $vm->type ne 'KVM'; +my $xml =< + $dom_name + 6f6c9b78-3ce4-4a4e-a025-b1c7ae1965e0 + + + + + + 1273856 + 1273856 + 1 + + hvm + + + + + + + + qemu64 + + + destroy + restart + restart + + /usr/bin/qemu-system-x86_64 + + + + +
+ + +
+ + +
+ + + + + +
+ + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + + + + + + + +
+ + + + + + +