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

refactor volume management (#1128)

refactor(volumes): volume management

issue #1127 
parent edb27ae4
......@@ -56,7 +56,6 @@ requires '_do_force_shutdown';
requires 'pause';
requires 'resume';
requires 'prepare_base';
requires 'rename';
requires 'dettach';
......@@ -73,7 +72,6 @@ requires 'disk_size';
requires 'spinoff_volumes';
requires 'clean_swap_volumes';
#hardware info
requires 'get_info';
......@@ -507,17 +505,22 @@ sub _around_add_volume {
if scalar @_ % 2;
my %args = @_;
my $path = $args{path};
my $file = ($args{file} or $args{path});
my $name = $args{name};
$args{target} = $self->_new_target_dev() if !exists $args{target};
if (!$name) {
($name) = $file =~ m{.*/(.*)} if !$name && $file;
$name = $self->name if !$name;
$name .= "-".Ravada::Utils::random_name(4)."-$args{target}";
$args{name} = $name;
}
$args{size} = delete $args{capacity} if exists $args{capacity} && !exists $args{size};
my $size = $args{size};
my $target = $args{target};
if ( $path ) {
$self->_check_volume_added($path);
if (!$name) {
($args{name}) = $path =~ m{.*/(.*)};
$name = $args{name};
}
if ( $file ) {
$self->_check_volume_added($file);
}
$args{size} = Ravada::Utils::size_to_number($size) if defined $size;
$args{allocation} = Ravada::Utils::size_to_number($args{allocation})
......@@ -531,9 +534,15 @@ sub _around_add_volume {
."\n"
if exists $args{size} && $args{size} >= $free;
if ($name) {
confess "Error: volume $name already exists"
if grep {$_->info->{name} eq $name} $self->list_volumes_info;
}
confess "Error: target $args{target} already exists"
if grep {$_->info->{target} eq $args{target} } $self->list_volumes_info;
my $ok = $self->$orig(%args);
confess "Error adding ".Dumper(\%args) if !$ok;
$path = $ok if ! $path;
return $ok;
}
......@@ -574,23 +583,19 @@ sub _around_list_volumes_info($orig, $self, $attribute=undef, $value=undef) {
return $self->$orig($attribute, $value) if ref($self) =~ /^Ravada::Front/i;
my @volumes = $self->$orig($attribute => $value);
#TODO make these atomic
my $sth = $$CONNECTOR->dbh->prepare("DELETE FROM volumes WHERE id_domain=?");
$sth->execute($self->id);
$sth->finish;
for my $vol (@volumes) {
$self->cache_volume_info(%$vol);
}
my @volumes = $self->$orig($attribute => $value);
return @volumes;
}
sub _around_prepare_base($orig, $self, $user, $request = undef) {
$self->_pre_prepare_base($user, $request);
my @base_img = $self->$orig($user, $request);
my @base_img = $self->$orig();
die "Error: No information files returned from prepare_base"
if !scalar (\@base_img);
......@@ -600,6 +605,28 @@ sub _around_prepare_base($orig, $self, $user, $request = undef) {
$self->_post_prepare_base($user, $request);
}
sub prepare_base($self) {
my @base_img;
for my $volume ($self->list_volumes_info(device => 'disk')) {
confess "Undefined info->target ".Dumper($volume)
if !$volume->info->{target};
my $base = $volume->prepare_base();
push @base_img,([$base, $volume->info->{target}]);
}
$self->post_prepare_base();
return @base_img;
}
=head2 post_prepare_base
Placeholder for optional method implemented in subclasses. This will
run after preparing the base files.
=cut
sub post_prepare_base($self) {}
sub _pre_prepare_base($self, $user, $request = undef ) {
$self->_allowed($user);
......@@ -648,9 +675,7 @@ sub _check_free_space_prepare_base($self) {
$pool_base = $self->_vm->base_storage_pool() if $self->_vm->base_storage_pool();
for my $volume ($self->list_volumes_info(device => 'disk')) {;
next if $volume->{device} ne 'disk';
$self->_vm->_check_free_disk($volume->{capacity} * 2, $pool_base);
$self->_vm->_check_free_disk($volume->capacity * 2, $pool_base);
}
};
......@@ -1285,6 +1310,9 @@ sub info($self, $user) {
}
$info->{hardware} = $self->get_controllers();
confess Dumper($info->{hardware}->{disk}->[0])
if ref($info->{hardware}->{disk}->[0]) =~ /^Ravada::Vol/;
my $internal_info = $self->get_info();
for (keys(%$internal_info)) {
die "Field $_ already in info" if exists $info->{$_};
......@@ -1733,6 +1761,7 @@ sub _do_remove_base($self, $user) {
}
$self->is_base(0);
for my $file ($self->list_files_base) {
next if $file =~ /\.iso$/i;
next if ! -e $file;
unlink $file or die "$! unlinking $file";
}
......@@ -1883,8 +1912,8 @@ sub _copy_clone($self, %args) {
my @volumes = $self->list_volumes_info(device => 'disk');
my @copy_volumes = $copy->list_volumes_info(device => 'disk');
my %volumes = map { $_->{target} => $_->{file} } @volumes;
my %copy_volumes = map { $_->{target} => $_->{file} } @copy_volumes;
my %volumes = map { $_->info->{target} => $_->file } @volumes;
my %copy_volumes = map { $_->info->{target} => $_->file } @copy_volumes;
for my $target (keys %volumes) {
copy($volumes{$target}, $copy_volumes{$target})
or die "$! $volumes{$target}, $copy_volumes{$target}"
......@@ -2896,9 +2925,9 @@ Check if the domain has swap volumes defined, and clean them
sub clean_swap_volumes {
my $self = shift;
for my $file ( $self->list_volumes) {
$self->clean_disk($file)
if $file =~ /\.SWAP\.\w+$/;
for my $vol ( $self->list_volumes_info) {
$vol->restore()
if $vol->file && $vol->file =~ /\.SWAP\.\w+$/;
}
}
......@@ -3853,8 +3882,10 @@ sub _pre_change_hardware($self, @) {
}
}
sub _post_change_hardware {
my $self = shift;
sub _post_change_hardware($self, $hardware, $index, $data=undef) {
if ($hardware eq 'disk' && ( defined $index || $data ) && $self->is_known() ) {
my @volumes = $self->list_volumes_info();
}
$self->info(Ravada::Utils::user_daemon) if $self->is_known();
$self->_remove_domain_cascade(Ravada::Utils::user_daemon,1);
$self->needs_restart(1) if $self->is_known && $self->_data('status') eq 'active';
......@@ -3990,57 +4021,6 @@ sub set_ldap_access($self, $id_access, $allowed, $last) {
$sth->execute($allowed, $last, $id_access);
}
sub _get_volume_info($self, $name) {
my $sth = $$CONNECTOR->dbh->prepare(
"SELECT * from volumes "
." WHERE name=? "
." AND id_domain=? "
." ORDER by n_order"
);
$sth->execute($name, $self->id);
my $row = $sth->fetchrow_hashref();
return if !$row || !keys %$row;
if ( $row->{info} ) {
$row->{info} = decode_json($row->{info})
}
return $row;
}
sub cache_volume_info($self, %info) {
my $name = delete $info{name} or confess "No name in info ".Dumper(\%info);
my $row = $self->_get_volume_info($name);
if (!$row) {
my $file = (delete $info{file} or '');
confess "Error: Missing n_order field ".Dumper(\%info) if !exists $info{n_order};
my $n_order = delete $info{n_order};
eval {
my $sth = $$CONNECTOR->dbh->prepare(
"INSERT INTO volumes (id_domain, name, file, n_order, info) "
."VALUES(?,?,?,?,?)"
);
$sth->execute($self->id
,$name
,$file
,$n_order
,encode_json(\%info));
};
confess "$name / $n_order \n".$@ if $@;
return;
}
for (keys %{$row->{info}}) {
$info{$_} = $row->{info}->{$_} if !exists $info{$_};
}
my $file = (delete $info{file} or $row->{file});
my $n_order = (delete $info{n_order} or $row->{n_order});
confess "Error: Missing file field ".Dumper(\%info, $row)
if !defined $file || !length($file);
my $sth = $$CONNECTOR->dbh->prepare(
"UPDATE volumes set info=?, name=?,file=?,id_domain=?,n_order=? WHERE id=?"
);
$sth->execute(encode_json(\%info), $name, $file, $self->id, $n_order, $row->{id});
}
sub rebase($self, $user, $new_base) {
croak "Error: ".$self->name." is not a base\n" if !$self->is_base;
......
......@@ -354,7 +354,7 @@ sub _disk_device($self, $with_info=undef, $attribute=undef, $value=undef) {
push @img,($file) if $file;
next;
}
push @img,$info;
push @img,Ravada::Volume->new(file => $file, info => $info, domain => $self);
}
return @img;
......@@ -421,55 +421,16 @@ sub disk_device {
return $self->_disk_device(@_);
}
sub _create_qcow_base {
my $self = shift;
my @base_img;
for my $vol_data ( $self->list_volumes_info( device => 'disk')) {
my ($file_img,$target) = ($vol_data->{file}, $vol_data->{target});
my $base_img = $file_img;
my $pool_base = $self->_vm->default_storage_pool_name;
$pool_base = $self->_vm->base_storage_pool() if $self->_vm->base_storage_pool();
$pool_base = $self->_vm->storage_pool() if !$pool_base;
$self->_vm->_check_free_disk($vol_data->{capacity} * 2);
my $dir_base = $self->_vm->_storage_path($pool_base);
my @cmd;
$base_img =~ s{(.*)/(.*)\.\w+$}{$dir_base/$2\.ro.qcow2};
die "ERROR: base image already exists '$base_img'" if -e $base_img;
if ($file_img =~ /\.SWAP\.\w+$/) {
@cmd = _cmd_copy($file_img, $base_img);
} else {
@cmd = _cmd_convert($file_img,$base_img);
}
push @base_img,([$base_img,$target]);
my ($in, $out, $err);
run3(\@cmd,\$in,\$out,\$err);
warn $out if $out;
warn "$?: $err" if $err;
my $base_img = $vol_data->prepare_base();
push @base_img,([$base_img,$vol_data->info->{target}]);
if ($? || ! -e $base_img) {
chomp $err;
chomp $out;
die "ERROR: Output file $base_img not created at "
."\n"
."ERROR $?: '".($err or '')."'\n"
." OUT: '".($out or '')."'\n"
."\n"
.join(" ",@cmd);
}
chmod 0555,$base_img;
unlink $file_img or die "$! $file_img";
$self->_vm->_clone_disk($base_img, $file_img);
}
return @base_img;
......@@ -538,20 +499,17 @@ sub _create_swap_base {
=cut
=head2 prepare_base
=head2 post_prepare_base
Prepares a base virtual machine with this domain disk
Task to run after preparing a base virtual machine
=cut
sub prepare_base {
sub post_prepare_base {
my $self = shift;
# my @img = $self->_create_swap_base();
my @img = $self->_create_qcow_base();
$self->_store_xml();
return @img;
}
sub _store_xml {
......@@ -958,7 +916,7 @@ sub add_volume {
($name) = $path =~ m{.*/(.*)} if !$name && $path;
$path = $args{vm}->create_volume(
name => ($name or $self->name)
name => $name
,xml => $args{xml}
,swap => ($args{swap} or 0)
,size => ($args{size} or undef)
......@@ -1641,6 +1599,8 @@ sub _hwaddr {
return @hwaddr;
}
=pod
sub _find_base {
my $self = shift;
my $file = shift;
......@@ -1659,8 +1619,6 @@ sub _find_base {
Clean swap volumes. It actually just creates an empty qcow file from the base
=cut
sub clean_swap_volumes {
my $self = shift;
return if !$self->is_local;
......@@ -1680,6 +1638,8 @@ sub clean_swap_volumes {
}
}
=cut
=head2 set_driver
Sets the value of a driver
......@@ -2147,7 +2107,8 @@ sub _change_hardware_disk($self, $index, $data) {
sub _change_hardware_disk_capacity($self, $index, $capacity) {
my @volumes = $self->list_volumes_info();
my $file = $volumes[$index]->{file};
my $vol_orig = $volumes[$index];
my $file = $vol_orig->file;
my $volume = $self->_vm->search_volume($file);
if (!$volume ) {
......@@ -2158,10 +2119,12 @@ sub _change_hardware_disk_capacity($self, $index, $capacity) {
my ($name) = $file =~ m{.*/(.*)};
my $new_capacity = Ravada::Utils::size_to_number($capacity);
my $old_capacity = $volume->get_info->{'capacity'};
$self->cache_volume_info(name => $name, capacity => $old_capacity) if $old_capacity;
# my $old_capacity = $volume->get_info->{'capacity'};
# if ( $old_capacity ) {
# $vol_orig->set_info( capacity => $old_capacity);
# $self->cache_volume_info($vol_orig);
#}
$volume->resize($new_capacity);
$self->cache_volume_info(name => $name, capacity => $new_capacity);
}
sub _change_hardware_disk_file($self, $index, $file) {
......@@ -2214,6 +2177,7 @@ sub _change_hardware_disk_bus($self, $index, $bus) {
sub _change_hardware_network($self, $index, $data) {
confess if !defined $index;
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
......
......@@ -14,6 +14,8 @@ use IPC::Run3 qw(run3);
use Moose;
use YAML qw(Load Dump LoadFile DumpFile);
use Ravada::Volume;
no warnings "experimental::signatures";
use feature qw(signatures);
......@@ -227,6 +229,8 @@ sub start($self, @args) {
$self->_set_display( $listen_ip );
}
=pod
sub prepare_base {
my $self = shift;
......@@ -248,10 +252,12 @@ sub prepare_base {
return @img;
}
=cut
sub list_disks {
my @disks;
for my $disk ( list_volumes_info(@_)) {
push @disks,( $disk->{file}) if $disk->{type} eq 'file';
push @disks,( $disk->file) if $disk->type eq 'file';
}
return @disks;
}
......@@ -274,7 +280,7 @@ sub remove_disks {
my @files = $self->list_volumes_info;
for my $vol (@files) {
my $file = $vol->{file};
my $device = $vol->{device};
my $device = $vol->info->{device};
next if $device eq 'cdrom';
$self->_vol_remove($file);
}
......@@ -302,8 +308,8 @@ sub add_volume {
my $device = ( delete $args{device} or 'disk' );
my $suffix = ".img";
$suffix = '.SWAP.img' if $args{swap};
my $suffix = ".void";
$suffix = '.SWAP.void' if $args{swap};
if ( !$args{file} ) {
my $vol_name = ($args{name} or Ravada::Utils::random_name(4) );
......@@ -332,13 +338,13 @@ sub add_volume {
delete $args{vm} if defined $args{vm};
my $data = $self->_load();
$args{target} = _new_target($data) if !$args{target};
$args{target} = $self->_new_target() if !$args{target};
$args{driver} = 'foo' if !exists $args{driver};
my $hardware = $data->{hardware};
my $device_list = $hardware->{device};
my $file = delete $args{file};
push @$device_list, {
my $data_new = {
name => $args{name}
,file => $file
,type => $args{type}
......@@ -346,12 +352,15 @@ sub add_volume {
,driver => $args{driver}
,device => $device
};
$data_new->{boot} = $args{boot} if $args{boot};
push @$device_list, $data_new;
$hardware->{device} = $device_list;
$self->_store(hardware => $hardware);
delete @args{'name', 'target', 'driver'};
$args{a} = 'a'x400;
$self->_vm->write_file($file, Dump(\%args)),
if ( ! -e $file ) {
$self->_vm->write_file($file, Dump(\%args)),
}
return $file;
}
......@@ -373,8 +382,10 @@ sub remove_volume($self, $file) {
$self->_store(hardware => $hardware);
}
sub _new_target {
my $data = shift;
sub _new_target_dev { return _new_target(@_) }
sub _new_target($self) {
my $data = $self->_load();
return 'vda' if !$data or !keys %$data;
my %targets;
for my $dev ( @{$data->{hardware}->{device}}) {
......@@ -444,17 +455,18 @@ sub list_volumes_info($self, $attribute=undef, $value=undef) {
next if exists $dev->{type}
&& $dev->{type} eq 'base';
my $info = {};
if (exists $dev->{file} ) {
eval { $info = Load($self->_vm->read_file($dev->{file})) if -e $dev->{file} };
confess "Error loading $dev->{file} ".$@ if $@;
next if defined $attribute
&& (!exists $dev->{$attribute} || $dev->{$attribute} ne $value);
}
$info = {} if !defined $info;
$info->{n_order} = $n_order++;
push @vol,({%$dev,%$info})
$dev->{n_order} = $n_order++;
my $vol = Ravada::Volume->new(
file => $dev->{file}
,info => $dev
,domain => $self
);
push @vol,($vol);
}
return @vol;
......@@ -589,13 +601,9 @@ sub ip {
return $info->{ip};
}
sub clean_swap_volumes {
my $self = shift;
for my $file ($self->list_volumes) {
next if $file !~ /SWAP.img$/;
open my $out,'>',$file or die "$! $file";
close $out;
}
sub clean_disk($self, $file) {
open my $out,'>',$file or die "$! $file";
close $out;
}
sub hybernate {
......
......@@ -135,12 +135,12 @@ sub list_volumes($self, $attribute=undef, $value=undef)
if defined $row->{info}->{capacity} && $row->{info}->{capacity} =~ /^\d+$/;
$row->{info}->{allocation} = Ravada::Utils::number_to_size($row->{info}->{allocation})
if defined $row->{info}->{allocation} && $row->{info}->{allocation} =~ /^\d+$/;
$row->{driver} = delete $row->{info}->{driver};
next if defined $attribute
&& ( !exists $row->{$attribute}
|| $row->{$attribute} != $value);
push @volumes, ($row);
$row->{info}->{file} = $row->{file} if $row->{file};
push @volumes, (Ravada::Volume->new(file => $row->{file}, info => $row->{info}));
}
$sth->finish;
return @volumes;
......@@ -188,4 +188,9 @@ sub list_controllers {}
sub set_controller {}
sub remove_controller {}
sub change_hardware { die "TODO" }
sub _get_controller_disk($self) {
return map { $_->info } $self->list_volumes_info();
}
1;
package Ravada::Front::Domain::KVM;
use Carp qw(confess);
use Data::Dumper;
use Moose;
use XML::LibXML;
......@@ -53,10 +55,6 @@ sub _get_controller_usb {
return @ret;
}
sub _get_controller_disk($self) {
return $self->list_volumes_info();
}
sub _get_controller_network($self) {
$self->xml_description if !$self->readonly();
my $doc = XML::LibXML->load_xml(string => $self->_data_extra('xml'));
......@@ -92,6 +90,10 @@ sub _get_controller_network($self) {
return @ret;
}
sub _get_controller_disk($self) {
return Ravada::Front::Domain::_get_controller_disk($self);
}
=head2 get_driver
Gets the value of a driver
......@@ -213,9 +215,9 @@ sub _get_driver_sound {
}
sub _get_driver_disk {
my $self = shift;
sub _get_driver_disk($self) {
my @volumes = $self->list_volumes_info();
return $volumes[0]->{driver};
return $volumes[0]->info()->{driver};
}
1;
package Ravada::Front::Domain::Void;
use Data::Dumper;
use Moose;
use YAML qw(LoadFile);
......@@ -67,8 +68,7 @@ sub _get_controller_mock {
}
sub _get_controller_disk {
my $self = shift;