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

Feature #198 disk (#974)

* test(backend); change and remove disk tests

issue #198

* test(backend); check new disk volumes added

test also new volumes are shown in frontend

issue #198

* feature(requests): change disk settings

and prepare the ground for change other hardware settings

issue #198

* wip(backend): refresh machine if allowed

issue #198

* refactor(volumes): get volume info

before we were only getting target

issue #198

* refactor(test): clean before and after

issue #198

* refactor(test): get proper exit error

issue #198

* refactor(test): get proper volume info

issue #198

* refactor(test): clean before and after

issue #198

* wip(volumes): store volume information

issue #198

* wip(volumes): disk drivers definitions

issue #198

* refactor(test); fixed remove test

We were removing the wrong machine

issue #198

* refactor(test): properly return if failed

issue #198

* feature(volumes): remove/list/change disk volumes...
parent 7e58178c
......@@ -557,6 +557,12 @@ sub _update_domain_drivers_types($self) {
,vm => 'KVM'
}
,disk => {
id => 9
,name => 'disk'
,vm => 'KVM'
}
};
$self->_update_table('domain_drivers_types','id',$data);
......@@ -730,6 +736,23 @@ sub _update_domain_drivers_options($self) {
$self->_update_table('domain_drivers_options','id',$data);
}
sub _update_domain_drivers_options_disk($self) {
my @options = ('virtio', 'usb','ide', 'sata', 'scsi');
my $id = 28;
my %data = map {
$_ => {
id => $id++
,id_driver_type => 9,
,name => $_
,value => $_
}
} @options;
$self->_update_table('domain_drivers_options','id',\%data);
}
sub _update_table($self, $table, $field, $data, $verbose=0) {
my $sth_search = $CONNECTOR->dbh->prepare("SELECT id FROM $table WHERE $field = ?");
......@@ -793,6 +816,7 @@ sub _update_data {
$self->_update_domain_drivers_types();
$self->_update_domain_drivers_options();
$self->_update_domain_drivers_options_disk();
$self->_update_old_qemus();
$self->_add_indexes();
......@@ -2605,13 +2629,14 @@ sub _cmd_add_hardware {
my $uid = $request->args('uid');
my $hardware = $request->args('name') or confess "Missing argument name";
my $id_domain = $request->defined_arg('id_domain') or confess "Missing argument id_domain";
my $number = $request->args('number');
my $domain = $self->search_domain_by_id($id_domain);
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: User ".$user->name." not allowed to add hardware to machine ".$domain->name
if !$user->is_admin;
$domain->set_controller($hardware, $number);
$domain->set_controller($hardware, $request->defined_arg('number'), $request->defined_arg('data'));
}
sub _cmd_remove_hardware {
......@@ -2630,6 +2655,28 @@ sub _cmd_remove_hardware {
$domain->remove_controller($hardware, $index);
}
sub _cmd_change_hardware {
my $self = shift;
my $request = shift;
my $uid = $request->args('uid');
my $hardware = $request->args('hardware') or confess "Missing argument hardware";
my $id_domain = $request->args('id_domain') or confess "Missing argument id_domain";
my $domain = $self->search_domain_by_id($id_domain);
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: User ".$user->name." not allowed\n"
if !$user->is_admin;
$domain->change_hardware(
$request->args('hardware')
,$request->args('index')
,$request->args('data')
);
}
sub _cmd_shutdown {
my $self = shift;
my $request = shift;
......@@ -2758,9 +2805,10 @@ sub _cmd_refresh_storage($self, $request=undef) {
sub _cmd_refresh_machine($self, $request) {
my $id_domain = $request->args('id_domain');
my $user = Ravada::Auth::SQL->search_by_id($request->args('uid'));
my $domain = Ravada::Domain->open($id_domain);
$domain->get_info();
$domain->info(Ravada::Utils::user_daemon);
$domain->list_volumes_info();
$domain->info($user);
}
......@@ -3075,6 +3123,7 @@ sub _req_method {
,set_driver => \&_cmd_set_driver
,domdisplay => \&_cmd_domdisplay
,screenshot => \&_cmd_screenshot
,add_disk => \&_cmd_add_disk
,copy_screenshot => \&_cmd_copy_screenshot
,cmd_cleanup => \&_cmd_cleanup
,remove_base => \&_cmd_remove_base
......@@ -3095,6 +3144,7 @@ sub _req_method {
,change_owner => \&_cmd_change_owner
,add_hardware => \&_cmd_add_hardware
,remove_hardware => \&_cmd_remove_hardware
,change_hardware => \&_cmd_change_hardware
,change_max_memory => \&_cmd_change_max_memory
,change_curr_memory => \&_cmd_change_curr_memory
# Virtual Managers or Nodes
......
......@@ -61,7 +61,9 @@ requires 'rename';
#storage
requires 'add_volume';
requires 'remove_volume';
requires 'list_volumes';
requires 'list_volumes_info';
requires 'disk_device';
......@@ -88,6 +90,7 @@ requires 'get_controller_by_name';
requires 'list_controllers';
requires 'set_controller';
requires 'remove_controller';
requires 'change_hardware';
#
##########################################################
......@@ -140,6 +143,8 @@ around 'display_info' => \&_around_display_info;
around 'display_file_tls' => \&_around_display_file_tls;
around 'add_volume' => \&_around_add_volume;
around 'remove_volume' => \&_around_remove_volume;
around 'list_volumes_info' => \&_around_list_volumes_info;
before 'remove' => \&_pre_remove_domain;
#\&_allow_remove;
......@@ -194,8 +199,9 @@ around 'is_hibernated' => \&_around_is_hibernated;
around 'autostart' => \&_around_autostart;
after 'set_controller' => \&_post_change_controller;
after 'remove_controller' => \&_post_change_controller;
after 'set_controller' => \&_post_change_hardware;
after 'remove_controller' => \&_post_change_hardware;
after 'change_hardware' => \&_post_change_hardware;
around 'name' => \&_around_name;
......@@ -462,13 +468,66 @@ sub _around_add_volume {
my %args = @_;
my $path = $args{path};
my $name = $args{name};
$args{size} = delete $args{capacity} if exists $args{capacity} && !exists $args{size};
my $size = $args{size};
my $target = $args{target};
if ( $path ) {
my $name = $args{name};
$self->_check_volume_added($path);
if (!$name) {
($args{name}) = $path =~ m{.*/(.*)};
$name = $args{name};
}
}
return $self->$orig(%args);
$args{size} = Ravada::Utils::size_to_number($size) if defined $size;
$args{allocation} = Ravada::Utils::size_to_number($args{allocation})
if exists $args{allocation} && defined $args{allocation};
my $ok = $self->$orig(%args);
confess "Error adding ".Dumper(\%args) if !$ok;
$path = $ok if ! $path;
return $ok;
}
sub _check_volume_added($self, $file) {
my $sth = $$CONNECTOR->dbh->prepare("SELECT id,id_domain FROM volumes "
." WHERE file=?"
);
$sth->execute($file);
my ($id, $id_domain) = $sth->fetchrow();
$sth->finish;
return if !$id;
confess "Volume $file already in domain id $id_domain";
}
sub _around_remove_volume {
my $orig = shift;
my $self = shift;
my ($file) = @_;
my $ok = $self->$orig(@_);
my $sth = $$CONNECTOR->dbh->prepare(
"DELETE FROM volumes "
." WHERE id_domain=? AND file=?"
);
$sth->execute($self->id, $file);
return $ok;
}
sub _around_list_volumes_info($orig, $self) {
return $self->$orig() if ref($self) =~ /^Ravada::Front/i;
my @volumes = $self->$orig();
for my $vol (@volumes) {
$self->cache_volume_info(%$vol);
}
return @volumes;
}
sub _pre_prepare_base($self, $user, $request = undef ) {
......@@ -1090,7 +1149,9 @@ sub info($self, $user) {
die "Field $_ already in info" if exists $info->{$_};
$info->{$_} = $internal_info->{$_};
}
for (qw(disk network)) {
$info->{drivers}->{$_} = $self->drivers($_,undef,1);
}
$info->{bases} = $self->_bases_vm();
$info->{clones} = $self->_clones_vm();
return $info;
......@@ -1180,8 +1241,10 @@ sub _pre_remove_domain($self, $user, @) {
$self->_check_active_node();
$self->is_volatile() if $self->is_known || $self->domain;
$self->list_disks() if ($self->is_known && $self->is_known_extra)
|| $self->domain ;
if (($self->is_known && $self->is_known_extra)
|| $self->domain ) {
$self->{_volumes} = [$self->list_disks()];
}
$self->pre_remove();
$self->_remove_iptables() if $self->is_known();
}
......@@ -1215,9 +1278,16 @@ sub _after_remove_domain {
$self->_finish_requests_db();
$self->_remove_base_db();
$self->_remove_access_attributes_db();
$self->_remove_all_volumes();
$self->_remove_domain_db();
}
sub _remove_all_volumes($self) {
for my $vol (@{$self->{_volumes}}) {
$self->remove_volume($vol);
}
}
sub _remove_domain_cascade($self,$user, $cascade = 1) {
return if !$self->_vm;
......@@ -1621,11 +1691,11 @@ sub _copy_clone($self, %args) {
,id_owner => $user->id
,@copy_arg
);
my @volumes = $self->list_volumes_target;
my @copy_volumes = $copy->list_volumes_target;
my @volumes = $self->list_volumes_info;
my @copy_volumes = $copy->list_volumes_info;
my %volumes = map { $_->[1] => $_->[0] } @volumes;
my %copy_volumes = map { $_->[1] => $_->[0] } @copy_volumes;
my %volumes = map { $_->{target} => $_->{file} } @volumes;
my %copy_volumes = map { $_->{target} => $_->{file} } @copy_volumes;
for my $target (keys %volumes) {
copy($volumes{$target}, $copy_volumes{$target})
or die "$! $volumes{$target}, $copy_volumes{$target}"
......@@ -2431,10 +2501,7 @@ List the drivers available for a domain. It may filter for a given type.
=cut
sub drivers {
my $self = shift;
my $name = shift;
my $type = shift;
sub drivers($self, $name=undef, $type=undef, $list=0) {
$type = $self->type if $self && !$type;
$type = $self->_vm->type if $self && !$type;
......@@ -2466,7 +2533,16 @@ sub drivers {
my @drivers;
while ( my ($id) = $sth->fetchrow) {
push @drivers,Ravada::Domain::Driver->new(id => $id, domain => $self);
my $cur_driver = Ravada::Domain::Driver->new(id => $id, domain => $self);
if ($list) {
my @options;
for my $option ( $cur_driver->get_options ) {
push @options,($option->{name});
}
push @drivers, \@options;
} else {
push @drivers,($cur_driver);
}
}
return $drivers[0] if !wantarray && $name && scalar@drivers< 2;
return @drivers;
......@@ -3141,8 +3217,9 @@ sub needs_restart($self, $value=undef) {
return $self->_data('needs_restart',$value);
}
sub _post_change_controller {
sub _post_change_hardware {
my $self = shift;
$self->info(Ravada::Utils::user_daemon) if $self->is_known();
$self->needs_restart(1) if $self->is_active;
}
......@@ -3276,4 +3353,51 @@ 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=?"
);
$sth->execute($name);
my $row = $sth->fetchrow_hashref();
confess "Error: volume $name belongs to domain $row->{id_domain}. "
."This is domain ".$self->id
if defined $row->{id_domain} && $self->id != $row->{id_domain};
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);
confess if $name eq 'tst_request_30_hardware_01';
my $row = $self->_get_volume_info($name);
if (!$row) {
my $file = $info{file} or
confess "Error: Missing file field ".Dumper(\%info);
my $sth = $$CONNECTOR->dbh->prepare(
"INSERT INTO volumes (id_domain, name, file, info) "
."VALUES(?,?,?,?)"
);
$sth->execute($self->id
,$name
,$file
,encode_json(\%info));
return;
}
for (keys %{$row->{info}}) {
$info{$_} = $row->{info}->{$_} if !exists $info{$_};
}
my $file = ($info{file} or $row->{file});
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=? WHERE id=?"
);
$sth->execute(encode_json(\%info), $name, $file, $self->id, $row->{id});
}
1;
......@@ -48,7 +48,6 @@ has readonly => (
##################################################
#
our $TIMEOUT_SHUTDOWN = 60;
our $OUT;
our %SET_DRIVER_SUB = (
......@@ -60,17 +59,25 @@ our %SET_DRIVER_SUB = (
,zlib => \&_set_driver_zlib
,playback => \&_set_driver_playback
,streaming => \&_set_driver_streaming
,disk => \&_set_driver_disk
);
our %GET_CONTROLLER_SUB = (
usb => \&_get_controller_usb
,disk => \&_get_controller_disk
);
our %SET_CONTROLLER_SUB = (
usb => \&_set_controller_usb
,disk => \&_set_controller_disk
);
our %REMOVE_CONTROLLER_SUB = (
usb => \&_remove_controller_usb
,disk => \&_remove_controller_disk
);
our %CHANGE_HARDWARE_SUB = (
disk => \&_change_hardware_disk
);
##################################################
sub BUILD {
......@@ -226,6 +233,10 @@ sub _vol_remove {
return 1;
}
sub remove_volume {
return _vol_remove(@_);
}
=head2 remove
Removes this domain. It removes also the disk drives and base images.
......@@ -252,7 +263,7 @@ sub remove {
confess $@ if $@ && $@ !~ /libvirt error code: 42/;
for my $file ( @volumes ) {
$self->_vol_remove($file);
$self->remove_volume($file);
}
eval { $self->_remove_file_image() };
......@@ -292,10 +303,11 @@ sub _remove_file_image {
sub _disk_device {
my $self = shift;
my $with_target = shift;
my $with_info = shift;
my $doc = XML::LibXML->load_xml(string => $self->xml_description)
my $doc = XML::LibXML->load_xml(string
=> $self->xml_description(Sys::Virt::Domain::XML_INACTIVE))
or die "ERROR: $!\n";
my @img;
......@@ -306,27 +318,53 @@ sub _disk_device {
$list_disks .= $disk->toString();
my ($file,$target);
my ($file,$target, $bus);
for my $child ($disk->childNodes) {
if ($child->nodeName eq 'source') {
$file = $child->getAttribute('file');
}
if ($child->nodeName eq 'target') {
$target = $child->getAttribute('dev');
$bus = $child->getAttribute('bus');
}
}
push @img,[$file,$target] if $with_target;
push @img,($file) if !$with_target;
}
if (!scalar @img) {
my (@devices) = $doc->findnodes('/domain/devices/disk');
die "I can't find disk device FROM "
.join("\n",map { $_->toString() } @devices);
if (!$with_info) {
push @img,($file);
next;
}
my $info = $self->_volume_info($file);
$info->{target} = $target;
$info->{driver} = $bus;
push @img,$info;
}
return @img;
}
sub _volume_info($self, $file, $refresh=0) {
my ($name) = $file =~ m{.*/(.*)};
my $vol;
for my $pool ( $self->_vm->vm->list_storage_pools ) {
$pool->refresh() if $refresh;
eval { $vol = $pool->get_volume_by_name($name) };
warn $@ if $@ && $@ !~ /^libvirt error code: 50,/;
last if $vol;
}
if (!$vol && !$refresh) {
return $self->_volume_info($file, ++$refresh);
}
confess "Error: Volume $file not found" if !$vol;
my $info = $vol->get_info;
$info->{file} = $file;
$info->{name} = $name;
$info->{driver} = delete $info->{bus};
return $info;
}
sub _disk_devices_xml {
my $self = shift;
......@@ -366,8 +404,8 @@ sub _create_qcow_base {
my @base_img;
for my $vol_data ( $self->list_volumes_target()) {
my ($file_img,$target) = @$vol_data;
for my $vol_data ( $self->list_volumes_info()) {
my ($file_img,$target) = ($vol_data->{file}, $vol_data->{target});
my $base_img = $file_img;
my $pool_base = $self->_vm->default_storage_pool_name;
......@@ -843,7 +881,8 @@ sub add_volume {
my $self = shift;
my %args = @_;
my %valid_arg = map { $_ => 1 } ( qw( name size vm xml swap target path));
my $bus = (delete $args{driver} or 'virtio');
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"
......@@ -851,27 +890,29 @@ sub add_volume {
}
# confess "Missing vm" if !$args{vm};
$args{vm} = $self->_vm if !$args{vm};
confess "Missing name " if !$args{name};
my ($target_dev) = ($args{target} or $self->_new_target_dev());
my $name = delete $args{name};
if (!$args{xml}) {
$args{xml} = $Ravada::VM::KVM::DIR_XML."/default-volume.xml";
$args{xml} = $Ravada::VM::KVM::DIR_XML."/swap-volume.xml" if $args{swap};
}
my $path = delete $args{path};
my $path = delete $args{file};
($name) = $path =~ m{.*/(.*)} if !$name && $path;
$path = $args{vm}->create_volume(
name => $args{name}
name => ($name or $self->name)
,xml => $args{xml}
,swap => ($args{swap} or 0)
,size => ($args{size} or undef)
,target => ( $args{target} or undef)
,allocation => ($args{allocation} or undef)
,target => $target_dev
) if !$path;
($name) = $path =~ m{.*/(.*)} if !$name;
# TODO check if <target dev="/dev/vda" bus='virtio'/> widhout dev works it out
# change dev=vd* , slot=*
#
my ($target_dev) = ($args{target} or $self->_new_target_dev());
my $pci_slot = $self->_new_pci_slot();
my $driver_type = 'qcow2';
my $cache = 'default';
......@@ -880,32 +921,53 @@ sub add_volume {
$driver_type = 'raw';
}
my $xml_device =<<EOT;
my $xml_device = $self->_xml_new_device(
bus => $bus
,file => $path
,type => $driver_type
,cache => $cache
,target => $target_dev
);
eval { $self->domain->attach_device($xml_device,Sys::Virt::Domain::DEVICE_MODIFY_CONFIG) };
die $@."\n".$self->domain->get_xml_description if$@;
return $path;
}
sub _xml_new_device($self , %arg) {
my $bus = delete $arg{bus} or confess "Missing bus.";
my $file = delete $arg{file} or confess "Missing target.";
my $xml = <<EOT;
<disk type='file' device='disk'>
<driver name='qemu' type='$driver_type' cache='$cache'/>
<source file='$path'/>
<driver name='qemu' type='$arg{type}' cache='$arg{cache}'/>
<source file='$file'/>
<backingStore/>
<target bus='virtio' dev='$target_dev'/>
<alias name='virtio-disk1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='$pci_slot' function='0x0'/>
<target bus='$bus' dev='$arg{target}'/>
<address type=''/>
</disk>
EOT
eval { $self->domain->attach_device($xml_device,Sys::Virt::Domain::DEVICE_MODIFY_CONFIG) };
die $@."\n".$self->domain->get_xml_description if$@;
}
my $device=XML::LibXML->load_xml(string => $xml);
my ($address) = $device->findnodes('/disk/address') or die "No address in ".$device->toString();
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
$self->_change_xml_address($doc, $address, $bus);
return $device->toString();
}
sub _new_target_dev {
my $self = shift;
my $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description)
my $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE))
or die "ERROR: $!\n";