1 package PVE
::API2
::Qemu
;
8 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
10 use PVE
::Tools
qw(extract_param);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
18 use PVE
::AccessControl
;
22 use Data
::Dumper
; # fixme: remove
24 use base
qw(PVE::RESTHandler);
26 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.";
28 my $resolve_cdrom_alias = sub {
31 if (my $value = $param->{cdrom
}) {
32 $value .= ",media=cdrom" if $value !~ m/media=/;
33 $param->{ide2
} = $value;
34 delete $param->{cdrom
};
39 my $check_storage_access = sub {
40 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
42 PVE
::QemuServer
::foreach_drive
($settings, sub {
43 my ($ds, $drive) = @_;
45 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
47 my $volid = $drive->{file
};
49 if (!$volid || $volid eq 'none') {
51 } elsif ($isCDROM && ($volid eq 'cdrom')) {
52 $rpcenv->check($authuser, "/", ['Sys.Console']);
53 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
54 my ($storeid, $size) = ($2 || $default_storage, $3);
55 die "no storage ID specified (and no default storage)\n" if !$storeid;
56 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
58 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
63 my $check_storage_access_clone = sub {
64 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
68 PVE
::QemuServer
::foreach_drive
($conf, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
75 return if !$volid || $volid eq 'none';
78 if ($volid eq 'cdrom') {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
81 # we simply allow access
82 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
83 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
84 $sharedvm = 0 if !$scfg->{shared
};
88 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
89 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
90 $sharedvm = 0 if !$scfg->{shared
};
92 $sid = $storage if $storage;
93 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
100 # Note: $pool is only needed when creating a VM, because pool permissions
101 # are automatically inherited if VM already exists inside a pool.
102 my $create_disks = sub {
103 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
108 PVE
::QemuServer
::foreach_drive
($settings, sub {
109 my ($ds, $disk) = @_;
111 my $volid = $disk->{file
};
113 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
114 delete $disk->{size
};
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
116 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
117 my ($storeid, $size) = ($2 || $default_storage, $3);
118 die "no storage ID specified (and no default storage)\n" if !$storeid;
119 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
120 my $fmt = $disk->{format
} || $defformat;
121 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
122 $fmt, undef, $size*1024*1024);
123 $disk->{file
} = $volid;
124 $disk->{size
} = $size*1024*1024*1024;
125 push @$vollist, $volid;
126 delete $disk->{format
}; # no longer needed
127 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
130 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
132 my $volid_is_new = 1;
135 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
136 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
141 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
143 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
145 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
147 die "volume $volid does not exists\n" if !$size;
149 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
378 if PVE
::Storage
::parse_volume_id
($archive, 1);
380 die "can't find archive file '$archive'\n" if !($path && -f
$path);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
448 die "create failed - $err";
451 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
454 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
457 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
460 __PACKAGE__-
>register_method({
465 description
=> "Directory index",
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
473 vmid
=> get_standard_option
('pve-vmid'),
481 subdir
=> { type
=> 'string' },
484 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'unlink' },
493 { subdir
=> 'vncproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'resize' },
496 { subdir
=> 'move' },
498 { subdir
=> 'rrddata' },
499 { subdir
=> 'monitor' },
500 { subdir
=> 'snapshot' },
501 { subdir
=> 'spiceproxy' },
507 __PACKAGE__-
>register_method({
509 path
=> '{vmid}/rrd',
511 protected
=> 1, # fixme: can we avoid that?
513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
515 description
=> "Read VM RRD statistics (returns PNG)",
517 additionalProperties
=> 0,
519 node
=> get_standard_option
('pve-node'),
520 vmid
=> get_standard_option
('pve-vmid'),
522 description
=> "Specify the time frame you are interested in.",
524 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
527 description
=> "The list of datasources you want to display.",
528 type
=> 'string', format
=> 'pve-configid-list',
531 description
=> "The RRD consolidation function",
533 enum
=> [ 'AVERAGE', 'MAX' ],
541 filename
=> { type
=> 'string' },
547 return PVE
::Cluster
::create_rrd_graph
(
548 "pve2-vm/$param->{vmid}", $param->{timeframe
},
549 $param->{ds
}, $param->{cf
});
553 __PACKAGE__-
>register_method({
555 path
=> '{vmid}/rrddata',
557 protected
=> 1, # fixme: can we avoid that?
559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
561 description
=> "Read VM RRD statistics",
563 additionalProperties
=> 0,
565 node
=> get_standard_option
('pve-node'),
566 vmid
=> get_standard_option
('pve-vmid'),
568 description
=> "Specify the time frame you are interested in.",
570 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
573 description
=> "The RRD consolidation function",
575 enum
=> [ 'AVERAGE', 'MAX' ],
590 return PVE
::Cluster
::create_rrd_data
(
591 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
595 __PACKAGE__-
>register_method({
597 path
=> '{vmid}/config',
600 description
=> "Get virtual machine configuration.",
602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
605 additionalProperties
=> 0,
607 node
=> get_standard_option
('pve-node'),
608 vmid
=> get_standard_option
('pve-vmid'),
616 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
623 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
625 delete $conf->{snapshots
};
630 my $vm_is_volid_owner = sub {
631 my ($storecfg, $vmid, $volid) =@_;
633 if ($volid !~ m
|^/|) {
635 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
636 if ($owner && ($owner == $vmid)) {
644 my $test_deallocate_drive = sub {
645 my ($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 my $sid = PVE
::Storage
::parse_volume_id
($volid);
660 my $delete_drive = sub {
661 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
663 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
664 my $volid = $drive->{file
};
666 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
667 if ($force || $key =~ m/^unused/) {
669 # check if the disk is really unused
670 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
671 my $path = PVE
::Storage
::path
($storecfg, $volid);
673 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
674 if $used_paths->{$path};
676 PVE
::Storage
::vdisk_free
($storecfg, $volid);
680 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
685 delete $conf->{$key};
688 my $vmconfig_delete_option = sub {
689 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
691 return if !defined($conf->{$opt});
693 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
696 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
698 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
699 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
700 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
704 my $unplugwarning = "";
705 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
706 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
707 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
708 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
709 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
710 $unplugwarning = "<br>verify that your guest support acpi hotplug";
713 if($opt eq 'tablet'){
714 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
716 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
720 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
721 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
723 delete $conf->{$opt};
726 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
729 my $safe_num_ne = sub {
732 return 0 if !defined($a) && !defined($b);
733 return 1 if !defined($a);
734 return 1 if !defined($b);
739 my $vmconfig_update_disk = sub {
740 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
742 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
744 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
745 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
747 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
752 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
754 my $media = $drive->{media
} || 'disk';
755 my $oldmedia = $old_drive->{media
} || 'disk';
756 die "unable to change media type\n" if $media ne $oldmedia;
758 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
759 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
761 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
762 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
765 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
766 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
767 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
768 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
769 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
770 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
771 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
772 ($drive->{mbps
} || 0)*1024*1024,
773 ($drive->{mbps_rd
} || 0)*1024*1024,
774 ($drive->{mbps_wr
} || 0)*1024*1024,
776 $drive->{iops_rd
} || 0,
777 $drive->{iops_wr
} || 0)
778 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
783 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
784 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
786 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
787 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
789 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
791 if (PVE
::QemuServer
::check_running
($vmid)) {
792 if ($drive->{file
} eq 'none') {
793 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
795 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
797 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
801 } else { # hotplug new disks
803 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
807 my $vmconfig_update_net = sub {
808 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
810 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
811 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
812 my $newnet = PVE
::QemuServer
::parse_net
($value);
814 if($oldnet->{model
} ne $newnet->{model
}){
815 #if model change, we try to hot-unplug
816 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
819 if($newnet->{bridge
} && $oldnet->{bridge
}){
820 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
822 if($newnet->{rate
} ne $oldnet->{rate
}){
823 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
826 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
827 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
828 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
832 #if bridge/nat mode change, we try to hot-unplug
833 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
838 $conf->{$opt} = $value;
839 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
840 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
842 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
844 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
847 # POST/PUT {vmid}/config implementation
849 # The original API used PUT (idempotent) an we assumed that all operations
850 # are fast. But it turned out that almost any configuration change can
851 # involve hot-plug actions, or disk alloc/free. Such actions can take long
852 # time to complete and have side effects (not idempotent).
854 # The new implementation uses POST and forks a worker process. We added
855 # a new option 'background_delay'. If specified we wait up to
856 # 'background_delay' second for the worker task to complete. It returns null
857 # if the task is finished within that time, else we return the UPID.
859 my $update_vm_api = sub {
860 my ($param, $sync) = @_;
862 my $rpcenv = PVE
::RPCEnvironment
::get
();
864 my $authuser = $rpcenv->get_user();
866 my $node = extract_param
($param, 'node');
868 my $vmid = extract_param
($param, 'vmid');
870 my $digest = extract_param
($param, 'digest');
872 my $background_delay = extract_param
($param, 'background_delay');
874 my @paramarr = (); # used for log message
875 foreach my $key (keys %$param) {
876 push @paramarr, "-$key", $param->{$key};
879 my $skiplock = extract_param
($param, 'skiplock');
880 raise_param_exc
({ skiplock
=> "Only root may use this option." })
881 if $skiplock && $authuser ne 'root@pam';
883 my $delete_str = extract_param
($param, 'delete');
885 my $force = extract_param
($param, 'force');
887 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
889 my $storecfg = PVE
::Storage
::config
();
891 my $defaults = PVE
::QemuServer
::load_defaults
();
893 &$resolve_cdrom_alias($param);
895 # now try to verify all parameters
898 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
899 $opt = 'ide2' if $opt eq 'cdrom';
900 raise_param_exc
({ delete => "you can't use '-$opt' and " .
901 "-delete $opt' at the same time" })
902 if defined($param->{$opt});
904 if (!PVE
::QemuServer
::option_exists
($opt)) {
905 raise_param_exc
({ delete => "unknown option '$opt'" });
911 foreach my $opt (keys %$param) {
912 if (PVE
::QemuServer
::valid_drivename
($opt)) {
914 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
915 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
916 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
917 } elsif ($opt =~ m/^net(\d+)$/) {
919 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
920 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
924 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
926 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
928 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
932 my $conf = PVE
::QemuServer
::load_config
($vmid);
934 die "checksum missmatch (file change by other user?)\n"
935 if $digest && $digest ne $conf->{digest
};
937 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
939 if ($param->{memory
} || defined($param->{balloon
})) {
940 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
941 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
943 die "balloon value too large (must be smaller than assigned memory)\n"
944 if $balloon && $balloon > $maxmem;
947 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
951 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
953 foreach my $opt (@delete) { # delete
954 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
955 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
958 my $running = PVE
::QemuServer
::check_running
($vmid);
960 foreach my $opt (keys %$param) { # add/change
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
964 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
966 if (PVE
::QemuServer
::valid_drivename
($opt)) {
968 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt}, $force);
971 } elsif ($opt =~ m/^net(\d+)$/) { #nics
973 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
974 $opt, $param->{$opt});
978 if($opt eq 'tablet' && $param->{$opt} == 1){
979 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
980 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
981 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
984 $conf->{$opt} = $param->{$opt};
985 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
989 # allow manual ballooning if shares is set to zero
990 if ($running && defined($param->{balloon
}) &&
991 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
992 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
993 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1001 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1003 if ($background_delay) {
1005 # Note: It would be better to do that in the Event based HTTPServer
1006 # to avoid blocking call to sleep.
1008 my $end_time = time() + $background_delay;
1010 my $task = PVE
::Tools
::upid_decode
($upid);
1013 while (time() < $end_time) {
1014 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1016 sleep(1); # this gets interrupted when child process ends
1020 my $status = PVE
::Tools
::upid_read_status
($upid);
1021 return undef if $status eq 'OK';
1030 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1033 my $vm_config_perm_list = [
1038 'VM.Config.Network',
1040 'VM.Config.Options',
1043 __PACKAGE__-
>register_method({
1044 name
=> 'update_vm_async',
1045 path
=> '{vmid}/config',
1049 description
=> "Set virtual machine options (asynchrounous API).",
1051 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1054 additionalProperties
=> 0,
1055 properties
=> PVE
::QemuServer
::json_config_properties
(
1057 node
=> get_standard_option
('pve-node'),
1058 vmid
=> get_standard_option
('pve-vmid'),
1059 skiplock
=> get_standard_option
('skiplock'),
1061 type
=> 'string', format
=> 'pve-configid-list',
1062 description
=> "A list of settings you want to delete.",
1067 description
=> $opt_force_description,
1069 requires
=> 'delete',
1073 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1077 background_delay
=> {
1079 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1090 code
=> $update_vm_api,
1093 __PACKAGE__-
>register_method({
1094 name
=> 'update_vm',
1095 path
=> '{vmid}/config',
1099 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1101 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1104 additionalProperties
=> 0,
1105 properties
=> PVE
::QemuServer
::json_config_properties
(
1107 node
=> get_standard_option
('pve-node'),
1108 vmid
=> get_standard_option
('pve-vmid'),
1109 skiplock
=> get_standard_option
('skiplock'),
1111 type
=> 'string', format
=> 'pve-configid-list',
1112 description
=> "A list of settings you want to delete.",
1117 description
=> $opt_force_description,
1119 requires
=> 'delete',
1123 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1129 returns
=> { type
=> 'null' },
1132 &$update_vm_api($param, 1);
1138 __PACKAGE__-
>register_method({
1139 name
=> 'destroy_vm',
1144 description
=> "Destroy the vm (also delete all used/owned volumes).",
1146 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1149 additionalProperties
=> 0,
1151 node
=> get_standard_option
('pve-node'),
1152 vmid
=> get_standard_option
('pve-vmid'),
1153 skiplock
=> get_standard_option
('skiplock'),
1162 my $rpcenv = PVE
::RPCEnvironment
::get
();
1164 my $authuser = $rpcenv->get_user();
1166 my $vmid = $param->{vmid
};
1168 my $skiplock = $param->{skiplock
};
1169 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1170 if $skiplock && $authuser ne 'root@pam';
1173 my $conf = PVE
::QemuServer
::load_config
($vmid);
1175 my $storecfg = PVE
::Storage
::config
();
1177 my $delVMfromPoolFn = sub {
1178 my $usercfg = cfs_read_file
("user.cfg");
1179 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1180 if (my $data = $usercfg->{pools
}->{$pool}) {
1181 delete $data->{vms
}->{$vmid};
1182 delete $usercfg->{vms
}->{$vmid};
1183 cfs_write_file
("user.cfg", $usercfg);
1191 syslog
('info', "destroy VM $vmid: $upid\n");
1193 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1195 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1198 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1201 __PACKAGE__-
>register_method({
1203 path
=> '{vmid}/unlink',
1207 description
=> "Unlink/delete disk images.",
1209 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1212 additionalProperties
=> 0,
1214 node
=> get_standard_option
('pve-node'),
1215 vmid
=> get_standard_option
('pve-vmid'),
1217 type
=> 'string', format
=> 'pve-configid-list',
1218 description
=> "A list of disk IDs you want to delete.",
1222 description
=> $opt_force_description,
1227 returns
=> { type
=> 'null'},
1231 $param->{delete} = extract_param
($param, 'idlist');
1233 __PACKAGE__-
>update_vm($param);
1240 __PACKAGE__-
>register_method({
1242 path
=> '{vmid}/vncproxy',
1246 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1248 description
=> "Creates a TCP VNC proxy connections.",
1250 additionalProperties
=> 0,
1252 node
=> get_standard_option
('pve-node'),
1253 vmid
=> get_standard_option
('pve-vmid'),
1257 additionalProperties
=> 0,
1259 user
=> { type
=> 'string' },
1260 ticket
=> { type
=> 'string' },
1261 cert
=> { type
=> 'string' },
1262 port
=> { type
=> 'integer' },
1263 upid
=> { type
=> 'string' },
1269 my $rpcenv = PVE
::RPCEnvironment
::get
();
1271 my $authuser = $rpcenv->get_user();
1273 my $vmid = $param->{vmid
};
1274 my $node = $param->{node
};
1276 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1278 my $authpath = "/vms/$vmid";
1280 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1282 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1285 my $port = PVE
::Tools
::next_vnc_port
();
1290 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1291 $remip = PVE
::Cluster
::remote_node_ip
($node);
1292 # NOTE: kvm VNC traffic is already TLS encrypted
1293 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1301 syslog
('info', "starting vnc proxy $upid\n");
1305 if ($conf->{vga
} =~ m/^serial\d+$/) {
1307 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1308 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1309 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1310 '-timeout', $timeout, '-authpath', $authpath,
1311 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1314 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1316 my $qmstr = join(' ', @$qmcmd);
1318 # also redirect stderr (else we get RFB protocol errors)
1319 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1322 PVE
::Tools
::run_command
($cmd);
1327 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1329 PVE
::Tools
::wait_for_vnc_port
($port);
1340 __PACKAGE__-
>register_method({
1341 name
=> 'spiceproxy',
1342 path
=> '{vmid}/spiceproxy',
1345 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1347 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1349 description
=> "Returns a SPICE configuration to connect to the VM.",
1351 additionalProperties
=> 0,
1353 node
=> get_standard_option
('pve-node'),
1354 vmid
=> get_standard_option
('pve-vmid'),
1356 description
=> "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
1357 type
=> 'string', format
=> 'dns-name',
1363 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1364 additionalProperties
=> 1,
1366 type
=> { type
=> 'string' },
1367 password
=> { type
=> 'string' },
1368 proxy
=> { type
=> 'string' },
1369 host
=> { type
=> 'string' },
1370 'tls-port' => { type
=> 'integer' },
1376 my $rpcenv = PVE
::RPCEnvironment
::get
();
1378 my $authuser = $rpcenv->get_user();
1380 my $vmid = $param->{vmid
};
1381 my $node = $param->{node
};
1382 my $proxy = $param->{proxy
};
1384 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1388 my $port = PVE
::QemuServer
::spice_port
($vmid);
1389 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1390 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1393 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1398 my $filename = "/etc/pve/local/pve-ssl.pem";
1399 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1401 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1402 $cacert =~ s/\n/\\n/g;
1406 title
=> "VM $vmid",
1407 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1408 proxy
=> "http://$proxy:3128",
1409 'tls-port' => $port,
1410 'host-subject' => $subject,
1412 password
=> $ticket,
1413 'delete-this-file' => 1,
1417 __PACKAGE__-
>register_method({
1419 path
=> '{vmid}/status',
1422 description
=> "Directory index",
1427 additionalProperties
=> 0,
1429 node
=> get_standard_option
('pve-node'),
1430 vmid
=> get_standard_option
('pve-vmid'),
1438 subdir
=> { type
=> 'string' },
1441 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1447 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1450 { subdir
=> 'current' },
1451 { subdir
=> 'start' },
1452 { subdir
=> 'stop' },
1458 my $vm_is_ha_managed = sub {
1461 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1462 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1468 __PACKAGE__-
>register_method({
1469 name
=> 'vm_status',
1470 path
=> '{vmid}/status/current',
1473 protected
=> 1, # qemu pid files are only readable by root
1474 description
=> "Get virtual machine status.",
1476 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1479 additionalProperties
=> 0,
1481 node
=> get_standard_option
('pve-node'),
1482 vmid
=> get_standard_option
('pve-vmid'),
1485 returns
=> { type
=> 'object' },
1490 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1492 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1493 my $status = $vmstatus->{$param->{vmid
}};
1495 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1497 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1502 __PACKAGE__-
>register_method({
1504 path
=> '{vmid}/status/start',
1508 description
=> "Start virtual machine.",
1510 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1513 additionalProperties
=> 0,
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid'),
1517 skiplock
=> get_standard_option
('skiplock'),
1518 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1519 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1520 machine
=> get_standard_option
('pve-qm-machine'),
1529 my $rpcenv = PVE
::RPCEnvironment
::get
();
1531 my $authuser = $rpcenv->get_user();
1533 my $node = extract_param
($param, 'node');
1535 my $vmid = extract_param
($param, 'vmid');
1537 my $machine = extract_param
($param, 'machine');
1539 my $stateuri = extract_param
($param, 'stateuri');
1540 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1541 if $stateuri && $authuser ne 'root@pam';
1543 my $skiplock = extract_param
($param, 'skiplock');
1544 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1545 if $skiplock && $authuser ne 'root@pam';
1547 my $migratedfrom = extract_param
($param, 'migratedfrom');
1548 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1549 if $migratedfrom && $authuser ne 'root@pam';
1551 # read spice ticket from STDIN
1553 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1554 if (defined(my $line = <>)) {
1556 $spice_ticket = $line;
1560 my $storecfg = PVE
::Storage
::config
();
1562 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1563 $rpcenv->{type
} ne 'ha') {
1568 my $service = "pvevm:$vmid";
1570 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1572 print "Executing HA start for VM $vmid\n";
1574 PVE
::Tools
::run_command
($cmd);
1579 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1586 syslog
('info', "start VM $vmid: $upid\n");
1588 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1589 $machine, $spice_ticket);
1594 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1598 __PACKAGE__-
>register_method({
1600 path
=> '{vmid}/status/stop',
1604 description
=> "Stop virtual machine.",
1606 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1609 additionalProperties
=> 0,
1611 node
=> get_standard_option
('pve-node'),
1612 vmid
=> get_standard_option
('pve-vmid'),
1613 skiplock
=> get_standard_option
('skiplock'),
1614 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1616 description
=> "Wait maximal timeout seconds.",
1622 description
=> "Do not decativate storage volumes.",
1635 my $rpcenv = PVE
::RPCEnvironment
::get
();
1637 my $authuser = $rpcenv->get_user();
1639 my $node = extract_param
($param, 'node');
1641 my $vmid = extract_param
($param, 'vmid');
1643 my $skiplock = extract_param
($param, 'skiplock');
1644 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1645 if $skiplock && $authuser ne 'root@pam';
1647 my $keepActive = extract_param
($param, 'keepActive');
1648 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1649 if $keepActive && $authuser ne 'root@pam';
1651 my $migratedfrom = extract_param
($param, 'migratedfrom');
1652 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1653 if $migratedfrom && $authuser ne 'root@pam';
1656 my $storecfg = PVE
::Storage
::config
();
1658 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1663 my $service = "pvevm:$vmid";
1665 my $cmd = ['clusvcadm', '-d', $service];
1667 print "Executing HA stop for VM $vmid\n";
1669 PVE
::Tools
::run_command
($cmd);
1674 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1680 syslog
('info', "stop VM $vmid: $upid\n");
1682 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1683 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1688 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1692 __PACKAGE__-
>register_method({
1694 path
=> '{vmid}/status/reset',
1698 description
=> "Reset virtual machine.",
1700 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1703 additionalProperties
=> 0,
1705 node
=> get_standard_option
('pve-node'),
1706 vmid
=> get_standard_option
('pve-vmid'),
1707 skiplock
=> get_standard_option
('skiplock'),
1716 my $rpcenv = PVE
::RPCEnvironment
::get
();
1718 my $authuser = $rpcenv->get_user();
1720 my $node = extract_param
($param, 'node');
1722 my $vmid = extract_param
($param, 'vmid');
1724 my $skiplock = extract_param
($param, 'skiplock');
1725 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1726 if $skiplock && $authuser ne 'root@pam';
1728 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1733 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1738 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1741 __PACKAGE__-
>register_method({
1742 name
=> 'vm_shutdown',
1743 path
=> '{vmid}/status/shutdown',
1747 description
=> "Shutdown virtual machine.",
1749 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1752 additionalProperties
=> 0,
1754 node
=> get_standard_option
('pve-node'),
1755 vmid
=> get_standard_option
('pve-vmid'),
1756 skiplock
=> get_standard_option
('skiplock'),
1758 description
=> "Wait maximal timeout seconds.",
1764 description
=> "Make sure the VM stops.",
1770 description
=> "Do not decativate storage volumes.",
1783 my $rpcenv = PVE
::RPCEnvironment
::get
();
1785 my $authuser = $rpcenv->get_user();
1787 my $node = extract_param
($param, 'node');
1789 my $vmid = extract_param
($param, 'vmid');
1791 my $skiplock = extract_param
($param, 'skiplock');
1792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1793 if $skiplock && $authuser ne 'root@pam';
1795 my $keepActive = extract_param
($param, 'keepActive');
1796 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1797 if $keepActive && $authuser ne 'root@pam';
1799 my $storecfg = PVE
::Storage
::config
();
1804 syslog
('info', "shutdown VM $vmid: $upid\n");
1806 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1807 1, $param->{forceStop
}, $keepActive);
1812 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1815 __PACKAGE__-
>register_method({
1816 name
=> 'vm_suspend',
1817 path
=> '{vmid}/status/suspend',
1821 description
=> "Suspend virtual machine.",
1823 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1826 additionalProperties
=> 0,
1828 node
=> get_standard_option
('pve-node'),
1829 vmid
=> get_standard_option
('pve-vmid'),
1830 skiplock
=> get_standard_option
('skiplock'),
1839 my $rpcenv = PVE
::RPCEnvironment
::get
();
1841 my $authuser = $rpcenv->get_user();
1843 my $node = extract_param
($param, 'node');
1845 my $vmid = extract_param
($param, 'vmid');
1847 my $skiplock = extract_param
($param, 'skiplock');
1848 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1849 if $skiplock && $authuser ne 'root@pam';
1851 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1856 syslog
('info', "suspend VM $vmid: $upid\n");
1858 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1863 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1866 __PACKAGE__-
>register_method({
1867 name
=> 'vm_resume',
1868 path
=> '{vmid}/status/resume',
1872 description
=> "Resume virtual machine.",
1874 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1877 additionalProperties
=> 0,
1879 node
=> get_standard_option
('pve-node'),
1880 vmid
=> get_standard_option
('pve-vmid'),
1881 skiplock
=> get_standard_option
('skiplock'),
1890 my $rpcenv = PVE
::RPCEnvironment
::get
();
1892 my $authuser = $rpcenv->get_user();
1894 my $node = extract_param
($param, 'node');
1896 my $vmid = extract_param
($param, 'vmid');
1898 my $skiplock = extract_param
($param, 'skiplock');
1899 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1900 if $skiplock && $authuser ne 'root@pam';
1902 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1907 syslog
('info', "resume VM $vmid: $upid\n");
1909 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1914 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1917 __PACKAGE__-
>register_method({
1918 name
=> 'vm_sendkey',
1919 path
=> '{vmid}/sendkey',
1923 description
=> "Send key event to virtual machine.",
1925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1928 additionalProperties
=> 0,
1930 node
=> get_standard_option
('pve-node'),
1931 vmid
=> get_standard_option
('pve-vmid'),
1932 skiplock
=> get_standard_option
('skiplock'),
1934 description
=> "The key (qemu monitor encoding).",
1939 returns
=> { type
=> 'null'},
1943 my $rpcenv = PVE
::RPCEnvironment
::get
();
1945 my $authuser = $rpcenv->get_user();
1947 my $node = extract_param
($param, 'node');
1949 my $vmid = extract_param
($param, 'vmid');
1951 my $skiplock = extract_param
($param, 'skiplock');
1952 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1953 if $skiplock && $authuser ne 'root@pam';
1955 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1960 __PACKAGE__-
>register_method({
1961 name
=> 'vm_feature',
1962 path
=> '{vmid}/feature',
1966 description
=> "Check if feature for virtual machine is available.",
1968 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1971 additionalProperties
=> 0,
1973 node
=> get_standard_option
('pve-node'),
1974 vmid
=> get_standard_option
('pve-vmid'),
1976 description
=> "Feature to check.",
1978 enum
=> [ 'snapshot', 'clone', 'copy' ],
1980 snapname
=> get_standard_option
('pve-snapshot-name', {
1988 hasFeature
=> { type
=> 'boolean' },
1991 items
=> { type
=> 'string' },
1998 my $node = extract_param
($param, 'node');
2000 my $vmid = extract_param
($param, 'vmid');
2002 my $snapname = extract_param
($param, 'snapname');
2004 my $feature = extract_param
($param, 'feature');
2006 my $running = PVE
::QemuServer
::check_running
($vmid);
2008 my $conf = PVE
::QemuServer
::load_config
($vmid);
2011 my $snap = $conf->{snapshots
}->{$snapname};
2012 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2015 my $storecfg = PVE
::Storage
::config
();
2017 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2018 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2021 hasFeature
=> $hasFeature,
2022 nodes
=> [ keys %$nodelist ],
2026 __PACKAGE__-
>register_method({
2028 path
=> '{vmid}/clone',
2032 description
=> "Create a copy of virtual machine/template.",
2034 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2035 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2036 "'Datastore.AllocateSpace' on any used storage.",
2039 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2041 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2042 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2047 additionalProperties
=> 0,
2049 node
=> get_standard_option
('pve-node'),
2050 vmid
=> get_standard_option
('pve-vmid'),
2051 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2054 type
=> 'string', format
=> 'dns-name',
2055 description
=> "Set a name for the new VM.",
2060 description
=> "Description for the new VM.",
2064 type
=> 'string', format
=> 'pve-poolid',
2065 description
=> "Add the new VM to the specified pool.",
2067 snapname
=> get_standard_option
('pve-snapshot-name', {
2071 storage
=> get_standard_option
('pve-storage-id', {
2072 description
=> "Target storage for full clone.",
2077 description
=> "Target format for file storage.",
2081 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2086 description
=> "Create a full copy of all disk. This is always done when " .
2087 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2090 target
=> get_standard_option
('pve-node', {
2091 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2102 my $rpcenv = PVE
::RPCEnvironment
::get
();
2104 my $authuser = $rpcenv->get_user();
2106 my $node = extract_param
($param, 'node');
2108 my $vmid = extract_param
($param, 'vmid');
2110 my $newid = extract_param
($param, 'newid');
2112 my $pool = extract_param
($param, 'pool');
2114 if (defined($pool)) {
2115 $rpcenv->check_pool_exist($pool);
2118 my $snapname = extract_param
($param, 'snapname');
2120 my $storage = extract_param
($param, 'storage');
2122 my $format = extract_param
($param, 'format');
2124 my $target = extract_param
($param, 'target');
2126 my $localnode = PVE
::INotify
::nodename
();
2128 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2130 PVE
::Cluster
::check_node_exists
($target) if $target;
2132 my $storecfg = PVE
::Storage
::config
();
2135 # check if storage is enabled on local node
2136 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2138 # check if storage is available on target node
2139 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2140 # clone only works if target storage is shared
2141 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2142 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2146 PVE
::Cluster
::check_cfs_quorum
();
2148 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2150 # exclusive lock if VM is running - else shared lock is enough;
2151 my $shared_lock = $running ?
0 : 1;
2155 # do all tests after lock
2156 # we also try to do all tests before we fork the worker
2158 my $conf = PVE
::QemuServer
::load_config
($vmid);
2160 PVE
::QemuServer
::check_lock
($conf);
2162 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2164 die "unexpected state change\n" if $verify_running != $running;
2166 die "snapshot '$snapname' does not exist\n"
2167 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2169 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2171 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2173 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2175 my $conffile = PVE
::QemuServer
::config_file
($newid);
2177 die "unable to create VM $newid: config file already exists\n"
2180 my $newconf = { lock => 'clone' };
2184 foreach my $opt (keys %$oldconf) {
2185 my $value = $oldconf->{$opt};
2187 # do not copy snapshot related info
2188 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2189 $opt eq 'vmstate' || $opt eq 'snapstate';
2191 # always change MAC! address
2192 if ($opt =~ m/^net(\d+)$/) {
2193 my $net = PVE
::QemuServer
::parse_net
($value);
2194 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2195 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2196 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2197 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2198 $newconf->{$opt} = $value; # simply copy configuration
2200 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2201 die "Full clone feature is not available"
2202 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2205 $drives->{$opt} = $drive;
2206 push @$vollist, $drive->{file
};
2209 # copy everything else
2210 $newconf->{$opt} = $value;
2214 delete $newconf->{template
};
2216 if ($param->{name
}) {
2217 $newconf->{name
} = $param->{name
};
2219 if ($oldconf->{name
}) {
2220 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2222 $newconf->{name
} = "Copy-of-VM-$vmid";
2226 if ($param->{description
}) {
2227 $newconf->{description
} = $param->{description
};
2230 # create empty/temp config - this fails if VM already exists on other node
2231 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2236 my $newvollist = [];
2239 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2241 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2243 foreach my $opt (keys %$drives) {
2244 my $drive = $drives->{$opt};
2246 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2247 $newid, $storage, $format, $drive->{full
}, $newvollist);
2249 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2251 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2254 delete $newconf->{lock};
2255 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2258 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2259 die "Failed to move config to node '$target' - rename failed: $!\n"
2260 if !rename($conffile, $newconffile);
2263 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2268 sleep 1; # some storage like rbd need to wait before release volume - really?
2270 foreach my $volid (@$newvollist) {
2271 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2274 die "clone failed: $err";
2280 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2283 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2284 # Aquire exclusive lock lock for $newid
2285 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2290 __PACKAGE__-
>register_method({
2291 name
=> 'move_vm_disk',
2292 path
=> '{vmid}/move_disk',
2296 description
=> "Move volume to different storage.",
2298 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2299 "and 'Datastore.AllocateSpace' permissions on the storage.",
2302 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2303 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2307 additionalProperties
=> 0,
2309 node
=> get_standard_option
('pve-node'),
2310 vmid
=> get_standard_option
('pve-vmid'),
2313 description
=> "The disk you want to move.",
2314 enum
=> [ PVE
::QemuServer
::disknames
() ],
2316 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2319 description
=> "Target Format.",
2320 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2325 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2331 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2339 description
=> "the task ID.",
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $digest = extract_param
($param, 'digest');
2354 my $disk = extract_param
($param, 'disk');
2356 my $storeid = extract_param
($param, 'storage');
2358 my $format = extract_param
($param, 'format');
2360 my $storecfg = PVE
::Storage
::config
();
2362 my $updatefn = sub {
2364 my $conf = PVE
::QemuServer
::load_config
($vmid);
2366 die "checksum missmatch (file change by other user?)\n"
2367 if $digest && $digest ne $conf->{digest
};
2369 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2371 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2373 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2375 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2378 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2379 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2383 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2384 (!$format || !$oldfmt || $oldfmt eq $format);
2386 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2388 my $running = PVE
::QemuServer
::check_running
($vmid);
2390 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2394 my $newvollist = [];
2397 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2399 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2400 $vmid, $storeid, $format, 1, $newvollist);
2402 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2404 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2406 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2410 foreach my $volid (@$newvollist) {
2411 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2414 die "storage migration failed: $err";
2417 if ($param->{delete}) {
2418 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2423 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2426 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2429 __PACKAGE__-
>register_method({
2430 name
=> 'migrate_vm',
2431 path
=> '{vmid}/migrate',
2435 description
=> "Migrate virtual machine. Creates a new migration task.",
2437 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2440 additionalProperties
=> 0,
2442 node
=> get_standard_option
('pve-node'),
2443 vmid
=> get_standard_option
('pve-vmid'),
2444 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2447 description
=> "Use online/live migration.",
2452 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2459 description
=> "the task ID.",
2464 my $rpcenv = PVE
::RPCEnvironment
::get
();
2466 my $authuser = $rpcenv->get_user();
2468 my $target = extract_param
($param, 'target');
2470 my $localnode = PVE
::INotify
::nodename
();
2471 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2473 PVE
::Cluster
::check_cfs_quorum
();
2475 PVE
::Cluster
::check_node_exists
($target);
2477 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2479 my $vmid = extract_param
($param, 'vmid');
2481 raise_param_exc
({ force
=> "Only root may use this option." })
2482 if $param->{force
} && $authuser ne 'root@pam';
2485 my $conf = PVE
::QemuServer
::load_config
($vmid);
2487 # try to detect errors early
2489 PVE
::QemuServer
::check_lock
($conf);
2491 if (PVE
::QemuServer
::check_running
($vmid)) {
2492 die "cant migrate running VM without --online\n"
2493 if !$param->{online
};
2496 my $storecfg = PVE
::Storage
::config
();
2497 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2499 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2504 my $service = "pvevm:$vmid";
2506 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2508 print "Executing HA migrate for VM $vmid to node $target\n";
2510 PVE
::Tools
::run_command
($cmd);
2515 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2522 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2525 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2530 __PACKAGE__-
>register_method({
2532 path
=> '{vmid}/monitor',
2536 description
=> "Execute Qemu monitor commands.",
2538 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2541 additionalProperties
=> 0,
2543 node
=> get_standard_option
('pve-node'),
2544 vmid
=> get_standard_option
('pve-vmid'),
2547 description
=> "The monitor command.",
2551 returns
=> { type
=> 'string'},
2555 my $vmid = $param->{vmid
};
2557 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2561 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2563 $res = "ERROR: $@" if $@;
2568 __PACKAGE__-
>register_method({
2569 name
=> 'resize_vm',
2570 path
=> '{vmid}/resize',
2574 description
=> "Extend volume size.",
2576 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2583 skiplock
=> get_standard_option
('skiplock'),
2586 description
=> "The disk you want to resize.",
2587 enum
=> [PVE
::QemuServer
::disknames
()],
2591 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2592 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.",
2596 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2602 returns
=> { type
=> 'null'},
2606 my $rpcenv = PVE
::RPCEnvironment
::get
();
2608 my $authuser = $rpcenv->get_user();
2610 my $node = extract_param
($param, 'node');
2612 my $vmid = extract_param
($param, 'vmid');
2614 my $digest = extract_param
($param, 'digest');
2616 my $disk = extract_param
($param, 'disk');
2618 my $sizestr = extract_param
($param, 'size');
2620 my $skiplock = extract_param
($param, 'skiplock');
2621 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2622 if $skiplock && $authuser ne 'root@pam';
2624 my $storecfg = PVE
::Storage
::config
();
2626 my $updatefn = sub {
2628 my $conf = PVE
::QemuServer
::load_config
($vmid);
2630 die "checksum missmatch (file change by other user?)\n"
2631 if $digest && $digest ne $conf->{digest
};
2632 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2634 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2636 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2638 my $volid = $drive->{file
};
2640 die "disk '$disk' has no associated volume\n" if !$volid;
2642 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2644 die "you can't online resize a virtio windows bootdisk\n"
2645 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2647 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2649 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2651 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2653 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2654 my ($ext, $newsize, $unit) = ($1, $2, $4);
2657 $newsize = $newsize * 1024;
2658 } elsif ($unit eq 'M') {
2659 $newsize = $newsize * 1024 * 1024;
2660 } elsif ($unit eq 'G') {
2661 $newsize = $newsize * 1024 * 1024 * 1024;
2662 } elsif ($unit eq 'T') {
2663 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2666 $newsize += $size if $ext;
2667 $newsize = int($newsize);
2669 die "unable to skrink disk size\n" if $newsize < $size;
2671 return if $size == $newsize;
2673 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2675 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2677 $drive->{size
} = $newsize;
2678 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2680 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2683 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2687 __PACKAGE__-
>register_method({
2688 name
=> 'snapshot_list',
2689 path
=> '{vmid}/snapshot',
2691 description
=> "List all snapshots.",
2693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2696 protected
=> 1, # qemu pid files are only readable by root
2698 additionalProperties
=> 0,
2700 vmid
=> get_standard_option
('pve-vmid'),
2701 node
=> get_standard_option
('pve-node'),
2710 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2715 my $vmid = $param->{vmid
};
2717 my $conf = PVE
::QemuServer
::load_config
($vmid);
2718 my $snaphash = $conf->{snapshots
} || {};
2722 foreach my $name (keys %$snaphash) {
2723 my $d = $snaphash->{$name};
2726 snaptime
=> $d->{snaptime
} || 0,
2727 vmstate
=> $d->{vmstate
} ?
1 : 0,
2728 description
=> $d->{description
} || '',
2730 $item->{parent
} = $d->{parent
} if $d->{parent
};
2731 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2735 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2736 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2737 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2739 push @$res, $current;
2744 __PACKAGE__-
>register_method({
2746 path
=> '{vmid}/snapshot',
2750 description
=> "Snapshot a VM.",
2752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2755 additionalProperties
=> 0,
2757 node
=> get_standard_option
('pve-node'),
2758 vmid
=> get_standard_option
('pve-vmid'),
2759 snapname
=> get_standard_option
('pve-snapshot-name'),
2763 description
=> "Save the vmstate",
2768 description
=> "Freeze the filesystem",
2773 description
=> "A textual description or comment.",
2779 description
=> "the task ID.",
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2786 my $authuser = $rpcenv->get_user();
2788 my $node = extract_param
($param, 'node');
2790 my $vmid = extract_param
($param, 'vmid');
2792 my $snapname = extract_param
($param, 'snapname');
2794 die "unable to use snapshot name 'current' (reserved name)\n"
2795 if $snapname eq 'current';
2798 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2799 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2800 $param->{freezefs
}, $param->{description
});
2803 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2806 __PACKAGE__-
>register_method({
2807 name
=> 'snapshot_cmd_idx',
2808 path
=> '{vmid}/snapshot/{snapname}',
2815 additionalProperties
=> 0,
2817 vmid
=> get_standard_option
('pve-vmid'),
2818 node
=> get_standard_option
('pve-node'),
2819 snapname
=> get_standard_option
('pve-snapshot-name'),
2828 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2835 push @$res, { cmd
=> 'rollback' };
2836 push @$res, { cmd
=> 'config' };
2841 __PACKAGE__-
>register_method({
2842 name
=> 'update_snapshot_config',
2843 path
=> '{vmid}/snapshot/{snapname}/config',
2847 description
=> "Update snapshot metadata.",
2849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2852 additionalProperties
=> 0,
2854 node
=> get_standard_option
('pve-node'),
2855 vmid
=> get_standard_option
('pve-vmid'),
2856 snapname
=> get_standard_option
('pve-snapshot-name'),
2860 description
=> "A textual description or comment.",
2864 returns
=> { type
=> 'null' },
2868 my $rpcenv = PVE
::RPCEnvironment
::get
();
2870 my $authuser = $rpcenv->get_user();
2872 my $vmid = extract_param
($param, 'vmid');
2874 my $snapname = extract_param
($param, 'snapname');
2876 return undef if !defined($param->{description
});
2878 my $updatefn = sub {
2880 my $conf = PVE
::QemuServer
::load_config
($vmid);
2882 PVE
::QemuServer
::check_lock
($conf);
2884 my $snap = $conf->{snapshots
}->{$snapname};
2886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2888 $snap->{description
} = $param->{description
} if defined($param->{description
});
2890 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2893 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2898 __PACKAGE__-
>register_method({
2899 name
=> 'get_snapshot_config',
2900 path
=> '{vmid}/snapshot/{snapname}/config',
2903 description
=> "Get snapshot configuration",
2905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2908 additionalProperties
=> 0,
2910 node
=> get_standard_option
('pve-node'),
2911 vmid
=> get_standard_option
('pve-vmid'),
2912 snapname
=> get_standard_option
('pve-snapshot-name'),
2915 returns
=> { type
=> "object" },
2919 my $rpcenv = PVE
::RPCEnvironment
::get
();
2921 my $authuser = $rpcenv->get_user();
2923 my $vmid = extract_param
($param, 'vmid');
2925 my $snapname = extract_param
($param, 'snapname');
2927 my $conf = PVE
::QemuServer
::load_config
($vmid);
2929 my $snap = $conf->{snapshots
}->{$snapname};
2931 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2936 __PACKAGE__-
>register_method({
2938 path
=> '{vmid}/snapshot/{snapname}/rollback',
2942 description
=> "Rollback VM state to specified snapshot.",
2944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2947 additionalProperties
=> 0,
2949 node
=> get_standard_option
('pve-node'),
2950 vmid
=> get_standard_option
('pve-vmid'),
2951 snapname
=> get_standard_option
('pve-snapshot-name'),
2956 description
=> "the task ID.",
2961 my $rpcenv = PVE
::RPCEnvironment
::get
();
2963 my $authuser = $rpcenv->get_user();
2965 my $node = extract_param
($param, 'node');
2967 my $vmid = extract_param
($param, 'vmid');
2969 my $snapname = extract_param
($param, 'snapname');
2972 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2973 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2976 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2979 __PACKAGE__-
>register_method({
2980 name
=> 'delsnapshot',
2981 path
=> '{vmid}/snapshot/{snapname}',
2985 description
=> "Delete a VM snapshot.",
2987 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2990 additionalProperties
=> 0,
2992 node
=> get_standard_option
('pve-node'),
2993 vmid
=> get_standard_option
('pve-vmid'),
2994 snapname
=> get_standard_option
('pve-snapshot-name'),
2998 description
=> "For removal from config file, even if removing disk snapshots fails.",
3004 description
=> "the task ID.",
3009 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3013 my $node = extract_param
($param, 'node');
3015 my $vmid = extract_param
($param, 'vmid');
3017 my $snapname = extract_param
($param, 'snapname');
3020 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3021 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3024 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3027 __PACKAGE__-
>register_method({
3029 path
=> '{vmid}/template',
3033 description
=> "Create a Template.",
3035 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3036 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3039 additionalProperties
=> 0,
3041 node
=> get_standard_option
('pve-node'),
3042 vmid
=> get_standard_option
('pve-vmid'),
3046 description
=> "If you want to convert only 1 disk to base image.",
3047 enum
=> [PVE
::QemuServer
::disknames
()],
3052 returns
=> { type
=> 'null'},
3056 my $rpcenv = PVE
::RPCEnvironment
::get
();
3058 my $authuser = $rpcenv->get_user();
3060 my $node = extract_param
($param, 'node');
3062 my $vmid = extract_param
($param, 'vmid');
3064 my $disk = extract_param
($param, 'disk');
3066 my $updatefn = sub {
3068 my $conf = PVE
::QemuServer
::load_config
($vmid);
3070 PVE
::QemuServer
::check_lock
($conf);
3072 die "unable to create template, because VM contains snapshots\n"
3073 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3075 die "you can't convert a template to a template\n"
3076 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3078 die "you can't convert a VM to template if VM is running\n"
3079 if PVE
::QemuServer
::check_running
($vmid);
3082 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3085 $conf->{template
} = 1;
3086 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3088 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3091 PVE
::QemuServer
::lock_config
($vmid, $updatefn);