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

Merge branch 'develop' into feature/nested_bases

parents a0b47edf ff2cb6c6
......@@ -216,8 +216,8 @@ sub _update_isos {
,arch => 'amd64'
,xml => 'focal_fossa-amd64.xml'
,xml_volume => 'focal_fossa64-volume.xml'
,url => 'http://cdimage.ubuntu.com/ubuntu-mate/releases/20.04/release/ubuntu-mate-20.04-desktop-amd64.iso'
,md5_url => '$url/MD5SUMS'
,url => 'http://cdimage.ubuntu.com/ubuntu-mate/releases/20.04.*/release/ubuntu-mate-20.04.*-desktop-amd64.iso'
,sha256_url => '$url/SHA256SUMS'
},
mate_bionic => {
name => 'Ubuntu Mate Bionic 64 bits'
......@@ -226,7 +226,7 @@ sub _update_isos {
,xml => 'bionic-amd64.xml'
,xml_volume => 'bionic64-volume.xml'
,url => 'http://cdimage.ubuntu.com/ubuntu-mate/releases/18.04.*/release/ubuntu-mate-18.04.*-desktop-amd64.iso'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
},
mate_bionic_i386 => {
name => 'Ubuntu Mate Bionic 32 bits'
......@@ -235,7 +235,7 @@ sub _update_isos {
,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'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
},
mate_xenial => {
name => 'Ubuntu Mate Xenial'
......@@ -244,7 +244,7 @@ sub _update_isos {
,xml => 'yakkety64-amd64.xml'
,xml_volume => 'yakkety64-volume.xml'
,url => 'http://cdimage.ubuntu.com/ubuntu-mate/releases/16.04.*/release/ubuntu-mate-16.04.*-desktop-amd64.iso'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
,min_disk_size => '10'
},
,focal_fossa=> {
......@@ -253,8 +253,8 @@ sub _update_isos {
,arch => 'amd64'
,xml => 'focal_fossa-amd64.xml'
,xml_volume => 'focal_fossa64-volume.xml'
,url => 'http://releases.ubuntu.com/20.04/'
,file_re => '^ubuntu-20.04.*desktop-amd64.iso'
,url => 'http://releases.ubuntu.com/20.04'
,file_re => '^ubuntu-20.04.1-desktop-amd64.iso'
,sha256_url => '$url/SHA256SUMS'
,min_disk_size => '9'
}
......@@ -333,9 +333,9 @@ sub _update_isos {
,arch => 'amd64'
,xml => 'focal_fossa-amd64.xml'
,xml_volume => 'focal_fossa64-volume.xml'
,md5_url => '$url/MD5SUMS'
,url => 'http://cdimage.ubuntu.com/kubuntu/releases/20.04/release/'
,file_re => 'kubuntu-20.04-desktop-amd64.iso'
,sha256_url => '$url/SHA256SUMS'
,url => 'http://cdimage.ubuntu.com/kubuntu/releases/20.04.*/release/'
,file_re => 'kubuntu-20.04.*-desktop-amd64.iso'
,rename_file => 'kubuntu_focal_fossa_64.iso'
}
,kubuntu_64 => {
......@@ -344,9 +344,9 @@ sub _update_isos {
,arch => 'amd64'
,xml => 'bionic-amd64.xml'
,xml_volume => 'bionic64-volume.xml'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
,url => 'http://cdimage.ubuntu.com/kubuntu/releases/18.04/release/'
,file_re => 'kubuntu-18.04-desktop-amd64.iso'
,file_re => 'kubuntu-18.04.\d+-desktop-amd64.iso'
,rename_file => 'kubuntu_bionic_64.iso'
}
,kubuntu_32 => {
......@@ -355,9 +355,9 @@ sub _update_isos {
,arch => 'i386'
,xml => 'bionic-i386.xml'
,xml_volume => 'bionic32-volume.xml'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
,url => 'http://cdimage.ubuntu.com/kubuntu/releases/18.04/release/'
,file_re => 'kubuntu-18.04-desktop-i386.iso'
,file_re => 'kubuntu-18.04.\d+-desktop-i386.iso'
,rename_file => 'kubuntu_bionic_32.iso'
}
,suse_15 => {
......@@ -377,7 +377,7 @@ sub _update_isos {
,arch => 'amd64'
,xml => 'bionic-amd64.xml'
,xml_volume => 'bionic64-volume.xml'
,md5_url => '$url/../MD5SUMS'
,sha256_url => '$url/../SHA256SUMS'
,url => 'http://archive.ubuntu.com/ubuntu/dists/bionic/main/installer-amd64/current/images/netboot/'
,file_re => 'mini.iso'
,rename_file => 'xubuntu_bionic_64.iso'
......@@ -407,7 +407,7 @@ sub _update_isos {
name => 'Lubuntu Bionic Beaver 64 bits'
,description => 'Lubuntu 18.04 Bionic Beaver 64 bits'
,url => 'http://cdimage.ubuntu.com/lubuntu/releases/18.04.*/release/lubuntu-18.04.*-desktop-amd64.iso'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
,xml => 'bionic-amd64.xml'
,xml_volume => 'bionic64-volume.xml'
}
......@@ -416,7 +416,7 @@ sub _update_isos {
,description => 'Lubuntu 18.04 Bionic Beaver 32 bits'
,arch => 'i386'
,url => 'http://cdimage.ubuntu.com/lubuntu/releases/18.04.*/release/lubuntu-18.04.*-desktop-i386.iso'
,md5_url => '$url/MD5SUMS'
,sha256_url => '$url/SHA256SUMS'
,xml => 'bionic-i386.xml'
,xml_volume => 'bionic32-volume.xml'
}
......@@ -1487,6 +1487,7 @@ sub _upgrade_tables {
my $self = shift;
# return if $CONNECTOR->dbh->{Driver}{Name} !~ /mysql/i;
$self->_upgrade_table("base_xml",'xml','TEXT');
$self->_upgrade_table('file_base_images','target','varchar(64) DEFAULT NULL');
$self->_upgrade_table('vms','vm_type',"char(20) NOT NULL DEFAULT 'KVM'");
......@@ -1573,9 +1574,12 @@ sub _upgrade_tables {
$self->_upgrade_table('domains','shared_storage','varchar(254)');
$self->_upgrade_table('domains','post_shutdown','int not null default 0');
$self->_upgrade_table('domains','post_hibernated','int not null default 0');
$self->_upgrade_table('domains','is_compacted','int not null default 0');
$self->_upgrade_table('domains','has_backups','int not null default 0');
$self->_upgrade_table('domains_network','allowed','int not null default 1');
$self->_upgrade_table('domains_kvm','xml','TEXT');
$self->_upgrade_table('iptables','id_vm','int DEFAULT NULL');
$self->_upgrade_table('vms','security','varchar(255) default NULL');
$self->_upgrade_table('grant_types','enabled','int not null default 1');
......@@ -3117,8 +3121,9 @@ sub _cmd_open_iptables {
sub _cmd_clone($self, $request) {
return _req_clone_many($self, $request) if $request->defined_arg('number')
&& $request->defined_arg('number') > 1;
return _req_clone_many($self, $request)
if ( $request->defined_arg('number') && $request->defined_arg('number') > 1)
|| (! $request->defined_arg('name') && $request->defined_arg('add_to_pool'));
my $domain = Ravada::Domain->open($request->args('id_domain'))
or confess "Error: Domain ".$request->args('id_domain')." not found";
......@@ -3781,6 +3786,42 @@ sub _cmd_set_time($self, $request) {
die "$@ , retry.\n" if $@;
}
sub _cmd_compact($self, $request) {
my $id_domain = $request->args('id_domain');
my $domain = Ravada::Domain->open($id_domain)
or do {
$request->retry(0);
Ravada::Request->refresh_vms();
die "Error: domain $id_domain not found\n";
};
my $uid = $request->args('uid');
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: user ".$user->name." not allowed to compact ".$domain->name
unless $user->is_operator || $uid == $domain->_data('id_owner');
$domain->compact($request);
}
sub _cmd_purge($self, $request) {
my $id_domain = $request->args('id_domain');
my $domain = Ravada::Domain->open($id_domain)
or do {
$request->retry(0);
Ravada::Request->refresh_vms();
die "Error: domain $id_domain not found\n";
};
my $uid = $request->args('uid');
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: user ".$user->name." not allowed to compact ".$domain->name
unless $user->is_operator || $uid == $domain->_data('id_owner');
$domain->purge($request);
}
sub _migrate_base($self, $domain, $id_node, $uid, $request) {
if (ref($id_node)) {
$id_node = $id_node->id;
......@@ -4168,6 +4209,8 @@ sub _req_method {
,remove_hardware => \&_cmd_remove_hardware
,change_hardware => \&_cmd_change_hardware
,set_time => \&_cmd_set_time
,compact => \&_cmd_compact
,purge => \&_cmd_purge
# Domain ports
,expose => \&_cmd_expose
......@@ -4515,3 +4558,4 @@ Sys::Virt
=cut
1;
......@@ -1582,7 +1582,7 @@ sub info($self, $user) {
,volatile_clones => $self->volatile_clones
,id_vm => $self->_data('id_vm')
};
for (qw(comment screenshot id_owner shutdown_disconnected)) {
for (qw(comment screenshot id_owner shutdown_disconnected is_compacted has_backups)) {
$info->{$_} = $self->_data($_);
}
if ($is_active) {
......@@ -3165,12 +3165,14 @@ sub _post_start {
my $set_time = delete $arg{set_time};
$set_time = 1 if !defined $set_time;
$self->_data('status','active') if $self->is_active();
if ( $self->is_active() ) {
$self->_data('status','active');
}
my $sth = $$CONNECTOR->dbh->prepare(
"UPDATE domains set start_time=? "
"UPDATE domains set start_time=?,is_compacted=? "
." WHERE id=?"
);
$sth->execute(time, $self->id);
$sth->execute(time, 0, $self->id);
$sth->finish;
$self->_data('internal_id',$self->internal_id);
......@@ -5369,4 +5371,52 @@ sub _domain_in_nodes($self) {
return $self->list_instances > 1;
}
sub compact($self, $request=undef) {
#first check if active, that will trigger status refresh
die "Error: ".$self->name." can't be compacted because it is active\n"
if $self->is_active;
# now check the status, it may be hibernated or in some other
my $status = $self->_data('status');
die "Error: ".$self->name." can't be compacted because it is $status\n"
unless $status eq 'shutdown';
my $keep_backup = 1;
$keep_backup = $request->defined_arg('keep_backup') if $request;
$keep_backup = 1 if !defined $keep_backup;
my $backed_up = '';
$backed_up = " [backed up]" if $keep_backup;
my $out = '';
for my $vol ( $self->list_volumes_info ) {
next if !$vol->file || $vol->file =~ /iso$/;
if ( !$self->is_active ) {
my $vm = $self->_vm->new ( host => 'localhost' );
$vol->vm($vm);
}
$request->error("compacting ".$vol->file."$backed_up") if $request;
$out .= $vol->info->{target}." ".($vol->compact($keep_backup) or '');
}
$request->error($out) if $request;
$self->_data('is_compacted' => 1);
$self->_data('has_backups' => $self->_data('has_backups') +1 ) if $keep_backup;
}
sub purge($self, $request=undef) {
my $vm = $self->_vm->new ( host => 'localhost' );
for my $vol ( $self->list_volumes_info ) {
next if !$vol->file || $vol->file =~ /iso$/;
my ($dir, $file) = $vol->file =~ m{(.*)/(.*)};
my ($out, $err) = $vm->run_command("ls",$dir);
die $err if $err;
my @found = grep { /^$file/ } $out =~ m{^(.*backup)}mg;
for my $file_backup ( @found ) {
$vm->remove_file("$dir/$file_backup");
}
}
$self->_data( 'has_backups' => 0 );
}
1;
......@@ -114,6 +114,8 @@ our %VALID_ARG = (
,migrate => { uid => 1, id_node => 1, id_domain => 1, start => 2, remote_ip => 2
,shutdown => 2, shutdown_timeout => 2
}
,compact => { uid => 1, id_domain => 1 , keep_backup => 2 }
,purge => { uid => 1, id_domain => 1 }
#users
,post_login => { user => 1, locale => 2 }
......@@ -170,6 +172,7 @@ our %COMMAND = (
, 'remove_base_vm'
, 'screenshot'
, 'cleanup'
, 'compact'
]
,priority => 20
}
......
......@@ -441,6 +441,7 @@ sub _around_create_domain {
my $domain = $self->$orig(%args_create, volatile => $volatile);
$self->_add_instance_db($domain->id);
$domain->add_volume_swap( size => $swap ) if $swap;
$domain->_data('is_compacted' => 1);
if ($id_base) {
$domain->run_timeout($base->run_timeout)
......
......@@ -96,4 +96,14 @@ sub copy_file($self, $src, $dst) {
die $err if $err;
}
sub backup($self) {
my $vol_backup = $self->file.".".time.".backup";
my ($out, $err) = $self->vm->run_command("cp","--preserve=all",$self->file,$vol_backup);
if ($err) {
$self->vm->remove_file($vol_backup);
die "Error: I can't backup $vol_backup $err";
}
return $vol_backup;
}
1;
......@@ -187,4 +187,32 @@ sub _qemu_info($self, $field=undef) {
return $info{$field};
}
sub compact($self, $keep_backup=1) {
my $vol_backup = $self->backup();
my @cmd = ( "virt-sparsify"
, "--in-place"
, $self->file
);
my ($out, $err) = $self->vm->run_command(@cmd);
die "Error: I can't sparsify ".$self->file." , backup file stored on $vol_backup : $err"
if $err;
@cmd = ("qemu-img", "check", $self->file);
($out, $err) = $self->vm->run_command(@cmd);
die "Error: problem checking ".$self->file." after virt-sparsify $err" if $err;
my ($du_backup, $du_backup_err) = $self->vm->run_command("du","-m",$vol_backup);
my ($du, $du_err) = $self->vm->run_command("du","-m",$self->file);
chomp $du_backup;
$du_backup =~ s/(^\d+).*/$1/;
chomp $du;
$du =~ s/(^\d+).*/$1/;
unlink $vol_backup or die "$! $vol_backup"
if !$keep_backup;
return int(100*($du_backup-$du)/$du_backup)." % compacted. ";
}
1;
......@@ -102,4 +102,9 @@ sub block_commit($self) {
}
sub compact($self, $keep_backup) {
$self->backup() if $keep_backup;
return $self->info->{target}." 100% compacted. ";
}
1;
......@@ -750,6 +750,14 @@ get '/machine/move_access/(#id_domain)/(#id_access)/(#position)' => sub {
};
get '/machine/compact/(#id_domain)' => sub($c) {
my $req = Ravada::Request->compact(
id_domain => $c->stash('id_domain')
,uid => $USER->id
);
return $c->render(json => { request => $req->id });
};
get '/node/exists/#name' => sub {
my $c = shift;
my $name = $c->stash('name');
......
......@@ -724,7 +724,7 @@ sub _remove_old_disks_kvm {
sleep 1;
}
for my $volume ( @volumes ) {
next if $volume->get_name !~ /^${name}_\d+.*\.(img|raw|ro\.qcow2|qcow2|void)$/;
next if $volume->get_name !~ /^${name}_\d+.*\.(img|raw|ro\.qcow2|qcow2|void|backup)$/;
eval { $volume->delete() };
warn $@ if $@;
......
......@@ -31,7 +31,7 @@ sub test_download($vm, $id_iso, $test=0) {
rvd_back->_process_all_requests_dont_fork();
is($req1->status, 'done');
is($req1->error,'');
is($req1->error,'',$iso->{name});
}
......
use warnings;
use strict;
use Carp qw(confess);
use Data::Dumper;
use Test::More;
no warnings "experimental::signatures";
use feature qw(signatures);
use lib 't/lib';
use Test::Ravada;
sub test_compact($vm) {
my $domain = create_domain($vm);
$domain->add_volume(type => 'TMP' , format => 'qcow2', size => 1024 * 10);
is($domain->_data('is_compacted'),1) or exit;
$domain->start(user_admin);
is($domain->_data('is_compacted'),0) or exit;
eval { $domain->compact() };
like($@,qr/is active/);
is($domain->_data('is_compacted'),0);
$domain->shutdown_now(user_admin);
is($domain->_data('is_compacted'),0);
$domain->compact();
is($domain->_data('is_compacted'),1);
$domain->_data('is_compacted' => 0);
my $req = Ravada::Request->compact(
id_domain => $domain->id
,uid => user_admin->id
);
wait_request( check_error => 0);
is($req->status,'done');
like($req->error, qr'compacted'i);
delete $domain->{_data};
is($domain->_data('is_compacted'),1);
for my $vol ($domain->list_volumes) {
next if $vol =~ /iso$/;
my ($dir, $file) = $vol =~ m{(.*)/(.*)};
my ($out, $err) = $vm->run_command("ls",$dir);
die $err if $err;
my @found = grep { /^$file/ } $out =~ m{^(.*backup)}mg;
is(scalar(@found),2) or die Dumper($vol,\@found);
}
$domain->start(user_admin);
$domain->hibernate(user_admin);
eval { $domain->compact() };
like($@,qr/t be compacted because it is/);
is($domain->_data('has_backups'),2);
$domain->purge();
is($domain->_data('has_backups'),0);
for my $vol ($domain->list_volumes) {
next if $vol =~ /iso$/;
my ($dir, $file) = $vol =~ m{(.*)/(.*)};
my ($out, $err) = $vm->run_command("ls",$dir);
die $err if $err;
my @found = grep { /^$file/ } $out =~ m{^(.*backup)}mg;
is(scalar(@found),0) or die Dumper($vol,\@found);
}
$domain->remove(user_admin);
}
#######################################################
clean();
for my $vm_name (vm_names() ) {
ok($vm_name);
SKIP: {
my $vm = rvd_back->search_vm($vm_name);
my $msg = "SKIPPED: No virtual managers found";
if ($vm && $vm_name =~ /kvm/i && $>) {
$msg = "SKIPPED: Test must run as root";
$vm = undef;
}
skip($msg,10) if !$vm;
diag("test compact on $vm_name");
test_compact($vm);
}
}
end();
done_testing();
......@@ -283,6 +283,48 @@ sub test_no_pool($vm) {
wait_request( debug => 0);
}
sub test_remove_clone($vm) {
my $base;
if ($vm->type eq 'KVM') {
my $base0 = import_domain( $vm->type , 'zz-test-base-alpine');
$base = $base0->clone(name => new_domain_name , user => user_admin);
}
$base = create_domain($vm) if !$base;
$base->pools(1);
$base->volatile_clones(1);
my $n = 5;
$base->pool_clones($n);
$base->pool_start($n);
my $req = Ravada::Request->manage_pools(uid => user_admin->id , _no_duplicate => 1);
wait_request( debug => 0);
is($req->status, 'done');
my $req_refresh = Ravada::Request->refresh_vms( _no_duplicate => 1);
wait_request( debug => 0);
is($req_refresh->status, 'done');
my @clones = $base->clones();
is(scalar @clones, $n);
Ravada::Domain->open($clones[0]->{id})->remove(user_admin);
is(scalar($base->clones()),$n-1);
warn Dumper([map { $_->{name}." ".$_->{status} } $base->clones]);
$req = Ravada::Request->manage_pools(uid => user_admin->id, _no_duplicate => 1);
wait_request();
is($req->status, 'done');
ok(Dumper([map { $_->{name} } $base->clones]));
for my $clone ( $base->clones ) {
like($clone->{name},qr/-\d+$/);
Ravada::Domain->open($clone->{id})->remove(user_admin);
}
$base->remove(user_admin);
}
###############################################################
init();
clean();
......@@ -303,6 +345,7 @@ for my $vm_name (reverse vm_names() ) {
diag("*** Testing pools in $vm_name ***");
test_remove_clone($vm);
test_duplicate_req();
test_no_pool($vm);
......
......@@ -42,4 +42,44 @@
<br><br>
</div>
<hr>
<div class="container">
<div class="row mt-2">
<div class="col-lg-2" align="right">
<button class="btn btn-outline-dark"
ng-disabled="showmachine.is_compacted"
ng-click="request('compact',{'id_domain': showmachine.id , 'keep_backup': true })"
><%=l 'Compact' %></button>
</div>
<div class="col-md-7">
<div><%=l 'Compact disk volumes' %></div>
<div ng-show="showmachine.is_compacted">
This virtual machine has already been compacted
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-lg-2" align="right">
<button class="btn btn-outline-dark"
ng-disabled="!showmachine.has_backups"
ng-click="request('purge',{'id_domain': showmachine.id })"
><%=l 'Purge' %></button>
</div>
<div class="col-md-7">
<span><%=l 'Purge disk volumes' %></span>
<div ng-hide="showmachine.has_backups">
This virtual machine has no backups to purge
</div>
</div>
</div>
</div>
<div ng-show="pending_request">
%= include "/main/pending_request"
</div>
</div>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment