Commit 2912f395 authored by Francesc Guasch's avatar Francesc Guasch
Browse files

Merge branch 'main' into refactor/messages

parents 5e7b0dbc f6423c1a
......@@ -61,4 +61,4 @@ jobs:
- name: Test mock VM
run: prove -lr t/vm/60_new_args.t t/30_request.t
- name: Test create from ISO
run: prove -lr t/request/25_create_from_iso.t
run: prove -lr t/request/25_create_from_iso.t t/vm/d10_not_download.t
......@@ -2,5 +2,14 @@
**Implemented enhancements:**
- Define min swap space for machine templates [\#1697]
- Filter virtual machine by name [\#1696]
- Download extra ISO file for installation [\#1695]
**Refactors**
- enforce ordered adding of hardware [\#1697]
**Bugfixes**
- hibernate button was always disabled
......@@ -304,7 +304,7 @@ sub list_dists {
die "Error: no dists control files found in 'debian' dir"
if !@dists;
return @dists;
return reverse @dists;
}
sub set_control_file {
......
......@@ -36,7 +36,7 @@ sub download($url, $dst = $DIR_FALLBACK) {
$dst .= $filename;
}
return $dst if -e $dst;
return if -e $dst;
print "get $url\n";
my $res = $ua->get($url)->result;
......@@ -89,6 +89,6 @@ while (<$in>) {
next if /^#/;
my ($url, $dst) = split;
my $file = download($url, $dst);
uncompress($file) if $file =~ /\.zip$/;
uncompress($file) if $file && $file =~ /\.zip$/;
}
close $in;
......@@ -17,7 +17,7 @@
,secrets => ['changeme1','changeme2']
,login_custom => ''
,footer => '/bootstrap/footer'
,login_bg_file => 'img/intro-bg.jpg'
,login_bg_file => '/img/intro-bg.jpg'
,login_message => 'Acme VDI service'
,monitoring => 0
,guide => ''
......
......@@ -67,6 +67,7 @@ our %VALID_CONFIG = (
,field => undef
,server => undef
,port => undef
,size_limit => undef
}
,log => undef
);
......@@ -217,7 +218,7 @@ sub _do_create_constraints($self) {
my ($name) = $constraint =~ /CONSTRAINT (\w+)\s/;
warn "INFO: creating constraint $name \n"
if !$FIRST_TIME_RUN && $0 !~ /\.t$/;
if $name && !$FIRST_TIME_RUN && $0 !~ /\.t$/;
print "+" if $FIRST_TIME_RUN && !$CAN_FORK;
$self->_clean_db_leftovers();
......@@ -320,7 +321,7 @@ sub _update_isos {
mate_bionic_i386 => {
name => 'Ubuntu Mate Bionic 32 bits'
,description => 'Ubuntu Mate 18.04 (Bionic Beaver) 32 bits'
,arch => 'i386'
,arch => 'i686'
,xml => 'bionic-i386.xml'
,xml_volume => 'bionic32-volume.xml'
,url => 'http://cdimage.ubuntu.com/ubuntu-mate/releases/18.04.*/release/ubuntu-mate-18.04.*-desktop-i386.iso'
......@@ -458,7 +459,7 @@ sub _update_isos {
,kubuntu_32 => {
name => 'Kubuntu Bionic Beaver 32 bits'
,description => 'Kubuntu 18.04 Bionic Beaver 32 bits'
,arch => 'i386'
,arch => 'i686'
,xml => 'bionic-i386.xml'
,xml_volume => 'bionic32-volume.xml'
,sha256_url => '$url/SHA256SUMS'
......@@ -491,7 +492,7 @@ sub _update_isos {
,xubuntu_beaver_32 => {
name => 'Xubuntu Bionic Beaver 32 bits'
,description => 'Xubuntu 18.04 Bionic Beaver 32 bits'
,arch => 'i386'
,arch => 'i686'
,xml => 'bionic-i386.xml'
,xml_volume => 'bionic32-volume.xml'
,md5_url => '$url/../MD5SUMS'
......@@ -520,7 +521,7 @@ sub _update_isos {
,lubuntu_bionic_32 => {
name => 'Lubuntu Bionic Beaver 32 bits'
,description => 'Lubuntu 18.04 Bionic Beaver 32 bits'
,arch => 'i386'
,arch => 'i686'
,url => 'http://cdimage.ubuntu.com/lubuntu/releases/18.04.*/release/lubuntu-18.04.*-desktop-i386.iso'
,sha256_url => '$url/SHA256SUMS'
,xml => 'bionic-i386.xml'
......@@ -545,6 +546,7 @@ sub _update_isos {
,xml => 'jessie-i386.xml'
,xml_volume => 'jessie-volume.xml'
,min_disk_size => '10'
,arch => 'i686'
}
,debian_jessie_64 => {
name =>'Debian Jessie 64 bits'
......@@ -566,6 +568,7 @@ sub _update_isos {
,xml => 'jessie-i386.xml'
,xml_volume => 'jessie-volume.xml'
,min_disk_size => '10'
,arch => 'i686'
}
,debian_stretch_64 => {
name =>'Debian Stretch 64 bits'
......@@ -598,6 +601,7 @@ sub _update_isos {
,xml => 'jessie-i386.xml'
,xml_volume => 'jessie-volume.xml'
,min_disk_size => '10'
,arch => 'i686'
}
,debian_bullseye_64=> {
name =>'Debian Bullseye 64 bits'
......@@ -637,7 +641,7 @@ sub _update_isos {
,devuan_beowulf_i386=> {
name =>'Devuan Beowulf 32 bits'
,description => 'Devuan Beowulf Desktop Live (i386)'
,arch => 'i386'
,arch => 'i686'
,url => 'http://tw1.mirror.blendbyte.net/devuan-cd/devuan_beowulf/desktop-live/'
,file_re => 'devuan_beowulf_.*_i386_desktop-live.iso'
,sha256_url => '$url/SHASUMS.txt'
......@@ -651,8 +655,8 @@ sub _update_isos {
,arch => 'x86_64'
,xml => 'jessie-amd64.xml'
,xml_volume => 'jessie-volume.xml'
,url => 'https://download.parrot.sh/parrot/iso/4.11.2/'
,file_re => 'Parrot-xfce-4.11.2_amd64.iso'
,url => 'https://edge1.parrot.run/parrot/iso/4.11.3/'
,file_re => 'Parrot-xfce-4.11.3_amd64.iso'
,sha256_url => '$url/signed-hashes.txt'
,min_disk_size => '10'
}
......@@ -662,30 +666,30 @@ sub _update_isos {
,arch => 'x86_64'
,xml => 'jessie-amd64.xml'
,xml_volume => 'jessie-volume.xml'
,url => 'https://download.parrot.sh/parrot/iso/4.11.2/'
,file_re => 'Parrot-security-4.11.2_amd64.iso'
,url => 'https://edge1.parrot.run/parrot/iso/4.11.3/'
,file_re => 'Parrot-security-4.11.3_amd64.iso'
,sha256_url => '$url/signed-hashes.txt'
,min_disk_size => '10'
}
,kali_64 => {
name => 'Kali Linux 2020'
,description => 'Kali Linux 2020 64 Bits'
name => 'Kali Linux 2021'
,description => 'Kali Linux 2021 64 Bits'
,arch => 'x86_64'
,xml => 'jessie-amd64.xml'
,xml_volume => 'jessie-volume.xml'
,url => 'https://cdimage.kali.org/kali-2020.\d+/'
,file_re => 'kali-linux-2020.\d+-installer-amd64.iso'
,url => 'https://cdimage.kali.org/kali-2021.\d+/'
,file_re => 'kali-linux-2021.\d+-installer-amd64.iso'
,sha256_url => '$url/SHA256SUMS'
,min_disk_size => '10'
}
,kali_64_netinst => {
name => 'Kali Linux 2020 (NetInstaller)'
,description => 'Kali Linux 2020 64 Bits (light NetInstall)'
name => 'Kali Linux 2021 (NetInstaller)'
,description => 'Kali Linux 2021 64 Bits (light NetInstall)'
,arch => 'x86_64'
,xml => 'jessie-amd64.xml'
,xml_volume => 'jessie-volume.xml'
,url => 'https://cdimage.kali.org/kali-2020.\d+/'
,file_re => 'kali-linux-2020.\d+-installer-netinst-amd64.iso'
,url => 'https://cdimage.kali.org/kali-2021.\d+/'
,file_re => 'kali-linux-2021.\d+-installer-netinst-amd64.iso'
,sha256_url => '$url/SHA256SUMS'
,min_disk_size => '10'
}
......@@ -705,7 +709,9 @@ sub _update_isos {
,xml => 'windows_10.xml'
,xml_volume => 'windows10-volume.xml'
,min_disk_size => '21'
,min_swap_size => '2'
,arch => 'x86_64'
,extra_iso => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.215-\d+/virtio-win-0.1.\d+.iso'
}
,windows_xp => {
name => 'Windows XP'
......@@ -814,7 +820,9 @@ sub _scheduled_fedora_releases($self,$data) {
my $url_file = $url.$release
.'/Workstation/x86_64/iso/Fedora-Workstation-.*-x86_64-'.$release
.'-.*\.iso';
my @found = $vm->_search_url_file($url_file);
my @found;
eval { @found = $vm->_search_url_file($url_file) };
die $@ if $@ && $@ !~ /Not Found/i;
if(!@found) {
next if $url =~ m{//archives};
......@@ -1385,7 +1393,7 @@ sub _add_indexes_generic($self) {
$self->_clean_index_conflicts($table, $name);
print "+" if $FIRST_TIME_RUN;
if ($table eq 'domain_displays' && $name eq 'id_vm_port') {
if ($table eq 'domain_displays' && $name =~ /port/) {
my $sth_clean=$CONNECTOR->dbh->prepare(
"UPDATE domain_displays set port=NULL"
);
......@@ -2044,6 +2052,12 @@ sub _create_constraints($self, $table, @constraints) {
my $name = "constraint_${table}_$field";
next if $known->{$name} && $known->{$name} eq $sql;
if ($known->{$name}) {
push @{$self->{_constraints}}
,"alter table $table DROP constraint $name";
}
$sql = "alter table $table add CONSTRAINT $name $sql";
# $CONNECTOR->dbh->do($sql);
push @{$self->{_constraints}},($sql);
......@@ -2291,9 +2305,11 @@ sub _upgrade_tables {
$self->_upgrade_table('iso_images','file_re','char(64)');
$self->_upgrade_table('iso_images','device','varchar(255)');
$self->_upgrade_table('iso_images','min_disk_size','int (11) DEFAULT NULL');
$self->_upgrade_table('iso_images','min_swap_size','int (11) DEFAULT NULL');
$self->_upgrade_table('iso_images','options','varchar(255)');
$self->_upgrade_table('iso_images','has_cd','int (1) DEFAULT "1"');
$self->_upgrade_table('iso_images','downloading','int (1) DEFAULT "0"');
$self->_upgrade_table('iso_images','extra_iso','varchar(255)');
$self->_upgrade_table('users','language','char(40) DEFAULT NULL');
if ( $self->_upgrade_table('users','is_external','int(11) DEFAULT 0')) {
......@@ -2721,7 +2737,7 @@ sub create_domain {
}
my $vm_name = delete $args{vm};
my $start = $args{start};
my $start = delete $args{start};
my $id_base = $args{id_base};
my $data = delete $args{data};
my $id_owner = $args{id_owner} or confess "Error: missing id_owner ".Dumper(\%args);
......@@ -2745,6 +2761,10 @@ sub create_domain {
$request->status("creating machine") if $request;
unlock_hash(%args);
my $swap = delete $args{swap};
lock_hash(%args);
my $domain;
eval { $domain = $vm->create_domain(%args)};
......@@ -2754,32 +2774,124 @@ sub create_domain {
if ($error =~ /has \d+ requests/) {
$request->status('retry');
}
$request->id_domain($domain->id) if $domain;
} elsif ($error) {
die $error;
}
if (!$error && $start) {
$request->status("starting") if $request;
eval {
my $remote_ip;
$remote_ip = $request->defined_arg('remote_ip') if $request;
$domain->start(
user => $user
,remote_ip => $remote_ip
)
};
my $error = $@;
die $error if $error && !$request;
$request->error($error) if $error;
return if !$domain;
my $req_add_swap = _req_add_disk($args{id_owner}, $domain->id,
,'swap', $swap ,$request);
my $req_add_data = _req_add_disk($args{id_owner}, $domain->id
,'data', $data, ($req_add_swap or $request ));
my $previous_req = ($req_add_data or $req_add_swap or $request);
my $req_add_iso = _add_extra_iso($domain, $request,$previous_req);
if ( $start ) {
$previous_req = ($req_add_iso or $req_add_data or $req_add_swap
or $request);
_start_domain_after_create($domain, $request, $id_owner, $previous_req)
}
Ravada::Request->add_hardware(
uid => $args{id_owner}
,id_domain => $domain->id
,name => 'disk'
,data => { size => $data, type => 'data' }
) if $domain && $data;
return $domain;
}
sub _req_add_disk($uid, $id_domain, $type, $size, $request) {
return if !$size;
my @after_req;
@after_req = (after_request => $request->id ) if $request;
return Ravada::Request->add_hardware(
uid => $uid
,id_domain => $id_domain
,name => 'disk'
,data => { size => $size, type => $type }
,@after_req
);
}
sub _start_domain_after_create($domain, $request, $uid,$previous_request) {
my $remote_ip;
$remote_ip = $request->defined_arg('remote_ip') if $request;
my @after_req;
@after_req = (after_request => $previous_request->id );
my $req_refresh = Ravada::Request->refresh_machine(
uid => $uid
,id_domain => $domain->id
,@after_req
);
@after_req = (after_request => $req_refresh->id )
if $req_refresh;
my $req = Ravada::Request->start_domain(
uid => $uid
,id_domain => $domain->id
,remote_ip => $remote_ip
,at => time + 3
,@after_req
);
}
sub _search_iso($id) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT * FROM iso_images"
." WHERE id = ? "
);
$sth->execute($id);
my $row = $sth->fetchrow_hashref;
return $row;
}
sub _add_extra_iso($domain, $request, $previous_request) {
return if !$request;
my $id_iso = $request->defined_arg('id_iso');
return if !$id_iso;
my $iso = _search_iso($id_iso);
my $extra_iso = $iso->{extra_iso};
return if !$extra_iso;
$previous_request = $request if !$previous_request;
my ($url, $file_re) = $extra_iso =~ m{(.*/)(.*)};
my $volume = $domain->_vm->search_volume_path_re(qr($file_re));
my $download = 0;
if (!$volume) {
my ($url_match) = $domain->_vm->_search_url_file($extra_iso);
my ($device) = $url_match =~ m{.*/(.*)};
die "Error: file not found in $extra_iso\n"
if !$device;
$volume = $domain->_vm->dir_img()."/$device";
$download = 1 if $device;
}
my @after_request;
@after_request = ( after_request => $previous_request->id )
if $previous_request;
my $req = Ravada::Request->refresh_storage(id_vm => $domain->_vm->id
,@after_request);
@after_request = ( after_request => $req->id ) if $req;
my $req_add = Ravada::Request->add_hardware(
name => 'disk'
,uid => Ravada::Utils::user_daemon->id
,id_domain => $domain->id
,data => {
file => $volume
,device => 'cdrom'
}
,@after_request
,at => time+5
);
$domain->_vm->_download_file_external($extra_iso, $volume)
if $download;
return $req_add;
}
sub _check_args($args,@) {
my %args_check = %$args;
for my $field (@_) {
......@@ -2830,7 +2942,7 @@ sub remove_domain {
eval { $domain = Ravada::Domain->open(id => $id, _force => 1, id_vm => $vm->id) };
warn $@ if $@;
if (!$domain) {
warn "Warning: I can't find domain '$id', maybe already removed.";
warn "Warning: I can't find domain '$id', maybe already removed.\n";
Ravada::Domain::_remove_domain_data_db($id);
return;
};
......@@ -3811,8 +3923,8 @@ sub _cmd_create{
my $msg = '';
if ($domain) {
$msg = 'Domain '
."<a href=\"/machine/view/".$domain->id.".html\">"
$msg = 'Machine'
." <a href=\"/machine/manage/".$domain->id.".html\">"
.$request->args('name')."</a>"
." created."
;
......@@ -4092,8 +4204,8 @@ sub _cmd_start {
uid => Ravada::Utils::user_daemon->id
) if $domain->is_pool && $request->defined_arg('remote_ip');
my $msg = 'Domain '
."<a href=\"/machine/view/".$domain->id.".html\">"
my $msg = 'Machine'
." <a href=\"/machine/view/".$domain->id.".html\">"
.$domain->name."</a>"
." started"
;
......
......@@ -144,7 +144,7 @@ sub unshown_messages {
my $count = shift;
$count = 50 if !defined $count;
my $sth = $$CONNECTOR->dbh->prepare("SELECT id, subject, message FROM messages "
my $sth = $$CONNECTOR->dbh->prepare("SELECT id, subject, message, id_request FROM messages "
." WHERE id_user=? AND date_shown IS NULL"
." ORDER BY date_send DESC "
." LIMIT ?,?");
......
......@@ -3655,7 +3655,8 @@ sub list_ports($self) {
while (my $data = $sth->fetchrow_hashref) {
lock_hash(%$data);
push @list,($data);
$clone_port{$data->{internal_port}}++;
$clone_port{$data->{internal_port}}++
if $data->{internal_port};
}
if ($self->is_known() && !$self->is_base && $self->id_base) {
......
......@@ -1198,7 +1198,7 @@ sub add_volume {
if ( !defined $bus ) {
if ($device eq 'cdrom') {
$bus = 'ide';
$bus = 'sata' if $machine_type =~ /^pc-q35/;
$bus = 'sata' if $machine_type =~ /^pc-(i440fx|q35)/;
} else {
$bus = 'virtio'
}
......@@ -1214,7 +1214,6 @@ sub add_volume {
eval { $self->domain->attach_device($xml_device,Sys::Virt::Domain::DEVICE_MODIFY_CONFIG) };
die $@ if $@;
$self->_set_boot_order($path, $boot) if $boot;
return $path;
}
......@@ -1422,7 +1421,7 @@ sub _new_pci_slot{
}
}
}
for my $dec ( 1 .. 99) {
for my $dec ( 2 .. 99) {
next if $target{$dec};
return sprintf("0x%X", $dec);
}
......@@ -2765,6 +2764,7 @@ sub _change_xml_address_usb($self, $address) {
}
sub _change_xml_address_ide($self, $doc, $address, $max_bus=2, $max_unit=9) {
return if $address->getAttribute('type') eq 'drive'
&& $address->getAttribute('bus') =~ /^\d+$/
&& $address->getAttribute('bus') <= $max_bus
......
......@@ -528,6 +528,7 @@ sub add_volume {
}
sub _create_volume($self, $file, $format, $data=undef) {
confess "Undefined format" if !defined $format;
if ($format =~ /iso|raw|void/) {
$data->{format} = $format;
$self->_vm->write_file($file, Dump($data)),
......
......@@ -83,7 +83,7 @@ our %VALID_ARG = (
,set_driver => {uid => 1, id_domain => 1, id_option => 1}
,hybernate=> {uid => 1, id_domain => 1}
,download => {uid => 2, id_iso => 1, id_vm => 2, vm => 2, verbose => 2, delay => 2, test => 2}
,refresh_storage => { id_vm => 2 }
,refresh_storage => { id_vm => 2, uid => 2 }
,list_storage_pools => { id_vm => 1 , uid => 1 }
,check_storage => { uid => 1 }
,set_base_vm=> {uid => 1, id_vm=> 1, id_domain => 1, value => 2 }
......@@ -152,6 +152,7 @@ our %CMD_SEND_MESSAGE = map { $_ => 1 }
rebase rebase_volumes
shutdown_node reboot_node start_node
compact purge
start_domain
);
our %CMD_NO_DUPLICATE = map { $_ => 1 }
......@@ -217,6 +218,11 @@ our %CMD_VALIDATE = (
clone => \&_validate_clone
,create => \&_validate_create_domain
,create_domain => \&_validate_create_domain
,start_domain => \&_validate_start_domain
,start => \&_validate_start_domain
,add_hardware=> \&_validate_change_hardware
,change_hardware=> \&_validate_change_hardware
,remove_hardware=> \&_validate_change_hardware
);
sub _init_connector {
......@@ -747,6 +753,39 @@ sub _validate($self) {
$method->($self);
}
sub _validate_start_domain($self) {
my $id_domain = $self->defined_arg('id_domain');
if (!$id_domain) {
my $domain_name = $self->defined_arg('name');
$id_domain = _search_domain_id(undef,$domain_name);
}
return if !$id_domain;
for my $command ('start','%_hardware') {
my $req=$self->_search_request($command, id_domain => $id_domain);
next if !$req;
next if $req->at_time;
next if $command eq 'start' && !$req->after_request();
$self->after_request($req->id);
}
}
sub _validate_change_hardware($self) {
return if $self->after_request();
my $id_domain = $self->defined_arg('id_domain');
if (!$id_domain) {
my $domain_name = $self->defined_arg('name');
$id_domain = _search_domain_id(undef,$domain_name);
}
return if !$id_domain;
my $req = $self->_search_request('%_hardware', id_domain => $id_domain);
$self->after_request($req->id) if $req;
}
sub _validate_create_domain($self) {
my $base;
......@@ -771,18 +810,28 @@ sub _check_downloading($self) {
my $id_iso = $self->defined_arg('id_iso');
my $iso_file = $self->defined_arg('iso_file');
$iso_file = '' if $iso_file && $iso_file eq '<NONE>';
return if !$id_iso && !$iso_file;
my $sth = $$CONNECTOR->dbh->prepare(
"SELECT id,downloading,device,has_cd "
"SELECT id,downloading,device,has_cd,name,url "
." FROM iso_images "
." WHERE (id=? or device=?) "
);
$sth->execute($id_iso,$iso_file);
my ($id_iso2,$downloading, $device, $has_cd) = $sth->fetchrow;
my ($id_iso2,$downloading, $device, $has_cd, $iso_name, $iso_url)
= $sth->fetchrow;
return if !$downloading && $device;
my $req_download = $self->_search_request('download', id_iso => $id_iso2);
my $req_download = _search_request('download', id_iso => $id_iso2);
if ($has_cd && !$device && !$req_download) {
return $self->_status_error("done"
,"Error: ISO file required for $iso_name")
if $has_cd && !$device && !$iso_file && !$iso_url && !$device;
if ($has_cd && !$device && !$iso_file && !$req_download) {
$req_download = Ravada::Request->download(
id_iso => $id_iso2
,uid => Ravada::Utils::user_daemon->id
......@@ -819,13 +868,16 @@ sub _mark_iso_downloaded($id_iso) {
$sth->execute($id_iso);
}
sub _search_request($command,%fields) {
my $sth= $$CONNECTOR->dbh->prepare(
"SELECT id, args FROM requests WHERE command = ?"
." AND status <> 'done' "