1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_copy = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf) = @_;
65 PVE
::QemuServer
::foreach_drive
($conf, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
72 return if !$volid || $volid eq 'none';
75 if ($volid eq 'cdrom') {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
82 die "unable to copy arbitrary files\n" if !$sid;
83 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
88 # Note: $pool is only needed when creating a VM, because pool permissions
89 # are automatically inherited if VM already exists inside a pool.
90 my $create_disks = sub {
91 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
96 PVE
::QemuServer
::foreach_drive
($settings, sub {
99 my $volid = $disk->{file
};
101 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
102 delete $disk->{size
};
103 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
104 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
105 my ($storeid, $size) = ($2 || $default_storage, $3);
106 die "no storage ID specified (and no default storage)\n" if !$storeid;
107 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
108 my $fmt = $disk->{format
} || $defformat;
109 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
110 $fmt, undef, $size*1024*1024);
111 $disk->{file
} = $volid;
112 $disk->{size
} = $size*1024*1024*1024;
113 push @$vollist, $volid;
114 delete $disk->{format
}; # no longer needed
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
120 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
122 my $foundvolid = undef;
125 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
126 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
128 PVE
::Storage
::foreach_volid
($dl, sub {
130 if($volumeid eq $volid) {
137 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
139 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
140 $disk->{size
} = $size;
141 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
145 # free allocated images on error
147 syslog
('err', "VM $vmid creating disks failed");
148 foreach my $volid (@$vollist) {
149 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
155 # modify vm config if everything went well
156 foreach my $ds (keys %$res) {
157 $conf->{$ds} = $res->{$ds};
163 my $check_vm_modify_config_perm = sub {
164 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
166 return 1 if $authuser eq 'root@pam';
168 foreach my $opt (@$key_list) {
169 # disk checks need to be done somewhere else
170 next if PVE
::QemuServer
::valid_drivename
($opt);
172 if ($opt eq 'sockets' || $opt eq 'cores' ||
173 $opt eq 'cpu' || $opt eq 'smp' ||
174 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
175 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
176 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
177 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
178 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
179 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
180 } elsif ($opt eq 'args' || $opt eq 'lock') {
181 die "only root can set '$opt' config\n";
182 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
183 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
184 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
185 } elsif ($opt =~ m/^net\d+$/) {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
195 __PACKAGE__-
>register_method({
199 description
=> "Virtual machine index (per node).",
201 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
205 protected
=> 1, # qemu pid files are only readable by root
207 additionalProperties
=> 0,
209 node
=> get_standard_option
('pve-node'),
218 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
223 my $rpcenv = PVE
::RPCEnvironment
::get
();
224 my $authuser = $rpcenv->get_user();
226 my $vmstatus = PVE
::QemuServer
::vmstatus
();
229 foreach my $vmid (keys %$vmstatus) {
230 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
232 my $data = $vmstatus->{$vmid};
233 $data->{vmid
} = $vmid;
240 __PACKAGE__-
>register_method({
244 description
=> "Create or restore a virtual machine.",
246 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
248 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
249 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
255 additionalProperties
=> 0,
256 properties
=> PVE
::QemuServer
::json_config_properties
(
258 node
=> get_standard_option
('pve-node'),
259 vmid
=> get_standard_option
('pve-vmid'),
261 description
=> "The backup file.",
266 storage
=> get_standard_option
('pve-storage-id', {
267 description
=> "Default storage.",
273 description
=> "Allow to overwrite existing VM.",
274 requires
=> 'archive',
279 description
=> "Assign a unique random ethernet address.",
280 requires
=> 'archive',
284 type
=> 'string', format
=> 'pve-poolid',
285 description
=> "Add the VM to the specified pool.",
295 my $rpcenv = PVE
::RPCEnvironment
::get
();
297 my $authuser = $rpcenv->get_user();
299 my $node = extract_param
($param, 'node');
301 my $vmid = extract_param
($param, 'vmid');
303 my $archive = extract_param
($param, 'archive');
305 my $storage = extract_param
($param, 'storage');
307 my $force = extract_param
($param, 'force');
309 my $unique = extract_param
($param, 'unique');
311 my $pool = extract_param
($param, 'pool');
313 my $filename = PVE
::QemuServer
::config_file
($vmid);
315 my $storecfg = PVE
::Storage
::config
();
317 PVE
::Cluster
::check_cfs_quorum
();
319 if (defined($pool)) {
320 $rpcenv->check_pool_exist($pool);
323 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
324 if defined($storage);
327 &$resolve_cdrom_alias($param);
329 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
331 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
333 foreach my $opt (keys %$param) {
334 if (PVE
::QemuServer
::valid_drivename
($opt)) {
335 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
336 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
338 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
339 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
343 PVE
::QemuServer
::add_random_macs
($param);
345 my $keystr = join(' ', keys %$param);
346 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
348 if ($archive eq '-') {
349 die "pipe requires cli environment\n"
350 if $rpcenv->{type
} ne 'cli';
352 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
354 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
355 if PVE
::Storage
::parse_volume_id
($archive, 1);
357 die "can't find archive file '$archive'\n" if !($path && -f
$path);
362 my $addVMtoPoolFn = sub {
363 my $usercfg = cfs_read_file
("user.cfg");
364 if (my $data = $usercfg->{pools
}->{$pool}) {
365 $data->{vms
}->{$vmid} = 1;
366 $usercfg->{vms
}->{$vmid} = $pool;
367 cfs_write_file
("user.cfg", $usercfg);
371 my $restorefn = sub {
373 # fixme: this test does not work if VM exists on other node!
375 die "unable to restore vm $vmid: config file already exists\n"
378 die "unable to restore vm $vmid: vm is running\n"
379 if PVE
::QemuServer
::check_running
($vmid);
383 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
386 unique
=> $unique });
388 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
391 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
397 die "unable to create vm $vmid: config file already exists\n"
408 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
410 # try to be smart about bootdisk
411 my @disks = PVE
::QemuServer
::disknames
();
413 foreach my $ds (reverse @disks) {
414 next if !$conf->{$ds};
415 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
416 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
420 if (!$conf->{bootdisk
} && $firstdisk) {
421 $conf->{bootdisk
} = $firstdisk;
424 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
430 foreach my $volid (@$vollist) {
431 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
434 die "create failed - $err";
437 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
440 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
443 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
446 __PACKAGE__-
>register_method({
451 description
=> "Directory index",
456 additionalProperties
=> 0,
458 node
=> get_standard_option
('pve-node'),
459 vmid
=> get_standard_option
('pve-vmid'),
467 subdir
=> { type
=> 'string' },
470 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
476 { subdir
=> 'config' },
477 { subdir
=> 'status' },
478 { subdir
=> 'unlink' },
479 { subdir
=> 'vncproxy' },
480 { subdir
=> 'migrate' },
481 { subdir
=> 'resize' },
483 { subdir
=> 'rrddata' },
484 { subdir
=> 'monitor' },
485 { subdir
=> 'snapshot' },
491 __PACKAGE__-
>register_method({
493 path
=> '{vmid}/rrd',
495 protected
=> 1, # fixme: can we avoid that?
497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
499 description
=> "Read VM RRD statistics (returns PNG)",
501 additionalProperties
=> 0,
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid'),
506 description
=> "Specify the time frame you are interested in.",
508 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
511 description
=> "The list of datasources you want to display.",
512 type
=> 'string', format
=> 'pve-configid-list',
515 description
=> "The RRD consolidation function",
517 enum
=> [ 'AVERAGE', 'MAX' ],
525 filename
=> { type
=> 'string' },
531 return PVE
::Cluster
::create_rrd_graph
(
532 "pve2-vm/$param->{vmid}", $param->{timeframe
},
533 $param->{ds
}, $param->{cf
});
537 __PACKAGE__-
>register_method({
539 path
=> '{vmid}/rrddata',
541 protected
=> 1, # fixme: can we avoid that?
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
545 description
=> "Read VM RRD statistics",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "Specify the time frame you are interested in.",
554 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
557 description
=> "The RRD consolidation function",
559 enum
=> [ 'AVERAGE', 'MAX' ],
574 return PVE
::Cluster
::create_rrd_data
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
579 __PACKAGE__-
>register_method({
581 path
=> '{vmid}/config',
584 description
=> "Get virtual machine configuration.",
586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
589 additionalProperties
=> 0,
591 node
=> get_standard_option
('pve-node'),
592 vmid
=> get_standard_option
('pve-vmid'),
600 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
607 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
609 delete $conf->{snapshots
};
614 my $vm_is_volid_owner = sub {
615 my ($storecfg, $vmid, $volid) =@_;
617 if ($volid !~ m
|^/|) {
619 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
620 if ($owner && ($owner == $vmid)) {
628 my $test_deallocate_drive = sub {
629 my ($storecfg, $vmid, $key, $drive, $force) = @_;
631 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
632 my $volid = $drive->{file
};
633 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
634 if ($force || $key =~ m/^unused/) {
635 my $sid = PVE
::Storage
::parse_volume_id
($volid);
644 my $delete_drive = sub {
645 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
647 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
648 my $volid = $drive->{file
};
649 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
650 if ($force || $key =~ m/^unused/) {
651 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
654 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
659 delete $conf->{$key};
662 my $vmconfig_delete_option = sub {
663 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
665 return if !defined($conf->{$opt});
667 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
670 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
672 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
673 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
674 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
678 my $unplugwarning = "";
679 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
680 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
681 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
682 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
683 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
684 $unplugwarning = "<br>verify that your guest support acpi hotplug";
687 if($opt eq 'tablet'){
688 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
690 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
694 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
695 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
697 delete $conf->{$opt};
700 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
703 my $safe_num_ne = sub {
706 return 0 if !defined($a) && !defined($b);
707 return 1 if !defined($a);
708 return 1 if !defined($b);
713 my $vmconfig_update_disk = sub {
714 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
716 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
718 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
719 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
721 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
726 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
728 my $media = $drive->{media
} || 'disk';
729 my $oldmedia = $old_drive->{media
} || 'disk';
730 die "unable to change media type\n" if $media ne $oldmedia;
732 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
733 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
735 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
736 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
739 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
740 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
741 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
742 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
743 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
744 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
745 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
746 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
747 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
748 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
753 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
754 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
756 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
757 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
759 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
761 if (PVE
::QemuServer
::check_running
($vmid)) {
762 if ($drive->{file
} eq 'none') {
763 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
765 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
766 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
767 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
771 } else { # hotplug new disks
773 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
777 my $vmconfig_update_net = sub {
778 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
780 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
781 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
782 my $newnet = PVE
::QemuServer
::parse_net
($value);
784 if($oldnet->{model
} ne $newnet->{model
}){
785 #if model change, we try to hot-unplug
786 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
789 if($newnet->{bridge
} && $oldnet->{bridge
}){
790 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
792 if($newnet->{rate
} ne $oldnet->{rate
}){
793 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
796 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
797 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
798 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
802 #if bridge/nat mode change, we try to hot-unplug
803 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
808 $conf->{$opt} = $value;
809 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
812 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
814 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
817 my $vm_config_perm_list = [
827 __PACKAGE__-
>register_method({
829 path
=> '{vmid}/config',
833 description
=> "Set virtual machine options.",
835 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
838 additionalProperties
=> 0,
839 properties
=> PVE
::QemuServer
::json_config_properties
(
841 node
=> get_standard_option
('pve-node'),
842 vmid
=> get_standard_option
('pve-vmid'),
843 skiplock
=> get_standard_option
('skiplock'),
845 type
=> 'string', format
=> 'pve-configid-list',
846 description
=> "A list of settings you want to delete.",
851 description
=> $opt_force_description,
853 requires
=> 'delete',
857 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
863 returns
=> { type
=> 'null'},
867 my $rpcenv = PVE
::RPCEnvironment
::get
();
869 my $authuser = $rpcenv->get_user();
871 my $node = extract_param
($param, 'node');
873 my $vmid = extract_param
($param, 'vmid');
875 my $digest = extract_param
($param, 'digest');
877 my @paramarr = (); # used for log message
878 foreach my $key (keys %$param) {
879 push @paramarr, "-$key", $param->{$key};
882 my $skiplock = extract_param
($param, 'skiplock');
883 raise_param_exc
({ skiplock
=> "Only root may use this option." })
884 if $skiplock && $authuser ne 'root@pam';
886 my $delete_str = extract_param
($param, 'delete');
888 my $force = extract_param
($param, 'force');
890 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
892 my $storecfg = PVE
::Storage
::config
();
894 my $defaults = PVE
::QemuServer
::load_defaults
();
896 &$resolve_cdrom_alias($param);
898 # now try to verify all parameters
901 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
902 $opt = 'ide2' if $opt eq 'cdrom';
903 raise_param_exc
({ delete => "you can't use '-$opt' and " .
904 "-delete $opt' at the same time" })
905 if defined($param->{$opt});
907 if (!PVE
::QemuServer
::option_exists
($opt)) {
908 raise_param_exc
({ delete => "unknown option '$opt'" });
914 foreach my $opt (keys %$param) {
915 if (PVE
::QemuServer
::valid_drivename
($opt)) {
917 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
918 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
919 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
920 } elsif ($opt =~ m/^net(\d+)$/) {
922 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
923 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
927 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
929 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
931 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
935 my $conf = PVE
::QemuServer
::load_config
($vmid);
937 die "checksum missmatch (file change by other user?)\n"
938 if $digest && $digest ne $conf->{digest
};
940 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
942 if ($param->{memory
} || defined($param->{balloon
})) {
943 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
944 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
946 die "balloon value too large (must be smaller than assigned memory)\n"
947 if $balloon > $maxmem;
950 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
952 foreach my $opt (@delete) { # delete
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
957 my $running = PVE
::QemuServer
::check_running
($vmid);
959 foreach my $opt (keys %$param) { # add/change
961 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt}, $force);
970 } elsif ($opt =~ m/^net(\d+)$/) { #nics
972 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
973 $opt, $param->{$opt});
977 if($opt eq 'tablet' && $param->{$opt} == 1){
978 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
979 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
980 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
983 $conf->{$opt} = $param->{$opt};
984 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
988 # allow manual ballooning if shares is set to zero
989 if ($running && defined($param->{balloon
}) &&
990 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
991 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
992 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
997 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1003 __PACKAGE__-
>register_method({
1004 name
=> 'destroy_vm',
1009 description
=> "Destroy the vm (also delete all used/owned volumes).",
1011 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1014 additionalProperties
=> 0,
1016 node
=> get_standard_option
('pve-node'),
1017 vmid
=> get_standard_option
('pve-vmid'),
1018 skiplock
=> get_standard_option
('skiplock'),
1027 my $rpcenv = PVE
::RPCEnvironment
::get
();
1029 my $authuser = $rpcenv->get_user();
1031 my $vmid = $param->{vmid
};
1033 my $skiplock = $param->{skiplock
};
1034 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1035 if $skiplock && $authuser ne 'root@pam';
1038 my $conf = PVE
::QemuServer
::load_config
($vmid);
1040 my $storecfg = PVE
::Storage
::config
();
1042 my $delVMfromPoolFn = sub {
1043 my $usercfg = cfs_read_file
("user.cfg");
1044 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1045 if (my $data = $usercfg->{pools
}->{$pool}) {
1046 delete $data->{vms
}->{$vmid};
1047 delete $usercfg->{vms
}->{$vmid};
1048 cfs_write_file
("user.cfg", $usercfg);
1056 syslog
('info', "destroy VM $vmid: $upid\n");
1058 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1060 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1063 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1066 __PACKAGE__-
>register_method({
1068 path
=> '{vmid}/unlink',
1072 description
=> "Unlink/delete disk images.",
1074 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1077 additionalProperties
=> 0,
1079 node
=> get_standard_option
('pve-node'),
1080 vmid
=> get_standard_option
('pve-vmid'),
1082 type
=> 'string', format
=> 'pve-configid-list',
1083 description
=> "A list of disk IDs you want to delete.",
1087 description
=> $opt_force_description,
1092 returns
=> { type
=> 'null'},
1096 $param->{delete} = extract_param
($param, 'idlist');
1098 __PACKAGE__-
>update_vm($param);
1105 __PACKAGE__-
>register_method({
1107 path
=> '{vmid}/vncproxy',
1111 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1113 description
=> "Creates a TCP VNC proxy connections.",
1115 additionalProperties
=> 0,
1117 node
=> get_standard_option
('pve-node'),
1118 vmid
=> get_standard_option
('pve-vmid'),
1122 additionalProperties
=> 0,
1124 user
=> { type
=> 'string' },
1125 ticket
=> { type
=> 'string' },
1126 cert
=> { type
=> 'string' },
1127 port
=> { type
=> 'integer' },
1128 upid
=> { type
=> 'string' },
1134 my $rpcenv = PVE
::RPCEnvironment
::get
();
1136 my $authuser = $rpcenv->get_user();
1138 my $vmid = $param->{vmid
};
1139 my $node = $param->{node
};
1141 my $authpath = "/vms/$vmid";
1143 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1145 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1148 my $port = PVE
::Tools
::next_vnc_port
();
1152 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1153 $remip = PVE
::Cluster
::remote_node_ip
($node);
1156 # NOTE: kvm VNC traffic is already TLS encrypted
1157 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1164 syslog
('info', "starting vnc proxy $upid\n");
1166 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1168 my $qmstr = join(' ', @$qmcmd);
1170 # also redirect stderr (else we get RFB protocol errors)
1171 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1173 PVE
::Tools
::run_command
($cmd);
1178 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1180 PVE
::Tools
::wait_for_vnc_port
($port);
1191 __PACKAGE__-
>register_method({
1193 path
=> '{vmid}/status',
1196 description
=> "Directory index",
1201 additionalProperties
=> 0,
1203 node
=> get_standard_option
('pve-node'),
1204 vmid
=> get_standard_option
('pve-vmid'),
1212 subdir
=> { type
=> 'string' },
1215 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1221 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1224 { subdir
=> 'current' },
1225 { subdir
=> 'start' },
1226 { subdir
=> 'stop' },
1232 my $vm_is_ha_managed = sub {
1235 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1236 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1242 __PACKAGE__-
>register_method({
1243 name
=> 'vm_status',
1244 path
=> '{vmid}/status/current',
1247 protected
=> 1, # qemu pid files are only readable by root
1248 description
=> "Get virtual machine status.",
1250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1253 additionalProperties
=> 0,
1255 node
=> get_standard_option
('pve-node'),
1256 vmid
=> get_standard_option
('pve-vmid'),
1259 returns
=> { type
=> 'object' },
1264 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1266 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1267 my $status = $vmstatus->{$param->{vmid
}};
1269 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1274 __PACKAGE__-
>register_method({
1276 path
=> '{vmid}/status/start',
1280 description
=> "Start virtual machine.",
1282 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1285 additionalProperties
=> 0,
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid'),
1289 skiplock
=> get_standard_option
('skiplock'),
1290 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1291 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1301 my $rpcenv = PVE
::RPCEnvironment
::get
();
1303 my $authuser = $rpcenv->get_user();
1305 my $node = extract_param
($param, 'node');
1307 my $vmid = extract_param
($param, 'vmid');
1309 my $stateuri = extract_param
($param, 'stateuri');
1310 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1311 if $stateuri && $authuser ne 'root@pam';
1313 my $skiplock = extract_param
($param, 'skiplock');
1314 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1315 if $skiplock && $authuser ne 'root@pam';
1317 my $migratedfrom = extract_param
($param, 'migratedfrom');
1318 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1319 if $migratedfrom && $authuser ne 'root@pam';
1321 my $storecfg = PVE
::Storage
::config
();
1323 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1324 $rpcenv->{type
} ne 'ha') {
1329 my $service = "pvevm:$vmid";
1331 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1333 print "Executing HA start for VM $vmid\n";
1335 PVE
::Tools
::run_command
($cmd);
1340 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1347 syslog
('info', "start VM $vmid: $upid\n");
1349 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1354 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1358 __PACKAGE__-
>register_method({
1360 path
=> '{vmid}/status/stop',
1364 description
=> "Stop virtual machine.",
1366 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1369 additionalProperties
=> 0,
1371 node
=> get_standard_option
('pve-node'),
1372 vmid
=> get_standard_option
('pve-vmid'),
1373 skiplock
=> get_standard_option
('skiplock'),
1374 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1376 description
=> "Wait maximal timeout seconds.",
1382 description
=> "Do not decativate storage volumes.",
1395 my $rpcenv = PVE
::RPCEnvironment
::get
();
1397 my $authuser = $rpcenv->get_user();
1399 my $node = extract_param
($param, 'node');
1401 my $vmid = extract_param
($param, 'vmid');
1403 my $skiplock = extract_param
($param, 'skiplock');
1404 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1405 if $skiplock && $authuser ne 'root@pam';
1407 my $keepActive = extract_param
($param, 'keepActive');
1408 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1409 if $keepActive && $authuser ne 'root@pam';
1411 my $migratedfrom = extract_param
($param, 'migratedfrom');
1412 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1413 if $migratedfrom && $authuser ne 'root@pam';
1416 my $storecfg = PVE
::Storage
::config
();
1418 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1423 my $service = "pvevm:$vmid";
1425 my $cmd = ['clusvcadm', '-d', $service];
1427 print "Executing HA stop for VM $vmid\n";
1429 PVE
::Tools
::run_command
($cmd);
1434 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1440 syslog
('info', "stop VM $vmid: $upid\n");
1442 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1443 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1448 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1452 __PACKAGE__-
>register_method({
1454 path
=> '{vmid}/status/reset',
1458 description
=> "Reset virtual machine.",
1460 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1463 additionalProperties
=> 0,
1465 node
=> get_standard_option
('pve-node'),
1466 vmid
=> get_standard_option
('pve-vmid'),
1467 skiplock
=> get_standard_option
('skiplock'),
1476 my $rpcenv = PVE
::RPCEnvironment
::get
();
1478 my $authuser = $rpcenv->get_user();
1480 my $node = extract_param
($param, 'node');
1482 my $vmid = extract_param
($param, 'vmid');
1484 my $skiplock = extract_param
($param, 'skiplock');
1485 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1486 if $skiplock && $authuser ne 'root@pam';
1488 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1493 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1498 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1501 __PACKAGE__-
>register_method({
1502 name
=> 'vm_shutdown',
1503 path
=> '{vmid}/status/shutdown',
1507 description
=> "Shutdown virtual machine.",
1509 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1512 additionalProperties
=> 0,
1514 node
=> get_standard_option
('pve-node'),
1515 vmid
=> get_standard_option
('pve-vmid'),
1516 skiplock
=> get_standard_option
('skiplock'),
1518 description
=> "Wait maximal timeout seconds.",
1524 description
=> "Make sure the VM stops.",
1530 description
=> "Do not decativate storage volumes.",
1543 my $rpcenv = PVE
::RPCEnvironment
::get
();
1545 my $authuser = $rpcenv->get_user();
1547 my $node = extract_param
($param, 'node');
1549 my $vmid = extract_param
($param, 'vmid');
1551 my $skiplock = extract_param
($param, 'skiplock');
1552 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1553 if $skiplock && $authuser ne 'root@pam';
1555 my $keepActive = extract_param
($param, 'keepActive');
1556 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1557 if $keepActive && $authuser ne 'root@pam';
1559 my $storecfg = PVE
::Storage
::config
();
1564 syslog
('info', "shutdown VM $vmid: $upid\n");
1566 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1567 1, $param->{forceStop
}, $keepActive);
1572 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1575 __PACKAGE__-
>register_method({
1576 name
=> 'vm_suspend',
1577 path
=> '{vmid}/status/suspend',
1581 description
=> "Suspend virtual machine.",
1583 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1586 additionalProperties
=> 0,
1588 node
=> get_standard_option
('pve-node'),
1589 vmid
=> get_standard_option
('pve-vmid'),
1590 skiplock
=> get_standard_option
('skiplock'),
1599 my $rpcenv = PVE
::RPCEnvironment
::get
();
1601 my $authuser = $rpcenv->get_user();
1603 my $node = extract_param
($param, 'node');
1605 my $vmid = extract_param
($param, 'vmid');
1607 my $skiplock = extract_param
($param, 'skiplock');
1608 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1609 if $skiplock && $authuser ne 'root@pam';
1611 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1616 syslog
('info', "suspend VM $vmid: $upid\n");
1618 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1623 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1626 __PACKAGE__-
>register_method({
1627 name
=> 'vm_resume',
1628 path
=> '{vmid}/status/resume',
1632 description
=> "Resume virtual machine.",
1634 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1637 additionalProperties
=> 0,
1639 node
=> get_standard_option
('pve-node'),
1640 vmid
=> get_standard_option
('pve-vmid'),
1641 skiplock
=> get_standard_option
('skiplock'),
1650 my $rpcenv = PVE
::RPCEnvironment
::get
();
1652 my $authuser = $rpcenv->get_user();
1654 my $node = extract_param
($param, 'node');
1656 my $vmid = extract_param
($param, 'vmid');
1658 my $skiplock = extract_param
($param, 'skiplock');
1659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1660 if $skiplock && $authuser ne 'root@pam';
1662 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1667 syslog
('info', "resume VM $vmid: $upid\n");
1669 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1674 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1677 __PACKAGE__-
>register_method({
1678 name
=> 'vm_sendkey',
1679 path
=> '{vmid}/sendkey',
1683 description
=> "Send key event to virtual machine.",
1685 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid'),
1692 skiplock
=> get_standard_option
('skiplock'),
1694 description
=> "The key (qemu monitor encoding).",
1699 returns
=> { type
=> 'null'},
1703 my $rpcenv = PVE
::RPCEnvironment
::get
();
1705 my $authuser = $rpcenv->get_user();
1707 my $node = extract_param
($param, 'node');
1709 my $vmid = extract_param
($param, 'vmid');
1711 my $skiplock = extract_param
($param, 'skiplock');
1712 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1713 if $skiplock && $authuser ne 'root@pam';
1715 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1720 __PACKAGE__-
>register_method({
1721 name
=> 'vm_feature',
1722 path
=> '{vmid}/feature',
1726 description
=> "Check if feature for virtual machine is available.",
1728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1731 additionalProperties
=> 0,
1733 node
=> get_standard_option
('pve-node'),
1734 vmid
=> get_standard_option
('pve-vmid'),
1736 description
=> "Feature to check.",
1738 enum
=> [ 'snapshot', 'clone' ],
1740 snapname
=> get_standard_option
('pve-snapshot-name', {
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $snapname = extract_param
($param, 'snapname');
1758 my $feature = extract_param
($param, 'feature');
1760 my $running = PVE
::QemuServer
::check_running
($vmid);
1762 my $conf = PVE
::QemuServer
::load_config
($vmid);
1765 my $snap = $conf->{snapshots
}->{$snapname};
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1769 my $storecfg = PVE
::Storage
::config
();
1771 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1772 my $res = $hasfeature ?
1 : 0 ;
1776 __PACKAGE__-
>register_method({
1778 path
=> '{vmid}/copy',
1782 description
=> "Creat a copy of virtual machine/template.",
1784 description
=> "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1785 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1786 "'Datastore.AllocateSpace' on any used storage.",
1789 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1791 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1792 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1797 additionalProperties
=> 0,
1799 # fixme: add other parameters like name and description?
1800 node
=> get_standard_option
('pve-node'),
1801 vmid
=> get_standard_option
('pve-vmid'),
1802 newid
=> get_standard_option
('pve-vmid', {
1803 description
=> 'VMID for the copy.' }),
1806 type
=> 'string', format
=> 'pve-poolid',
1807 description
=> "Add the new VM to the specified pool.",
1812 description
=> "Create a full copy of all disk. This is always done when " .
1813 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1824 my $rpcenv = PVE
::RPCEnvironment
::get
();
1826 my $authuser = $rpcenv->get_user();
1828 my $node = extract_param
($param, 'node');
1830 my $vmid = extract_param
($param, 'vmid');
1832 my $newid = extract_param
($param, 'newid');
1834 # fixme: update pool after create
1835 my $pool = extract_param
($param, 'pool');
1837 if (defined($pool)) {
1838 $rpcenv->check_pool_exist($pool);
1841 my $storecfg = PVE
::Storage
::config
();
1843 PVE
::Cluster
::check_cfs_quorum
();
1845 # fixme: do early checks - re-check after lock
1847 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1851 # all tests after lock
1852 my $conf = PVE
::QemuServer
::load_config
($vmid);
1854 PVE
::QemuServer
::check_lock
($conf);
1856 my $running = PVE
::QemuServer
::check_running
($vmid);
1858 die "Copy running VM $vmid not implemented\n" if $running;
1860 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf);
1862 # fixme: snapshots??
1864 my $conffile = PVE
::QemuServer
::config_file
($newid);
1866 die "unable to create VM $newid: config file already exists\n"
1869 # create empty/temp config - this fails if VM already exists on other node
1870 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1875 my $newvollist = [];
1878 my $newconf = { lock => 'copy' };
1881 foreach my $opt (keys %$conf) {
1882 my $value = $conf->{$opt};
1884 next if $opt eq 'snapshots'; # do not copy snapshot info
1886 # always change MAC! address
1887 if ($opt =~ m/^net(\d+)$/) {
1888 my $net = PVE
::QemuServer
::parse_net
($value);
1889 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1890 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1891 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1892 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1893 $newconf->{$opt} = $value; # simply copy configuration
1895 $drives->{$opt} = $drive;
1896 push @$vollist, $drive->{file
};
1899 # copy everything else
1900 $newconf->{$opt} = $value;
1904 delete $newconf->{template
};
1906 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1909 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1911 foreach my $opt (keys %$drives) {
1912 my $drive = $drives->{$opt};
1915 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1916 print "clone drive $opt ($drive->{file})\n";
1917 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1919 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1920 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1921 my $fmt = $drive->{format
} || $defformat;
1923 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
1925 print "copy drive $opt ($drive->{file})\n";
1926 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1928 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size);
1931 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
1932 my $disk = { file
=> $newvolid, size
=> $size };
1933 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
1934 push @$newvollist, $newvolid;
1936 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1941 delete $newconf->{lock};
1942 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1947 sleep 1; # some storage like rbd need to wait before release volume - really?
1949 foreach my $volid (@$newvollist) {
1950 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1953 die "copy failed: $err";
1959 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
1962 # Aquire shared lock for $vmid
1963 return PVE
::QemuServer
::lock_config_shared
($vmid, 1, sub {
1964 # Aquire exclusive lock lock for $newid
1965 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
1970 __PACKAGE__-
>register_method({
1971 name
=> 'migrate_vm',
1972 path
=> '{vmid}/migrate',
1976 description
=> "Migrate virtual machine. Creates a new migration task.",
1978 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1981 additionalProperties
=> 0,
1983 node
=> get_standard_option
('pve-node'),
1984 vmid
=> get_standard_option
('pve-vmid'),
1985 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1988 description
=> "Use online/live migration.",
1993 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2000 description
=> "the task ID.",
2005 my $rpcenv = PVE
::RPCEnvironment
::get
();
2007 my $authuser = $rpcenv->get_user();
2009 my $target = extract_param
($param, 'target');
2011 my $localnode = PVE
::INotify
::nodename
();
2012 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2014 PVE
::Cluster
::check_cfs_quorum
();
2016 PVE
::Cluster
::check_node_exists
($target);
2018 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2020 my $vmid = extract_param
($param, 'vmid');
2022 raise_param_exc
({ force
=> "Only root may use this option." })
2023 if $param->{force
} && $authuser ne 'root@pam';
2026 my $conf = PVE
::QemuServer
::load_config
($vmid);
2028 # try to detect errors early
2030 PVE
::QemuServer
::check_lock
($conf);
2032 if (PVE
::QemuServer
::check_running
($vmid)) {
2033 die "cant migrate running VM without --online\n"
2034 if !$param->{online
};
2037 my $storecfg = PVE
::Storage
::config
();
2038 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2040 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2045 my $service = "pvevm:$vmid";
2047 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2049 print "Executing HA migrate for VM $vmid to node $target\n";
2051 PVE
::Tools
::run_command
($cmd);
2056 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2063 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2066 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2071 __PACKAGE__-
>register_method({
2073 path
=> '{vmid}/monitor',
2077 description
=> "Execute Qemu monitor commands.",
2079 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2082 additionalProperties
=> 0,
2084 node
=> get_standard_option
('pve-node'),
2085 vmid
=> get_standard_option
('pve-vmid'),
2088 description
=> "The monitor command.",
2092 returns
=> { type
=> 'string'},
2096 my $vmid = $param->{vmid
};
2098 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2102 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2104 $res = "ERROR: $@" if $@;
2109 __PACKAGE__-
>register_method({
2110 name
=> 'resize_vm',
2111 path
=> '{vmid}/resize',
2115 description
=> "Extend volume size.",
2117 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2120 additionalProperties
=> 0,
2122 node
=> get_standard_option
('pve-node'),
2123 vmid
=> get_standard_option
('pve-vmid'),
2124 skiplock
=> get_standard_option
('skiplock'),
2127 description
=> "The disk you want to resize.",
2128 enum
=> [PVE
::QemuServer
::disknames
()],
2132 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2133 description
=> "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
2137 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2143 returns
=> { type
=> 'null'},
2147 my $rpcenv = PVE
::RPCEnvironment
::get
();
2149 my $authuser = $rpcenv->get_user();
2151 my $node = extract_param
($param, 'node');
2153 my $vmid = extract_param
($param, 'vmid');
2155 my $digest = extract_param
($param, 'digest');
2157 my $disk = extract_param
($param, 'disk');
2159 my $sizestr = extract_param
($param, 'size');
2161 my $skiplock = extract_param
($param, 'skiplock');
2162 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2163 if $skiplock && $authuser ne 'root@pam';
2165 my $storecfg = PVE
::Storage
::config
();
2167 my $updatefn = sub {
2169 my $conf = PVE
::QemuServer
::load_config
($vmid);
2171 die "checksum missmatch (file change by other user?)\n"
2172 if $digest && $digest ne $conf->{digest
};
2173 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2175 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2177 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2179 my $volid = $drive->{file
};
2181 die "disk '$disk' has no associated volume\n" if !$volid;
2183 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2185 die "you can't online resize a virtio windows bootdisk\n"
2186 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2188 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2190 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2192 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2194 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2195 my ($ext, $newsize, $unit) = ($1, $2, $4);
2198 $newsize = $newsize * 1024;
2199 } elsif ($unit eq 'M') {
2200 $newsize = $newsize * 1024 * 1024;
2201 } elsif ($unit eq 'G') {
2202 $newsize = $newsize * 1024 * 1024 * 1024;
2203 } elsif ($unit eq 'T') {
2204 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2207 $newsize += $size if $ext;
2208 $newsize = int($newsize);
2210 die "unable to skrink disk size\n" if $newsize < $size;
2212 return if $size == $newsize;
2214 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2216 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2218 $drive->{size
} = $newsize;
2219 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2221 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2224 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2228 __PACKAGE__-
>register_method({
2229 name
=> 'snapshot_list',
2230 path
=> '{vmid}/snapshot',
2232 description
=> "List all snapshots.",
2234 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2237 protected
=> 1, # qemu pid files are only readable by root
2239 additionalProperties
=> 0,
2241 vmid
=> get_standard_option
('pve-vmid'),
2242 node
=> get_standard_option
('pve-node'),
2251 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2256 my $vmid = $param->{vmid
};
2258 my $conf = PVE
::QemuServer
::load_config
($vmid);
2259 my $snaphash = $conf->{snapshots
} || {};
2263 foreach my $name (keys %$snaphash) {
2264 my $d = $snaphash->{$name};
2267 snaptime
=> $d->{snaptime
} || 0,
2268 vmstate
=> $d->{vmstate
} ?
1 : 0,
2269 description
=> $d->{description
} || '',
2271 $item->{parent
} = $d->{parent
} if $d->{parent
};
2272 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2276 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2277 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2278 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2280 push @$res, $current;
2285 __PACKAGE__-
>register_method({
2287 path
=> '{vmid}/snapshot',
2291 description
=> "Snapshot a VM.",
2293 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2296 additionalProperties
=> 0,
2298 node
=> get_standard_option
('pve-node'),
2299 vmid
=> get_standard_option
('pve-vmid'),
2300 snapname
=> get_standard_option
('pve-snapshot-name'),
2304 description
=> "Save the vmstate",
2309 description
=> "Freeze the filesystem",
2314 description
=> "A textual description or comment.",
2320 description
=> "the task ID.",
2325 my $rpcenv = PVE
::RPCEnvironment
::get
();
2327 my $authuser = $rpcenv->get_user();
2329 my $node = extract_param
($param, 'node');
2331 my $vmid = extract_param
($param, 'vmid');
2333 my $snapname = extract_param
($param, 'snapname');
2335 die "unable to use snapshot name 'current' (reserved name)\n"
2336 if $snapname eq 'current';
2339 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2340 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2341 $param->{freezefs
}, $param->{description
});
2344 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2347 __PACKAGE__-
>register_method({
2348 name
=> 'snapshot_cmd_idx',
2349 path
=> '{vmid}/snapshot/{snapname}',
2356 additionalProperties
=> 0,
2358 vmid
=> get_standard_option
('pve-vmid'),
2359 node
=> get_standard_option
('pve-node'),
2360 snapname
=> get_standard_option
('pve-snapshot-name'),
2369 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2376 push @$res, { cmd
=> 'rollback' };
2377 push @$res, { cmd
=> 'config' };
2382 __PACKAGE__-
>register_method({
2383 name
=> 'update_snapshot_config',
2384 path
=> '{vmid}/snapshot/{snapname}/config',
2388 description
=> "Update snapshot metadata.",
2390 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2393 additionalProperties
=> 0,
2395 node
=> get_standard_option
('pve-node'),
2396 vmid
=> get_standard_option
('pve-vmid'),
2397 snapname
=> get_standard_option
('pve-snapshot-name'),
2401 description
=> "A textual description or comment.",
2405 returns
=> { type
=> 'null' },
2409 my $rpcenv = PVE
::RPCEnvironment
::get
();
2411 my $authuser = $rpcenv->get_user();
2413 my $vmid = extract_param
($param, 'vmid');
2415 my $snapname = extract_param
($param, 'snapname');
2417 return undef if !defined($param->{description
});
2419 my $updatefn = sub {
2421 my $conf = PVE
::QemuServer
::load_config
($vmid);
2423 PVE
::QemuServer
::check_lock
($conf);
2425 my $snap = $conf->{snapshots
}->{$snapname};
2427 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2429 $snap->{description
} = $param->{description
} if defined($param->{description
});
2431 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2434 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2439 __PACKAGE__-
>register_method({
2440 name
=> 'get_snapshot_config',
2441 path
=> '{vmid}/snapshot/{snapname}/config',
2444 description
=> "Get snapshot configuration",
2446 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2449 additionalProperties
=> 0,
2451 node
=> get_standard_option
('pve-node'),
2452 vmid
=> get_standard_option
('pve-vmid'),
2453 snapname
=> get_standard_option
('pve-snapshot-name'),
2456 returns
=> { type
=> "object" },
2460 my $rpcenv = PVE
::RPCEnvironment
::get
();
2462 my $authuser = $rpcenv->get_user();
2464 my $vmid = extract_param
($param, 'vmid');
2466 my $snapname = extract_param
($param, 'snapname');
2468 my $conf = PVE
::QemuServer
::load_config
($vmid);
2470 my $snap = $conf->{snapshots
}->{$snapname};
2472 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2477 __PACKAGE__-
>register_method({
2479 path
=> '{vmid}/snapshot/{snapname}/rollback',
2483 description
=> "Rollback VM state to specified snapshot.",
2485 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2488 additionalProperties
=> 0,
2490 node
=> get_standard_option
('pve-node'),
2491 vmid
=> get_standard_option
('pve-vmid'),
2492 snapname
=> get_standard_option
('pve-snapshot-name'),
2497 description
=> "the task ID.",
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $snapname = extract_param
($param, 'snapname');
2513 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2514 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2517 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2520 __PACKAGE__-
>register_method({
2521 name
=> 'delsnapshot',
2522 path
=> '{vmid}/snapshot/{snapname}',
2526 description
=> "Delete a VM snapshot.",
2528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2531 additionalProperties
=> 0,
2533 node
=> get_standard_option
('pve-node'),
2534 vmid
=> get_standard_option
('pve-vmid'),
2535 snapname
=> get_standard_option
('pve-snapshot-name'),
2539 description
=> "For removal from config file, even if removing disk snapshots fails.",
2545 description
=> "the task ID.",
2550 my $rpcenv = PVE
::RPCEnvironment
::get
();
2552 my $authuser = $rpcenv->get_user();
2554 my $node = extract_param
($param, 'node');
2556 my $vmid = extract_param
($param, 'vmid');
2558 my $snapname = extract_param
($param, 'snapname');
2561 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2562 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2565 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2568 __PACKAGE__-
>register_method({
2570 path
=> '{vmid}/template',
2574 description
=> "Create a Template.",
2576 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2578 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2579 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2583 additionalProperties
=> 0,
2585 node
=> get_standard_option
('pve-node'),
2586 vmid
=> get_standard_option
('pve-vmid'),
2590 description
=> "If you want to convert only 1 disk to base image.",
2591 enum
=> [PVE
::QemuServer
::disknames
()],
2596 returns
=> { type
=> 'null'},
2600 my $rpcenv = PVE
::RPCEnvironment
::get
();
2602 my $authuser = $rpcenv->get_user();
2604 my $node = extract_param
($param, 'node');
2606 my $vmid = extract_param
($param, 'vmid');
2608 my $disk = extract_param
($param, 'disk');
2610 my $updatefn = sub {
2612 my $conf = PVE
::QemuServer
::load_config
($vmid);
2614 PVE
::QemuServer
::check_lock
($conf);
2616 die "unable to create template, because VM contains snapshots\n"
2617 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2619 die "you can't convert a template to a template\n"
2620 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2622 die "you can't convert a VM to template if VM is running\n"
2623 if PVE
::QemuServer
::check_running
($vmid);
2626 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2629 $conf->{template
} = 1;
2630 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2632 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2635 PVE
::QemuServer
::lock_config
($vmid, $updatefn);