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

Change number of virtual CPUs (#1238)

feature(KVM): change number of virtual CPUs

* change CPUs and memory
* refactor(test): change memory via hardware
* refactor(frontend): more responsive machine settings
parent 71e14fe3
......@@ -3079,7 +3079,7 @@ sub _cmd_change_hardware {
$domain->change_hardware(
$request->args('hardware')
,$request->args('index')
,$request->defined_arg('index')
,$request->args('data')
);
}
......@@ -3264,24 +3264,6 @@ sub _cmd_refresh_vms($self, $request=undef) {
$self->_refresh_volatile_domains();
}
sub _cmd_change_max_memory($self, $request) {
my $uid = $request->args('uid');
my $id_domain = $request->args('id_domain');
my $memory = $request->args('ram');
my $domain = $self->search_domain_by_id($id_domain);
$domain->set_max_mem($memory);
}
sub _cmd_change_curr_memory($self, $request) {
my $uid = $request->args('uid');
my $id_domain = $request->args('id_domain');
my $memory = $request->args('ram');
my $domain = $self->search_domain_by_id($id_domain);
$domain->set_memory($memory);
}
sub _cmd_shutdown_node($self, $request) {
my $id_node = $request->args('id_node');
my $node = Ravada::VM->open($id_node);
......@@ -3612,8 +3594,6 @@ sub _req_method {
,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
# Domain ports
,expose => \&_cmd_expose
......
......@@ -1339,6 +1339,10 @@ sub info($self, $user) {
,is_pool => $self->is_pool
,comment => $self->_data('comment')
,screenshot => $self->_data('screenshot')
,run_timeout => $self->run_timeout
,autostart => $self->autostart
,volatile_clones => $self->volatile_clones
,id_owner => $self->_data('id_owner')
};
if ($is_active) {
eval {
......
......@@ -81,6 +81,8 @@ our %REMOVE_CONTROLLER_SUB = (
our %CHANGE_HARDWARE_SUB = (
disk => \&_change_hardware_disk
,vcpus => \&_change_hardware_vcpus
,memory => \&_change_hardware_memory
,network => \&_change_hardware_network
);
##################################################
......@@ -1325,7 +1327,8 @@ sub get_info {
$info->{max_mem} = $mem_xml if $mem_xml ne $info->{max_mem};
$info->{cpu_time} = $info->{cpuTime};
$info->{n_virt_cpu} = $info->{nVirtCpu};
$info->{n_virt_cpu} = $info->{nrVirtCpu};
confess Dumper($info) if !$info->{n_virt_cpu};
$info->{ip} = $self->ip() if $self->is_active();
lock_keys(%$info);
......@@ -1534,6 +1537,65 @@ sub rename_volumes {
}
}
=cut
=head2 spinoff_volumes
Makes volumes indpendent from base
=cut
sub spinoff_volumes {
my $self = shift;
$self->_do_force_shutdown() if $self->is_active;
for my $volume ($self->list_volumes_info ) {
# $volume->spinoff;
}
}
sub _old_spinoff_volumes {
my $self = shift;
$self->_do_force_shutdown() if $self->is_active;
for my $volume ($self->list_disks) {
confess "ERROR: Domain ".$self->name
." volume '$volume' does not exists"
if ! -e $volume;
#TODO check mktemp or something
my $volume_tmp = "$volume.$$.tmp";
unlink($volume_tmp) or die "ERROR $! removing $volume.tmp"
if -e $volume_tmp;
my @cmd = ('qemu-img'
,'convert'
,'-O','qcow2'
,$volume
,$volume_tmp
);
my ($in, $out, $err);
run3(\@cmd,\$in,\$out,\$err);
warn $out if $out;
warn $err if $err;
die "ERROR: Temporary output file $volume_tmp not created at "
.join(" ",@cmd)
.($out or '')
.($err or '')
."\n"
if (! -e $volume_tmp );
copy($volume_tmp,$volume) or die "$! $volume_tmp -> $volume";
unlink($volume_tmp) or die "ERROR $! removing $volume_tmp";
}
}
sub _set_spice_ip($self, $set_password, $ip=undef) {
my $doc = XML::LibXML->load_xml(string
......@@ -2154,6 +2216,36 @@ sub _change_hardware_disk_bus($self, $index, $bus) {
}
sub _change_hardware_vcpus($self, $index, $data) {
confess "Error: I don't understand vcpus index = '$index' , only 0"
if defined $index && $index != 0;
my $n_virt_cpu = delete $data->{n_virt_cpu};
confess "Error: Unkown args ".Dumper($data) if keys %$data;
if ($self->domain->is_active) {
$self->domain->set_vcpus($n_virt_cpu, Sys::Virt::Domain::VCPU_GUEST);
}
my $doc = XML::LibXML->load_xml(string => $self->xml_description);
my ($vcpus) = ($doc->findnodes('/domain/vcpu/text()'));
$vcpus->setData($n_virt_cpu);
$self->_post_change_hardware($doc);
}
sub _change_hardware_memory($self, $index, $data) {
confess "Error: I don't understand memory index = '$index' , only 0"
if defined $index && $index != 0;
my $memory = delete $data->{memory};
my $max_mem= delete $data->{max_mem};
confess "Error: Unkown args ".Dumper($data) if keys %$data;
$self->set_memory($memory) if defined $memory;
$self->set_max_mem($max_mem) if defined $max_mem;
}
sub _change_hardware_network($self, $index, $data) {
confess if !defined $index;
......
......@@ -32,6 +32,8 @@ has 'domain' => (
our %CHANGE_HARDWARE_SUB = (
disk => \&_change_hardware_disk
,vcpus => \&_change_hardware_vcpus
,memory => \&_change_hardware_memory
);
our $CONVERT = `which convert`;
......@@ -736,6 +738,28 @@ sub _change_hardware_disk($self, $index, $data_new) {
$self->_vm->write_file($file, Dump($data));
}
sub _change_hardware_vcpus($self, $index, $data) {
my $n = delete $data->{n_virt_cpu};
confess "Error: unknown args ".Dumper($data) if keys %$data;
my $info = $self->_value('info');
$info->{n_virt_cpu} = $n;
$self->_store(info => $info);
}
sub _change_hardware_memory($self, $index, $data) {
my $memory = delete $data->{memory};
my $max_mem = delete $data->{max_mem};
confess "Error: unknown args ".Dumper($data) if keys %$data;
my $info = $self->_value('info');
$info->{memory} = $memory if defined $memory;
$info->{max_mem} = $max_mem if defined $max_mem;
$self->_store(info => $info);
}
sub change_hardware($self, $hardware, $index, $data) {
my $sub = $CHANGE_HARDWARE_SUB{$hardware};
return $sub->($self, $index, $data) if $sub;
......
......@@ -93,9 +93,7 @@ our %VALID_ARG = (
,change_owner => {uid => 1, id_domain => 1}
,add_hardware => {uid => 1, id_domain => 1, name => 1, number => 2, data => 2 }
,remove_hardware => {uid => 1, id_domain => 1, name => 1, index => 1}
,change_hardware => {uid => 1, id_domain => 1, hardware => 1, index => 1, data => 1 }
,change_max_memory => {uid => 1, id_domain => 1, ram => 1}
,change_curr_memory => {uid => 1, id_domain => 1, ram => 1}
,change_hardware => {uid => 1, id_domain => 1, hardware => 1, index => 2, data => 1 }
,enforce_limits => { timeout => 2, _force => 2 }
,refresh_machine => { id_domain => 1, uid => 1 }
,rebase => { uid => 1, id_base => 1, id_domain => 1 }
......@@ -129,7 +127,6 @@ our %CMD_SEND_MESSAGE = map { $_ => 1 }
set_base_vm remove_base_vm
domain_autostart hibernate hybernate
change_owner
change_max_memory change_curr_memory
add_hardware remove_hardware set_driver change_hardware
expose remove_expose
set_base_vm
......
......@@ -249,6 +249,15 @@
if (typeof $scope.new_name == 'undefined' ) {
$scope.new_name=$scope.showmachine.name+"-2";
$scope.validate_new_name($scope.showmachine.name);
$scope.new_n_virt_cpu= $scope.showmachine.n_virt_cpu;
$scope.new_memory = ($scope.showmachine.memory / 1024);
$scope.new_max_mem = ($scope.showmachine.max_mem / 1024);
$scope.new_run_timeout = ($scope.showmachine.run_timeout / 60);
if (!$scope.new_run_timeout) $scope.new_run_timeout = undefined;
$scope.new_volatile_clones = $scope.showmachine.volatile_clones;
$scope.new_autostart = $scope.showmachine.autostart;
}
$scope.init_domain_access();
$scope.init_ldap_access();
......@@ -256,6 +265,7 @@
list_interfaces();
$scope.hardware_types = Object.keys(response.data.hardware);
$scope.copy_ram = $scope.showmachine.max_mem / 1024 / 1024;
if (is_admin) list_users();
});
};
......@@ -368,12 +378,24 @@
if (! value) {
value_show = false;
}
if ($scope.pending_request && $scope.pending_request.status == 'done' ) {
$scope.pending_request = undefined;
}
$http.get("/machine/set/"+$scope.showmachine.id+"/"+field+"/"+value);
};
$scope.set = function(field) {
if ($scope.pending_request && $scope.pending_request.status == 'done' ) {
$scope.pending_request = undefined;
}
$http.get("/machine/set/"+$scope.showmachine.id+"/"+field+"/"+$scope.showmachine[field]);
};
$scope.set_value = function(field,value) {
if ($scope.pending_request && $scope.pending_request.status == 'done' ) {
$scope.pending_request = undefined;
}
$http.get("/machine/set/"+$scope.showmachine.id+"/"+field+"/"+value);
};
$scope.set_public = function(machineId, value) {
if (value) value=1;
else value=0;
......@@ -627,6 +649,17 @@
}
});
};
list_users= function() {
$http.get('/list_users.json')
.then(function(response) {
$scope.list_users=response.data;
for (var i = 0; i < response.data.length; i++) {
if (response.data[i].id == $scope.showmachine.id_owner) {
$scope.new_owner = response.data[i];
}
}
});
}
$scope.rebase= function() {
$scope.req_new_base = $scope.new_base;
$http.post('/request/rebase/'
......@@ -657,9 +690,17 @@
};
$scope.request = function(request, args) {
$scope.pending_request = undefined;
$http.post('/request/'+request+'/'
,JSON.stringify(args)
).then(function(response) {
if (! response.data.request ) {
$scope.pending_request = {
'status': 'done'
,'error': response.data.error
};
return;
}
id_request = response.data.request;
subscribe_request(id_request, function(data) {
$scope.$apply(function () {
......
......@@ -734,12 +734,15 @@ get '/machine/public/#id/#value' => sub {
};
get '/machine/set/#id/#field/#value' => sub {
my %privileged = map { $_ => 1 } qw(timeout id_owner);
my $c = shift;
my $id = $c->stash('id');
my $field = $c->stash('field');
my $value = $c->stash('value');
return access_denied($c) if !$USER->can_manage_machine($c->stash('id'));
return access_denied($c) if $privileged{$field} && !$USER->is_admin;
my $domain = Ravada::Front::Domain->open($id) or die "Unkown domain $id";
$USER->send_message("Setting $field to $value in ".$domain->name)
......@@ -1058,16 +1061,27 @@ get '/machine/set_access/(#id_domain)/(#id_access)/(#allowed)/(#last)' => sub {
post '/request/(:name)/' => sub {
my $c = shift;
my $name = $c->stash('name');
my $args = decode_json($c->req->body);
confess "Error: uid should not be provided".Dumper($args)
if exists $args->{uid};
my $req = Ravada::Request->new_request(
$c->stash('name')
,uid => $USER->id
,%$args
);
for (qw(remote_ip uid)) {
confess "Error: $_ should not be provided".Dumper($args)
if exists $args->{$_};
}
if ($name eq 'start_clones') {
$args->{remote_ip} = _remote_ip($c);
}
my $req;
eval {
$req = Ravada::Request->new_request(
$name
,uid => $USER->id
,%$args
);
};
return $c->render(json => { ok => 0, error => $@ }) if !$req;
return $c->render(json => { ok => 1, request => $req->id });
};
......@@ -1234,6 +1248,12 @@ post '/machine/hardware/add' => sub {
);
return $c->render( json => { request => $req->id } );
};
get '/list_users.json' => sub($c) {
return access_denied($c) if !$USER->is_admin;
return $c->render(json => $RAVADA->list_users );
};
###################################################
## user_settings
......@@ -1922,56 +1942,12 @@ sub manage_machine {
$c->stash(domain => $domain);
$c->stash(USER => $USER);
$c->stash(list_users => $RAVADA->list_users);
$c->stash(ldap_attributes_cn => ( $c->session('ldap_attributes_cn') or $USER->name or ''));
$c->stash( ram => int( $domain->get_info()->{max_mem} / 1024 ));
$c->stash( cram => int( $domain->get_info()->{memory} / 1024 ));
$c->stash( needs_restart => $domain->needs_restart );
my @messages;
my @errors;
my @reqs = ();
if ($c->param("ram") && ($domain->get_info())->{max_mem}!=$c->param("ram")*1024 && $USER->is_admin){
$c->stash( needs_restart => 1 ) if $domain->is_active;
my $req_mem = Ravada::Request->change_max_memory(uid => $USER->id, id_domain => $domain->id, ram => $c->param("ram")*1024);
push @reqs,($req_mem);
$c->stash(ram => $c->param('ram'));
push @messages,("MAx memory changed from "
.int($domain->get_info()->{max_mem}/1024)." to ".$c->param('ram'));
}
if ($c->param("cram") && int($domain->get_info()->{memory} / 1024) !=$c->param("cram")){
$c->stash(cram => $c->param('cram'));
if ($c->param("cram")*1024<=($domain->get_info())->{max_mem}){
my $req_mem = Ravada::Request->change_curr_memory(uid => $USER->id, id_domain => $domain->id, ram => $c->param("cram")*1024);
push @reqs,($req_mem);
push @messages,("Current memory changed from "
.int($domain->get_info()->{memory} / 1024)." to ".$c->param('cram'));
} else {
push @errors, ('Current memory must be less than max memory');
}
}
if (defined $c->param("start-clones") && $c->param("start-clones") ne "") {
my $req = Ravada::Request->start_clones(
id_domain => $domain->id,
,uid => $USER->id
,remote_ip => _remote_ip($c)
);
}
my $req;
$req = Ravada::Request->shutdown_domain(id_domain => $domain->id, uid => $USER->id)
if $c->param('shutdown') && $domain->is_active;
$req = Ravada::Request->start_domain(
uid => $USER->id
, name => $domain->name
, remote_ip => _remote_ip($c)
) if $c->param('start') && !$domain->is_active;
_enable_buttons($c, $domain);
my %cur_driver;
for my $driver (qw(sound video network image jpeg zlib playback streaming)) {
next if !$domain->drivers($driver);
......@@ -2006,25 +1982,12 @@ sub manage_machine {
push @reqs, ($req3);
}
}
for my $option (qw(autostart description run_timeout volatile_clones id_owner)) {
for my $option (qw(description)) {
next if $option eq 'description' && !$c->param('btn_description');
next if $option ne 'description' && !$c->param('btn_options');
return access_denied($c)
if $option =~ /^(id_owner|run_timeout)$/ && !$USER->is_admin;
my $old_value = $domain->_data($option);
my $value = $c->param($option);
$value= 0 if $option =~ /volatile_clones|autostart/ && !$value;
if ( $option eq 'run_timeout' ) {
$value = 0 if !$value;
$value *= 60;
}
next if defined $domain->_data($option) && defined $value
&& $domain->_data($option) eq $value;
......@@ -2035,6 +1998,7 @@ sub manage_machine {
$option_txt =~ s/_/ /g;
push @messages,("\u$option_txt changed.");
}
$c->stash(messages => \@messages);
$c->stash(errors => \@errors);
return $c->render(template => 'main/settings_machine'
......
......@@ -100,16 +100,18 @@ sub test_req_change_mem($vm) {
my $new_max_mem = int($max_mem * 1.5 ) + 1;
my $new_mem = int($mem * 1.5 ) + 1;
my $req1 = Ravada::Request->change_max_memory(
my $req1 = Ravada::Request->change_hardware(
uid => user_admin->id
,id_domain => $domain->id
,ram => $new_max_mem
,hardware => 'memory'
,data => { max_mem => $new_max_mem }
);
my $req2 = Ravada::Request->change_curr_memory(
my $req2 = Ravada::Request->change_hardware(
uid => user_admin->id
,id_domain => $domain->id
,ram => $new_max_mem
,hardware => 'memory'
,data => { memory => $new_mem }
);
wait_request(check_error => 1, background => 0);
......
......@@ -266,12 +266,19 @@ sub _mount_qcow($vm, $vol) {
run3(\@cmd, \$in, \$out, \$err);
die join(" ",@cmd)." : $? $err" if $?;
}
$vm->run_command($QEMU_NBD,"-d", $DEV_NBD);
for ( 1 .. 10 ) {
($out,$err) = $vm->run_command($QEMU_NBD,"-d", $DEV_NBD);
last if !$err;
sleep 1;
diag($err);
}
confess "qemu-nbd -d $DEV_NBD\n?:$?\n$out\n$err" if $? || $err;
for ( 1 .. 10 ) {
($out, $err) = $vm->run_command($QEMU_NBD,"-c",$DEV_NBD, $vol);
last if !$err || $err !~ /(NBD socket|Unexpected end)/;
sleep 1;
diag("$_ $err");
($out,$err) = $vm->run_command($QEMU_NBD,"-d", $DEV_NBD);
}
confess "qemu-nbd -c $DEV_NBD $vol\n?:$?\n$out\n$err" if $? || $err;
_create_part($DEV_NBD);
......@@ -434,6 +441,9 @@ for my $vm_name (vm_names() ) {
test_prepare_remove($vm);
if (!$ENV{TEST_LONG} ) {
test_rebase_with_vols($vm,1,1,1,0,0,1);
}
for my $swap0 ( 0 , 1 ) {
for my $data0 ( 0 , 1 ) {
for my $with_cd0 ( 0 , 1 ) {
......@@ -442,8 +452,10 @@ for my $vm_name (vm_names() ) {
for my $swap1 ( 0 , 1 ) {
for my $with_cd1 ( 0 , 1 ) {
for my $data1 ( 0 , 1 ) {
if ($ENV{TEST_LONG}) {
test_rebase_with_vols($vm, $swap0, $data0, $with_cd0
, $swap1, $data1, $with_cd1);
}
}
}
......
......@@ -9,7 +9,7 @@
<div class="page-header">
<div class="card"
ng-controller="singleMachinePage"
ng-init="init(<%= $id %>, '<%= url_for('ws_subscribe')->to_abs %>' )">
ng-init="init(<%= $id %>, '<%= url_for('ws_subscribe')->to_abs %>',<%= $USER->is_admin %> )">
<div class="card-header">
% if ($domain->is_base) {
<h2><%=l 'Virtual Machine' %>
......
<div class="panel-body">
<div class="form-group">
<form class="form-inline" action="<%= $action %>" method="post">
<div class="row">
<input type="submit" id="start-clones" name="start-clones" value="<%=l 'Start all clones' %>"></input>
<button class="btn btn-primary" id="start-clones" name="start-clones"
ng-click="request('start_clones',{ 'id_domain': showmachine.id })"
>
<%=l 'Start all clones' %>
</button>
</div>
</form>
</div>
<div class="row" ng-show="pending_request">
%= include "/main/pending_request"
</div>
</div>
......@@ -13,70 +13,93 @@
<div class="col-lg-3 mt-2">
<label class="control-label" for="ram"><%=l 'Max memory (MB)' %></label>
</div>
<div class="col-lg-3">
<input class="form-control" type="text" name="ram" size="5" value="<%= $ram %>">
<div class="col-lg-2">
<input class="form-control" type="text" name="ram" size="5"
ng-blur="request('change_hardware',{
'id_domain': showmachine.id
,'hardware': 'memory'
,'data': { 'max_mem': new_max_mem*1024}
})"
ng-model="new_max_mem">
</div>
% if ( $needs_restart) {
<div class="col-lg-4" ng-show="showmachine.needs_restart">
<%=l 'The changes will apply on next restart' %>
% }
<!--<div class="col-md-6">
<b>MB</b> of <%= int($domain->get_info->{max_mem}/1024) %>
<%=l 'MB available' %>.
</div>-->
</div>
</div>
<div class="row">
<div class="col-lg-3 mt-2">
<label class="control-label" for="cram"><%=l 'Current memory (MB)' %></label>
</div>
<div class="col-lg-2">
<input class="form-control" type="text" name="cram" size="5" value="<%= $cram %>">
<input class="form-control" type="text" name="cram" size="5"
ng-blur="request('change_hardware',{