Unverified Commit b4332406 authored by Francesc Guasch's avatar Francesc Guasch Committed by GitHub
Browse files

Rebase virtual machines (#1224)

feature(volumes): rebase volume files
parent 44e41a06
......@@ -1599,6 +1599,7 @@ sub create_domain {
my $start = $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);
_check_args(\%args,qw(iso_file id_base id_iso id_owner name active swap memory disk id_template start remote_ip request vm add_to_pool));
......@@ -1657,6 +1658,12 @@ sub create_domain {
die $error if $error && !$request;
$request->error($error) if $error;
}
Ravada::Request->add_hardware(
uid => $args{id_owner}
,id_domain => $domain->id
,name => 'disk'
,data => { size => $data, type => 'data' }
) if $domain && $data;
return $domain;
}
......@@ -2687,6 +2694,11 @@ sub _cmd_remove {
$self->remove_domain(name => $request->args('name'), uid => $request->args('uid'));
}
sub _cmd_restore_domain($self,$request) {
my $domain = Ravada::Domain->open($request->args('id_domain'));
return $domain->restore(Ravada::Auth::SQL->search_by_id($request->args('uid')));
}
sub _cmd_pause {
my $self = shift;
my $request = shift;
......@@ -2850,7 +2862,7 @@ sub _cmd_dettach($self, $request) {
$domain->dettach($user);
}
sub _cmd_rebase_volumes($self, $request) {
sub _cmd_rebase($self, $request) {
my $domain = Ravada::Domain->open($request->id_domain);
my $user = Ravada::Auth::SQL->search_by_id($request->args('uid'));
......@@ -2858,14 +2870,15 @@ sub _cmd_rebase_volumes($self, $request) {
if !$user->is_admin;
if ($domain->is_active) {
Ravada::Request->shutdown_domain(uid => $user->id, id_domain => $domain->id, timeout => 120);
$request->status("requested");
die "Error: domain ".$domain->name." is still active, shut it down to rebase\n"
my $req_shutdown = Ravada::Request->shutdown_domain(uid => $user->id, id_domain => $domain->id, timeout => 120);
$request->after_request($req_shutdown->id);
die "Warning: domain ".$domain->name." is up, retry.\n"
}
$request->status('working');
my $new_base = Ravada::Domain->open($request->args('id_base'));
$domain->rebase_volumes($new_base);
$domain->rebase($user, $new_base);
}
......@@ -3533,6 +3546,7 @@ sub _req_method {
,pause => \&_cmd_pause
,create => \&_cmd_create
,remove => \&_cmd_remove
,restore_domain => \&_cmd_restore_domain
,resume => \&_cmd_resume
,dettach => \&_cmd_dettach
,cleanup => \&_cmd_cleanup
......@@ -3555,7 +3569,8 @@ sub _req_method {
,list_vm_types => \&_cmd_list_vm_types
,enforce_limits => \&_cmd_enforce_limits
,force_shutdown => \&_cmd_force_shutdown
,rebase_volumes => \&_cmd_rebase_volumes
,rebase => \&_cmd_rebase
,refresh_storage => \&_cmd_refresh_storage
,refresh_machine => \&_cmd_refresh_machine
,domain_autostart=> \&_cmd_domain_autostart
......
......@@ -506,6 +506,7 @@ sub _around_add_volume {
my %args = @_;
my $file = ($args{file} or $args{path});
confess if $args{id_iso} && !$file;
my $name = $args{name};
$args{target} = $self->_new_target_dev() if !exists $args{target};
......@@ -585,10 +586,6 @@ sub _around_list_volumes_info($orig, $self, $attribute=undef, $value=undef) {
return $self->$orig($attribute, $value) if ref($self) =~ /^Ravada::Front/i;
my $sth = $$CONNECTOR->dbh->prepare("DELETE FROM volumes WHERE id_domain=?");
$sth->execute($self->id);
$sth->finish;
my @volumes = $self->$orig($attribute => $value);
return @volumes;
......@@ -1455,6 +1452,30 @@ sub _pre_remove_domain($self, $user, @) {
$owner->remove() if $owner && $owner->is_temporary();
}
sub restore($self,$user){
die "Error: ".$self->name." is not a clone. Only clones can be restored."
if !$self->id_base;
$self->_pre_remove_domain($user);
my $base = Ravada::Domain->open($self->id_base);
my @volumes = $self->list_volumes_info();
my %file = map { $_->info->{target} => $_->file } @volumes;
for my $file_data ( $base->list_files_base_target ) {
my ($file_base,$target) = @$file_data;
my $vol_base = Ravada::Volume->new(
file => $file_base
,is_base => 1
,vm => $self->_vm
);
next if $vol_base->file =~ /\.DATA\.\w+$/;
my $file_clone = $file{$target} or die Dumper(\%file);
unlink $file_clone;
my $clone = $vol_base->clone(file => $file_clone);
}
}
# check the node is active
# search the domain in another node if it is not
sub _check_active_node($self) {
......@@ -2978,10 +2999,7 @@ sub _rename_domain_db {
my $new_name = $args{name} or confess "Missing new name";
my $sth = $$CONNECTOR->dbh->prepare("UPDATE domains set name=?"
." WHERE id=?");
$sth->execute($new_name, $self->id);
$sth->finish;
$self->_data(name => $new_name);
}
=head2 is_public
......@@ -3091,8 +3109,11 @@ Check if the domain has swap volumes defined, and clean them
sub clean_swap_volumes {
my $self = shift;
for my $vol ( $self->list_volumes_info) {
$vol->restore()
if $vol->file && $vol->file =~ /\.SWAP\.\w+$/;
if ($vol->file && $vol->file =~ /\.SWAP\.\w+$/) {
eval { $vol->backing_file };
confess $@ if $@ && $@ !~ /No backing file/i;
$vol->restore() if !$@;
}
}
}
......@@ -3113,7 +3134,11 @@ sub _post_rename {
my $self = shift;
my %args = @_;
my $new_name = $args{new_name};
$self->_rename_domain_db(@_);
$self->{_name} = $new_name;
}
sub _post_dettach($self, @) {
......@@ -4057,6 +4082,8 @@ sub _pre_change_hardware($self, @) {
sub _post_change_hardware($self, $hardware, $index, $data=undef) {
if ($hardware eq 'disk' && ( defined $index || $data ) && $self->is_known() ) {
my $sth = $$CONNECTOR->dbh->prepare("DELETE FROM volumes WHERE id_domain=?");
$sth->execute($self->id);
my @volumes = $self->list_volumes_info();
}
$self->info(Ravada::Utils::user_daemon) if $self->is_known();
......@@ -4429,34 +4456,18 @@ sub set_access($self, $id_access, $allowed, $last) {
$sth->execute($allowed, $last, $id_access);
}
sub rebase($self, $user, $new_base) {
croak "Error: ".$self->name." is not a base\n" if !$self->is_base;
my @reqs = Ravada::Request->dettach(
uid => $user->id
,id_domain => $new_base->id
);
my @reqs;
push @reqs, Ravada::Request->prepare_base(
uid => $user->id
,id_domain => $new_base->id
,after_request => $reqs[0]->id
);
_create_base_as_old($self, $user, $new_base) if !$new_base->is_base;
for my $vm ($self->list_vms) {
next if $vm->is_local;
push @reqs, Ravada::Request->set_base_vm(
uid => $user->id
,id_vm => $vm->id
,id_domain => $new_base->id
,after_request => $reqs[-1]->id
);
if ( !$self->is_base ) {
return $self->_rebase_volumes($new_base);
}
$new_base->is_public($self->is_public);
$self->pool_clones(0);
$self->pool_start(0);
# if I am a base, we rebase all the clones
for my $clone_info ( $self->clones ) {
next if $clone_info->{id} == $new_base->id;
Ravada::Request->shutdown_domain(
......@@ -4464,30 +4475,116 @@ sub rebase($self, $user, $new_base) {
, id_domain => $clone_info->{id}
);
push @reqs,Ravada::Request->rebase_volumes(
my @args;
push @args, ( after_request => $reqs[-1]->id ) if $reqs[-1];
push @reqs,Ravada::Request->rebase (
uid => $user->id
,id_base => $new_base->id
,id_domain => $clone_info->{id}
,after_request => $reqs[-1]->id
,@args
,retry => 5
);
}
return @reqs;
}
sub rebase_volumes($self, $new_base) {
die "Error: domain ".$new_base->name." is not a base\n"
if !$new_base->is_base;
my @files_target = $new_base->list_files_base_target();
my %file_target = map { $_->[1] => $_->[0] } @files_target;
sub _create_base_as_old($self, $user, $new_base) {
$new_base->dettach($user);
$new_base->prepare_base($user);
for my $vol ( $self->list_volumes_info) {
next if $vol->info->{device} ne 'disk';
my $new_base = $file_target{$vol->info->{target}};
die "I can't find new base file for ".Dumper($vol) if !$new_base;
my @cmd = ('/usr/bin/qemu-img','rebase','-b',$new_base,$vol->file);
my ($out, $err) = $self->_vm->run_command(@cmd);
my $old_base = $self;
$old_base = Ravada::Domain->open($self->id_base) if $self->id_base;
my @reqs;
for my $vm ($old_base->list_vms) {
next if $vm->is_local;
my @after = (after_request => $reqs[0]->id ) if @reqs;
push @reqs, Ravada::Request->set_base_vm(
uid => $user->id
,id_vm => $vm->id
,id_domain => $new_base->id
,@after
);
}
$new_base->is_public($old_base->is_public);
return @reqs;
}
sub _rebase_volumes($self, $new_base) {
my %old;
for my $vol ( $self->list_volumes_info ) {
$old{$vol->info->{target}} = $vol;
}
_check_rebase_vols($self, $new_base, \%old);
# clone all volumes from new base but keep DATA volumes
for my $file_data ( $new_base->list_files_base_target ) {
my ($file_base,$target) = @$file_data;
my $vol = $old{$target};
#rebase DATA volumes
if ( $vol && $vol->file && $vol->file =~ /\.(DATA)\.\w+$/ ) {
$vol->rebase($file_base);
next;
}
#keep CDs
next if $vol
&& ( $vol->info->{device} eq 'cdrom'
|| ( $vol->file && $vol->file =~ /\.iso$/)
);
my $vol_base = Ravada::Volume->new(
file => $file_base
,is_base => 1
,vm => $self->_vm
);
my $vol_clone;
if ($vol) {
if ($vol->info->{device} eq 'disk' && $vol->file) {
$self->remove_volume($vol->file);
$vol_clone = $vol_base->clone(file => $vol->file);
} else {
confess "I don't know how to rebase ".Dumper($vol->info);
}
} else {
$vol_clone = $vol_base->clone(name => $self->name."-$target");
$self->add_volume(
file => $vol_clone->file
,target => $target
);
}
}
$self->id_base($new_base->id);
}
sub _check_rebase_vols($self, $new_base, $old) {
my %new = map {
my ($ext) = $_->[0] =~ /\.(\w+)$/;
my ($type) = $_->[0] =~ /\.([A-Z]+)\.\w+$/;
$type = 'SYS' if !defined $type;
$_->[1] => "$type.$ext"
} grep { $_->[0] }
$new_base->list_files_base_target;
my %old = map {
my $file = ($old->{$_}->file or '');
my ($ext) = $file =~ /\.(\w+)$/;
my ($type) = $file =~ /\.([A-Z]+)\.\w+$/;
$_ => ($type or 'SYS').".".($ext or "")
} grep { $old->{$_}->file } keys %$old;
for my $target (keys %new, keys %old) {
next if exists $old{$target} && exists $new{$target}
&& $old{$target} eq $new{$target};
die "Error: volume outline different in new base ".Dumper(\%new)
.". Expecting ".Dumper(\%old);
}
}
1;
......@@ -904,12 +904,18 @@ sub add_volume {
my $bus = delete $args{driver};# or 'virtio');
my $boot = (delete $args{boot} or undef);
my $device = (delete $args{device} or 'disk');
my $type = delete $args{type};
my %valid_arg = map { $_ => 1 } ( qw( driver name size vm xml swap target file allocation));
for my $arg_name (keys %args) {
confess "Unknown arg $arg_name"
if !$valid_arg{$arg_name};
}
$type = 'swap' if !defined $type && $args{swap};
$type = '' if !defined $type || $type eq 'sys';
confess "Error: type $type can't have swap flag" if $args{swap} && $type ne 'swap';
# confess "Missing vm" if !$args{vm};
$args{vm} = $self->_vm if !$args{vm};
my ($target_dev) = ($args{target} or $self->_new_target_dev());
......@@ -927,6 +933,7 @@ sub add_volume {
,xml => $args{xml}
,swap => ($args{swap} or 0)
,size => ($args{size} or undef)
,type => $type
,allocation => ($args{allocation} or undef)
,target => $target_dev
) if !$path;
......
......@@ -3,7 +3,7 @@ package Ravada::Domain::Void;
use warnings;
use strict;
use Carp qw(cluck croak);
use Carp qw(carp cluck croak);
use Data::Dumper;
use Fcntl qw(:flock SEEK_END);
use File::Copy;
......@@ -285,14 +285,18 @@ sub add_volume {
my %args = @_;
my $device = ( delete $args{device} or 'disk' );
my $type = ( delete $args{type} or '');
my $suffix = ".void";
$suffix = '.SWAP.void' if $args{swap};
$type = 'swap' if $args{swap};
$type = '' if $type eq 'sys';
$type = uc($type)."." if $type;
my $suffix = "void";
if ( !$args{file} ) {
my $vol_name = ($args{name} or Ravada::Utils::random_name(4) );
$args{file} = $self->_config_dir."/$vol_name";
$args{file} .= $suffix if $args{file} !~ /\.\w+$/;
$args{file} .= ".$type$suffix" if $args{file} !~ /\.\w+$/;
}
($args{name}) = $args{file} =~ m{.*/(.*)};
......@@ -347,6 +351,9 @@ sub remove_volume($self, $file) {
confess "Missing file" if ! defined $file || !length($file);
$self->_vol_remove($file);
}
sub _remove_controller_disk($self,$file) {
return if ! $self->_vm->file_exists($self->_config_file);
my $data = $self->_load();
my $hardware = $data->{hardware};
......@@ -665,6 +672,7 @@ sub _remove_disk {
confess "Index is '$index' not number" if !defined $index || $index !~ /^\d+$/;
my @volumes = $self->list_volumes();
$self->remove_volume($volumes[$index]);
$self->_remove_controller_disk($volumes[$index]);
}
sub remove_controller {
......
......@@ -53,6 +53,7 @@ our %VALID_ARG = (
# ,network => 2
,remote_ip => 2
,start => 2
,data => 2
}
,open_iptables => $args_manage_iptables
,remove_base => $args_remove_base
......@@ -60,6 +61,7 @@ our %VALID_ARG = (
,pause_domain => $args_manage
,resume_domain => {%$args_manage, remote_ip => 1 }
,remove_domain => $args_manage
,restore_domain => { id_domain => 1, uid => 1 }
,shutdown_domain => { name => 2, id_domain => 2, uid => 1, timeout => 2, at => 2
, id_vm => 2 }
,force_shutdown_domain => { id_domain => 1, uid => 1, at => 2, id_vm => 2 }
......@@ -95,7 +97,7 @@ our %VALID_ARG = (
,change_curr_memory => {uid => 1, id_domain => 1, ram => 1}
,enforce_limits => { timeout => 2, _force => 2 }
,refresh_machine => { id_domain => 1, uid => 1 }
,rebase_volumes => { uid => 1, id_base => 1, id_domain => 1 }
,rebase => { uid => 1, id_base => 1, id_domain => 1 }
# ports
,expose => { uid => 1, id_domain => 1, port => 1, name => 2, restricted => 2, id_port => 2}
,remove_expose => { uid => 1, id_domain => 1, port => 1}
......@@ -130,6 +132,7 @@ our %CMD_SEND_MESSAGE = map { $_ => 1 }
add_hardware remove_hardware set_driver change_hardware
expose remove_expose
set_base_vm
rebase rebase_volumes
shutdown_node start_node
);
......
......@@ -674,12 +674,14 @@ sub create_volume {
my $size = delete $args{size};
$size = int($size) if defined $size;
my $type =(delete $args{type} or 'sys');
my $swap =(delete $args{swap} or 0);
my $target = delete $args{target};
my $capacity = delete $args{capacity};
my $allocation = delete $args{allocation};
confess "ERROR: Unknown args ".Dumper(\%args) if keys %args;
confess "Error: type $type can't have swap flag" if $args{swap} && $type ne 'swap';
confess "Invalid size" if defined $size && ( $size == 0 || $size !~ /^\d+(\.\d+)?$/);
......@@ -706,7 +708,7 @@ sub create_volume {
my $img_file = $self->_volume_path(
target => $target
, swap => $swap
, type => $type
, name => $name
, storage => $storage_pool
);
......@@ -735,15 +737,16 @@ sub _volume_path {
my $self = shift;
my %args = @_;
my $swap =(delete $args{swap} or 0);
my $type = (delete $args{type} or 'sys');
my $storage = delete $args{storage} or confess "ERROR: Missing storage";
my $filename = $args{name} or confess "ERROR: Missing name";
my $target = delete $args{target};
my $dir_img = $self->_storage_path($storage);
my $suffix = "qcow2";
$suffix = "SWAP.qcow2" if $swap;
return "$dir_img/$filename.$suffix";
$type = '' if $type eq 'sys';
$type = uc($type)."." if $type;
return "$dir_img/$filename.$type$suffix";
}
sub _domain_create_from_iso {
......
......@@ -11,6 +11,7 @@ use Moose;
use Socket qw( inet_aton inet_ntoa );
use Sys::Hostname;
use URI;
use YAML qw(Dump);
use Ravada::Domain::Void;
use Ravada::NetInterface::Void;
......@@ -33,6 +34,8 @@ has 'vm' => (
,lazy => 1
);
our $CONNECTOR = \$Ravada::CONNECTOR;
##########################################################################
#
......@@ -114,8 +117,10 @@ sub create_domain {
);
my $vol_clone = $vol_base->clone(name => "$args{name}-$target");
$domain->add_volume(name => $vol_clone->name
, target => $target
, file => $vol_clone->file
,type => 'file');
,type => 'file'
);
}
my $drivers = {};
$drivers = $domain_base->_value('drivers');
......@@ -130,15 +135,8 @@ sub create_domain {
, type => 'file'
, target => 'vda'
);
my $cdrom_file = $domain->_config_dir()."/$args{name}-cdrom-"
.Ravada::Utils::random_name(4).".iso";
my ($cdrom_name) = $cdrom_file =~ m{.*/(.*)};
$domain->add_volume(name => $cdrom_name
, file => $cdrom_file
, device => 'cdrom'
, type => 'cdrom'
, target => 'hdc'
);
$self->_add_cdrom($domain, %args);
$domain->_set_default_drivers();
$domain->_set_default_info($listen_ip);
$domain->_store( is_active => 0 );
......@@ -151,6 +149,34 @@ sub create_domain {
return $domain;
}
sub _add_cdrom($self, $domain, %args) {
my $id_iso = delete $args{id_iso};
my $iso_file = delete $args{iso_file};
return if !$id_iso && !$iso_file;
if ($id_iso && ! $iso_file) {
my $sth = $$CONNECTOR->dbh->prepare("SELECT * FROM iso_images WHERE id=?");
$sth->execute($id_iso);
my $row = $sth->fetchrow_hashref();
$iso_file = $row->{device};
if (!$iso_file) {
$iso_file = $row->{name};
$iso_file =~ s/\s/_/g;
$iso_file=$self->dir_img."/".lc($iso_file).".iso";
if (! -e $iso_file ) {
$self->write_file($iso_file,Dump({iso => "ISO mock $row->{name}"}));
}
}
}
$iso_file = '' if $iso_file eq '<NONE>';
$domain->add_volume(
file => $iso_file
, device => 'cdrom'
, type => 'cdrom'
, target => 'hdc'
);
}
sub create_volume {
}
......
......@@ -72,6 +72,7 @@ has 'clone_base_after_prepare' => (
sub _type ($file) {
my ($ext) = $file =~ m{.*\.(.*)};
confess if !defined $ext;
confess if $ext =~ /-/;
my %type = (
void => 'Void'
......@@ -128,6 +129,15 @@ sub type($self) {
}
sub base_filename($self) {
return $self->_default_base_filename() if !$self->domain;
my $base_img = $self->vm->dir_base($self->capacity)
."/".$self->domain->name."-".$self->info->{target}.".".$self->base_extension;
return $base_img;
}
sub _default_base_filename($self) {
my $ext = $self->base_extension();
my $base_img = $self->file;
......@@ -136,7 +146,10 @@ sub base_filename($self) {
my $dir_base = $self->vm->dir_base($self->capacity);
$base_img =~ s{(.*)/(.*)\.\w+$}{$dir_base/$2\.ro.$ext};
$base_img =~ s{\.\w+$}{};