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')) {
1556 $spice_ticket = $line if $line;
1559 my $storecfg = PVE
::Storage
::config
();
1561 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1562 $rpcenv->{type
} ne 'ha') {
1567 my $service = "pvevm:$vmid";
1569 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1571 print "Executing HA start for VM $vmid\n";
1573 PVE
::Tools
::run_command
($cmd);
1578 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1585 syslog
('info', "start VM $vmid: $upid\n");
1587 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1588 $machine, $spice_ticket);
1593 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1597 __PACKAGE__-
>register_method({
1599 path
=> '{vmid}/status/stop',
1603 description
=> "Stop virtual machine.",
1605 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1608 additionalProperties
=> 0,
1610 node
=> get_standard_option
('pve-node'),
1611 vmid
=> get_standard_option
('pve-vmid'),
1612 skiplock
=> get_standard_option
('skiplock'),
1613 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1615 description
=> "Wait maximal timeout seconds.",
1621 description
=> "Do not decativate storage volumes.",
1634 my $rpcenv = PVE
::RPCEnvironment
::get
();
1636 my $authuser = $rpcenv->get_user();
1638 my $node = extract_param
($param, 'node');
1640 my $vmid = extract_param
($param, 'vmid');
1642 my $skiplock = extract_param
($param, 'skiplock');
1643 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1644 if $skiplock && $authuser ne 'root@pam';
1646 my $keepActive = extract_param
($param, 'keepActive');
1647 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1648 if $keepActive && $authuser ne 'root@pam';
1650 my $migratedfrom = extract_param
($param, 'migratedfrom');
1651 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1652 if $migratedfrom && $authuser ne 'root@pam';
1655 my $storecfg = PVE
::Storage
::config
();
1657 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1662 my $service = "pvevm:$vmid";
1664 my $cmd = ['clusvcadm', '-d', $service];
1666 print "Executing HA stop for VM $vmid\n";
1668 PVE
::Tools
::run_command
($cmd);
1673 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1679 syslog
('info', "stop VM $vmid: $upid\n");
1681 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1682 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1687 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1691 __PACKAGE__-
>register_method({
1693 path
=> '{vmid}/status/reset',
1697 description
=> "Reset virtual machine.",
1699 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1702 additionalProperties
=> 0,
1704 node
=> get_standard_option
('pve-node'),
1705 vmid
=> get_standard_option
('pve-vmid'),
1706 skiplock
=> get_standard_option
('skiplock'),
1715 my $rpcenv = PVE
::RPCEnvironment
::get
();
1717 my $authuser = $rpcenv->get_user();
1719 my $node = extract_param
($param, 'node');
1721 my $vmid = extract_param
($param, 'vmid');
1723 my $skiplock = extract_param
($param, 'skiplock');
1724 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1725 if $skiplock && $authuser ne 'root@pam';
1727 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1732 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1737 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1740 __PACKAGE__-
>register_method({
1741 name
=> 'vm_shutdown',
1742 path
=> '{vmid}/status/shutdown',
1746 description
=> "Shutdown virtual machine.",
1748 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1751 additionalProperties
=> 0,
1753 node
=> get_standard_option
('pve-node'),
1754 vmid
=> get_standard_option
('pve-vmid'),
1755 skiplock
=> get_standard_option
('skiplock'),
1757 description
=> "Wait maximal timeout seconds.",
1763 description
=> "Make sure the VM stops.",
1769 description
=> "Do not decativate storage volumes.",
1782 my $rpcenv = PVE
::RPCEnvironment
::get
();
1784 my $authuser = $rpcenv->get_user();
1786 my $node = extract_param
($param, 'node');
1788 my $vmid = extract_param
($param, 'vmid');
1790 my $skiplock = extract_param
($param, 'skiplock');
1791 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1792 if $skiplock && $authuser ne 'root@pam';
1794 my $keepActive = extract_param
($param, 'keepActive');
1795 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1796 if $keepActive && $authuser ne 'root@pam';
1798 my $storecfg = PVE
::Storage
::config
();
1803 syslog
('info', "shutdown VM $vmid: $upid\n");
1805 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1806 1, $param->{forceStop
}, $keepActive);
1811 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1814 __PACKAGE__-
>register_method({
1815 name
=> 'vm_suspend',
1816 path
=> '{vmid}/status/suspend',
1820 description
=> "Suspend virtual machine.",
1822 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1825 additionalProperties
=> 0,
1827 node
=> get_standard_option
('pve-node'),
1828 vmid
=> get_standard_option
('pve-vmid'),
1829 skiplock
=> get_standard_option
('skiplock'),
1838 my $rpcenv = PVE
::RPCEnvironment
::get
();
1840 my $authuser = $rpcenv->get_user();
1842 my $node = extract_param
($param, 'node');
1844 my $vmid = extract_param
($param, 'vmid');
1846 my $skiplock = extract_param
($param, 'skiplock');
1847 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1848 if $skiplock && $authuser ne 'root@pam';
1850 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1855 syslog
('info', "suspend VM $vmid: $upid\n");
1857 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1862 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1865 __PACKAGE__-
>register_method({
1866 name
=> 'vm_resume',
1867 path
=> '{vmid}/status/resume',
1871 description
=> "Resume virtual machine.",
1873 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1876 additionalProperties
=> 0,
1878 node
=> get_standard_option
('pve-node'),
1879 vmid
=> get_standard_option
('pve-vmid'),
1880 skiplock
=> get_standard_option
('skiplock'),
1889 my $rpcenv = PVE
::RPCEnvironment
::get
();
1891 my $authuser = $rpcenv->get_user();
1893 my $node = extract_param
($param, 'node');
1895 my $vmid = extract_param
($param, 'vmid');
1897 my $skiplock = extract_param
($param, 'skiplock');
1898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1899 if $skiplock && $authuser ne 'root@pam';
1901 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1906 syslog
('info', "resume VM $vmid: $upid\n");
1908 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1913 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1916 __PACKAGE__-
>register_method({
1917 name
=> 'vm_sendkey',
1918 path
=> '{vmid}/sendkey',
1922 description
=> "Send key event to virtual machine.",
1924 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1927 additionalProperties
=> 0,
1929 node
=> get_standard_option
('pve-node'),
1930 vmid
=> get_standard_option
('pve-vmid'),
1931 skiplock
=> get_standard_option
('skiplock'),
1933 description
=> "The key (qemu monitor encoding).",
1938 returns
=> { type
=> 'null'},
1942 my $rpcenv = PVE
::RPCEnvironment
::get
();
1944 my $authuser = $rpcenv->get_user();
1946 my $node = extract_param
($param, 'node');
1948 my $vmid = extract_param
($param, 'vmid');
1950 my $skiplock = extract_param
($param, 'skiplock');
1951 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1952 if $skiplock && $authuser ne 'root@pam';
1954 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1959 __PACKAGE__-
>register_method({
1960 name
=> 'vm_feature',
1961 path
=> '{vmid}/feature',
1965 description
=> "Check if feature for virtual machine is available.",
1967 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1970 additionalProperties
=> 0,
1972 node
=> get_standard_option
('pve-node'),
1973 vmid
=> get_standard_option
('pve-vmid'),
1975 description
=> "Feature to check.",
1977 enum
=> [ 'snapshot', 'clone', 'copy' ],
1979 snapname
=> get_standard_option
('pve-snapshot-name', {
1987 hasFeature
=> { type
=> 'boolean' },
1990 items
=> { type
=> 'string' },
1997 my $node = extract_param
($param, 'node');
1999 my $vmid = extract_param
($param, 'vmid');
2001 my $snapname = extract_param
($param, 'snapname');
2003 my $feature = extract_param
($param, 'feature');
2005 my $running = PVE
::QemuServer
::check_running
($vmid);
2007 my $conf = PVE
::QemuServer
::load_config
($vmid);
2010 my $snap = $conf->{snapshots
}->{$snapname};
2011 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2014 my $storecfg = PVE
::Storage
::config
();
2016 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2017 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2020 hasFeature
=> $hasFeature,
2021 nodes
=> [ keys %$nodelist ],
2025 __PACKAGE__-
>register_method({
2027 path
=> '{vmid}/clone',
2031 description
=> "Create a copy of virtual machine/template.",
2033 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2034 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2035 "'Datastore.AllocateSpace' on any used storage.",
2038 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2040 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2041 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid'),
2050 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2053 type
=> 'string', format
=> 'dns-name',
2054 description
=> "Set a name for the new VM.",
2059 description
=> "Description for the new VM.",
2063 type
=> 'string', format
=> 'pve-poolid',
2064 description
=> "Add the new VM to the specified pool.",
2066 snapname
=> get_standard_option
('pve-snapshot-name', {
2070 storage
=> get_standard_option
('pve-storage-id', {
2071 description
=> "Target storage for full clone.",
2076 description
=> "Target format for file storage.",
2080 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2085 description
=> "Create a full copy of all disk. This is always done when " .
2086 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2089 target
=> get_standard_option
('pve-node', {
2090 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2101 my $rpcenv = PVE
::RPCEnvironment
::get
();
2103 my $authuser = $rpcenv->get_user();
2105 my $node = extract_param
($param, 'node');
2107 my $vmid = extract_param
($param, 'vmid');
2109 my $newid = extract_param
($param, 'newid');
2111 my $pool = extract_param
($param, 'pool');
2113 if (defined($pool)) {
2114 $rpcenv->check_pool_exist($pool);
2117 my $snapname = extract_param
($param, 'snapname');
2119 my $storage = extract_param
($param, 'storage');
2121 my $format = extract_param
($param, 'format');
2123 my $target = extract_param
($param, 'target');
2125 my $localnode = PVE
::INotify
::nodename
();
2127 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2129 PVE
::Cluster
::check_node_exists
($target) if $target;
2131 my $storecfg = PVE
::Storage
::config
();
2134 # check if storage is enabled on local node
2135 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2137 # check if storage is available on target node
2138 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2139 # clone only works if target storage is shared
2140 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2141 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2145 PVE
::Cluster
::check_cfs_quorum
();
2147 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2149 # exclusive lock if VM is running - else shared lock is enough;
2150 my $shared_lock = $running ?
0 : 1;
2154 # do all tests after lock
2155 # we also try to do all tests before we fork the worker
2157 my $conf = PVE
::QemuServer
::load_config
($vmid);
2159 PVE
::QemuServer
::check_lock
($conf);
2161 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2163 die "unexpected state change\n" if $verify_running != $running;
2165 die "snapshot '$snapname' does not exist\n"
2166 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2168 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2170 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2172 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2174 my $conffile = PVE
::QemuServer
::config_file
($newid);
2176 die "unable to create VM $newid: config file already exists\n"
2179 my $newconf = { lock => 'clone' };
2183 foreach my $opt (keys %$oldconf) {
2184 my $value = $oldconf->{$opt};
2186 # do not copy snapshot related info
2187 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2188 $opt eq 'vmstate' || $opt eq 'snapstate';
2190 # always change MAC! address
2191 if ($opt =~ m/^net(\d+)$/) {
2192 my $net = PVE
::QemuServer
::parse_net
($value);
2193 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2194 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2195 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2196 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2197 $newconf->{$opt} = $value; # simply copy configuration
2199 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2200 die "Full clone feature is not available"
2201 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2204 $drives->{$opt} = $drive;
2205 push @$vollist, $drive->{file
};
2208 # copy everything else
2209 $newconf->{$opt} = $value;
2213 delete $newconf->{template
};
2215 if ($param->{name
}) {
2216 $newconf->{name
} = $param->{name
};
2218 if ($oldconf->{name
}) {
2219 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2221 $newconf->{name
} = "Copy-of-VM-$vmid";
2225 if ($param->{description
}) {
2226 $newconf->{description
} = $param->{description
};
2229 # create empty/temp config - this fails if VM already exists on other node
2230 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2235 my $newvollist = [];
2238 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2240 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2242 foreach my $opt (keys %$drives) {
2243 my $drive = $drives->{$opt};
2245 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2246 $newid, $storage, $format, $drive->{full
}, $newvollist);
2248 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2250 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2253 delete $newconf->{lock};
2254 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2257 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2258 die "Failed to move config to node '$target' - rename failed: $!\n"
2259 if !rename($conffile, $newconffile);
2262 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2267 sleep 1; # some storage like rbd need to wait before release volume - really?
2269 foreach my $volid (@$newvollist) {
2270 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2273 die "clone failed: $err";
2279 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2282 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2283 # Aquire exclusive lock lock for $newid
2284 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2289 __PACKAGE__-
>register_method({
2290 name
=> 'move_vm_disk',
2291 path
=> '{vmid}/move_disk',
2295 description
=> "Move volume to different storage.",
2297 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2298 "and 'Datastore.AllocateSpace' permissions on the storage.",
2301 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2302 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2306 additionalProperties
=> 0,
2308 node
=> get_standard_option
('pve-node'),
2309 vmid
=> get_standard_option
('pve-vmid'),
2312 description
=> "The disk you want to move.",
2313 enum
=> [ PVE
::QemuServer
::disknames
() ],
2315 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2318 description
=> "Target Format.",
2319 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2324 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2330 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2338 description
=> "the task ID.",
2343 my $rpcenv = PVE
::RPCEnvironment
::get
();
2345 my $authuser = $rpcenv->get_user();
2347 my $node = extract_param
($param, 'node');
2349 my $vmid = extract_param
($param, 'vmid');
2351 my $digest = extract_param
($param, 'digest');
2353 my $disk = extract_param
($param, 'disk');
2355 my $storeid = extract_param
($param, 'storage');
2357 my $format = extract_param
($param, 'format');
2359 my $storecfg = PVE
::Storage
::config
();
2361 my $updatefn = sub {
2363 my $conf = PVE
::QemuServer
::load_config
($vmid);
2365 die "checksum missmatch (file change by other user?)\n"
2366 if $digest && $digest ne $conf->{digest
};
2368 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2370 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2372 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2374 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2377 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2378 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2382 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2383 (!$format || !$oldfmt || $oldfmt eq $format);
2385 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2387 my $running = PVE
::QemuServer
::check_running
($vmid);
2389 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2393 my $newvollist = [];
2396 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2398 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2399 $vmid, $storeid, $format, 1, $newvollist);
2401 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2403 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2405 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2409 foreach my $volid (@$newvollist) {
2410 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2413 die "storage migration failed: $err";
2416 if ($param->{delete}) {
2417 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2422 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2425 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2428 __PACKAGE__-
>register_method({
2429 name
=> 'migrate_vm',
2430 path
=> '{vmid}/migrate',
2434 description
=> "Migrate virtual machine. Creates a new migration task.",
2436 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2439 additionalProperties
=> 0,
2441 node
=> get_standard_option
('pve-node'),
2442 vmid
=> get_standard_option
('pve-vmid'),
2443 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2446 description
=> "Use online/live migration.",
2451 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2458 description
=> "the task ID.",
2463 my $rpcenv = PVE
::RPCEnvironment
::get
();
2465 my $authuser = $rpcenv->get_user();
2467 my $target = extract_param
($param, 'target');
2469 my $localnode = PVE
::INotify
::nodename
();
2470 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2472 PVE
::Cluster
::check_cfs_quorum
();
2474 PVE
::Cluster
::check_node_exists
($target);
2476 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2478 my $vmid = extract_param
($param, 'vmid');
2480 raise_param_exc
({ force
=> "Only root may use this option." })
2481 if $param->{force
} && $authuser ne 'root@pam';
2484 my $conf = PVE
::QemuServer
::load_config
($vmid);
2486 # try to detect errors early
2488 PVE
::QemuServer
::check_lock
($conf);
2490 if (PVE
::QemuServer
::check_running
($vmid)) {
2491 die "cant migrate running VM without --online\n"
2492 if !$param->{online
};
2495 my $storecfg = PVE
::Storage
::config
();
2496 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2498 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2503 my $service = "pvevm:$vmid";
2505 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2507 print "Executing HA migrate for VM $vmid to node $target\n";
2509 PVE
::Tools
::run_command
($cmd);
2514 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2521 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2524 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2529 __PACKAGE__-
>register_method({
2531 path
=> '{vmid}/monitor',
2535 description
=> "Execute Qemu monitor commands.",
2537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid'),
2546 description
=> "The monitor command.",
2550 returns
=> { type
=> 'string'},
2554 my $vmid = $param->{vmid
};
2556 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2560 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2562 $res = "ERROR: $@" if $@;
2567 __PACKAGE__-
>register_method({
2568 name
=> 'resize_vm',
2569 path
=> '{vmid}/resize',
2573 description
=> "Extend volume size.",
2575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2578 additionalProperties
=> 0,
2580 node
=> get_standard_option
('pve-node'),
2581 vmid
=> get_standard_option
('pve-vmid'),
2582 skiplock
=> get_standard_option
('skiplock'),
2585 description
=> "The disk you want to resize.",
2586 enum
=> [PVE
::QemuServer
::disknames
()],
2590 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2591 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.",
2595 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2601 returns
=> { type
=> 'null'},
2605 my $rpcenv = PVE
::RPCEnvironment
::get
();
2607 my $authuser = $rpcenv->get_user();
2609 my $node = extract_param
($param, 'node');
2611 my $vmid = extract_param
($param, 'vmid');
2613 my $digest = extract_param
($param, 'digest');
2615 my $disk = extract_param
($param, 'disk');
2617 my $sizestr = extract_param
($param, 'size');
2619 my $skiplock = extract_param
($param, 'skiplock');
2620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2621 if $skiplock && $authuser ne 'root@pam';
2623 my $storecfg = PVE
::Storage
::config
();
2625 my $updatefn = sub {
2627 my $conf = PVE
::QemuServer
::load_config
($vmid);
2629 die "checksum missmatch (file change by other user?)\n"
2630 if $digest && $digest ne $conf->{digest
};
2631 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2633 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2635 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2637 my $volid = $drive->{file
};
2639 die "disk '$disk' has no associated volume\n" if !$volid;
2641 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2643 die "you can't online resize a virtio windows bootdisk\n"
2644 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2646 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2648 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2650 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2652 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2653 my ($ext, $newsize, $unit) = ($1, $2, $4);
2656 $newsize = $newsize * 1024;
2657 } elsif ($unit eq 'M') {
2658 $newsize = $newsize * 1024 * 1024;
2659 } elsif ($unit eq 'G') {
2660 $newsize = $newsize * 1024 * 1024 * 1024;
2661 } elsif ($unit eq 'T') {
2662 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2665 $newsize += $size if $ext;
2666 $newsize = int($newsize);
2668 die "unable to skrink disk size\n" if $newsize < $size;
2670 return if $size == $newsize;
2672 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2674 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2676 $drive->{size
} = $newsize;
2677 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2679 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2682 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2686 __PACKAGE__-
>register_method({
2687 name
=> 'snapshot_list',
2688 path
=> '{vmid}/snapshot',
2690 description
=> "List all snapshots.",
2692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2695 protected
=> 1, # qemu pid files are only readable by root
2697 additionalProperties
=> 0,
2699 vmid
=> get_standard_option
('pve-vmid'),
2700 node
=> get_standard_option
('pve-node'),
2709 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2714 my $vmid = $param->{vmid
};
2716 my $conf = PVE
::QemuServer
::load_config
($vmid);
2717 my $snaphash = $conf->{snapshots
} || {};
2721 foreach my $name (keys %$snaphash) {
2722 my $d = $snaphash->{$name};
2725 snaptime
=> $d->{snaptime
} || 0,
2726 vmstate
=> $d->{vmstate
} ?
1 : 0,
2727 description
=> $d->{description
} || '',
2729 $item->{parent
} = $d->{parent
} if $d->{parent
};
2730 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2734 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2735 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2736 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2738 push @$res, $current;
2743 __PACKAGE__-
>register_method({
2745 path
=> '{vmid}/snapshot',
2749 description
=> "Snapshot a VM.",
2751 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2754 additionalProperties
=> 0,
2756 node
=> get_standard_option
('pve-node'),
2757 vmid
=> get_standard_option
('pve-vmid'),
2758 snapname
=> get_standard_option
('pve-snapshot-name'),
2762 description
=> "Save the vmstate",
2767 description
=> "Freeze the filesystem",
2772 description
=> "A textual description or comment.",
2778 description
=> "the task ID.",
2783 my $rpcenv = PVE
::RPCEnvironment
::get
();
2785 my $authuser = $rpcenv->get_user();
2787 my $node = extract_param
($param, 'node');
2789 my $vmid = extract_param
($param, 'vmid');
2791 my $snapname = extract_param
($param, 'snapname');
2793 die "unable to use snapshot name 'current' (reserved name)\n"
2794 if $snapname eq 'current';
2797 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2798 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2799 $param->{freezefs
}, $param->{description
});
2802 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2805 __PACKAGE__-
>register_method({
2806 name
=> 'snapshot_cmd_idx',
2807 path
=> '{vmid}/snapshot/{snapname}',
2814 additionalProperties
=> 0,
2816 vmid
=> get_standard_option
('pve-vmid'),
2817 node
=> get_standard_option
('pve-node'),
2818 snapname
=> get_standard_option
('pve-snapshot-name'),
2827 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2834 push @$res, { cmd
=> 'rollback' };
2835 push @$res, { cmd
=> 'config' };
2840 __PACKAGE__-
>register_method({
2841 name
=> 'update_snapshot_config',
2842 path
=> '{vmid}/snapshot/{snapname}/config',
2846 description
=> "Update snapshot metadata.",
2848 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2851 additionalProperties
=> 0,
2853 node
=> get_standard_option
('pve-node'),
2854 vmid
=> get_standard_option
('pve-vmid'),
2855 snapname
=> get_standard_option
('pve-snapshot-name'),
2859 description
=> "A textual description or comment.",
2863 returns
=> { type
=> 'null' },
2867 my $rpcenv = PVE
::RPCEnvironment
::get
();
2869 my $authuser = $rpcenv->get_user();
2871 my $vmid = extract_param
($param, 'vmid');
2873 my $snapname = extract_param
($param, 'snapname');
2875 return undef if !defined($param->{description
});
2877 my $updatefn = sub {
2879 my $conf = PVE
::QemuServer
::load_config
($vmid);
2881 PVE
::QemuServer
::check_lock
($conf);
2883 my $snap = $conf->{snapshots
}->{$snapname};
2885 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2887 $snap->{description
} = $param->{description
} if defined($param->{description
});
2889 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2892 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2897 __PACKAGE__-
>register_method({
2898 name
=> 'get_snapshot_config',
2899 path
=> '{vmid}/snapshot/{snapname}/config',
2902 description
=> "Get snapshot configuration",
2904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2907 additionalProperties
=> 0,
2909 node
=> get_standard_option
('pve-node'),
2910 vmid
=> get_standard_option
('pve-vmid'),
2911 snapname
=> get_standard_option
('pve-snapshot-name'),
2914 returns
=> { type
=> "object" },
2918 my $rpcenv = PVE
::RPCEnvironment
::get
();
2920 my $authuser = $rpcenv->get_user();
2922 my $vmid = extract_param
($param, 'vmid');
2924 my $snapname = extract_param
($param, 'snapname');
2926 my $conf = PVE
::QemuServer
::load_config
($vmid);
2928 my $snap = $conf->{snapshots
}->{$snapname};
2930 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2935 __PACKAGE__-
>register_method({
2937 path
=> '{vmid}/snapshot/{snapname}/rollback',
2941 description
=> "Rollback VM state to specified snapshot.",
2943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2946 additionalProperties
=> 0,
2948 node
=> get_standard_option
('pve-node'),
2949 vmid
=> get_standard_option
('pve-vmid'),
2950 snapname
=> get_standard_option
('pve-snapshot-name'),
2955 description
=> "the task ID.",
2960 my $rpcenv = PVE
::RPCEnvironment
::get
();
2962 my $authuser = $rpcenv->get_user();
2964 my $node = extract_param
($param, 'node');
2966 my $vmid = extract_param
($param, 'vmid');
2968 my $snapname = extract_param
($param, 'snapname');
2971 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2972 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2975 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2978 __PACKAGE__-
>register_method({
2979 name
=> 'delsnapshot',
2980 path
=> '{vmid}/snapshot/{snapname}',
2984 description
=> "Delete a VM snapshot.",
2986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2989 additionalProperties
=> 0,
2991 node
=> get_standard_option
('pve-node'),
2992 vmid
=> get_standard_option
('pve-vmid'),
2993 snapname
=> get_standard_option
('pve-snapshot-name'),
2997 description
=> "For removal from config file, even if removing disk snapshots fails.",
3003 description
=> "the task ID.",
3008 my $rpcenv = PVE
::RPCEnvironment
::get
();
3010 my $authuser = $rpcenv->get_user();
3012 my $node = extract_param
($param, 'node');
3014 my $vmid = extract_param
($param, 'vmid');
3016 my $snapname = extract_param
($param, 'snapname');
3019 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3020 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3023 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3026 __PACKAGE__-
>register_method({
3028 path
=> '{vmid}/template',
3032 description
=> "Create a Template.",
3034 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3035 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3038 additionalProperties
=> 0,
3040 node
=> get_standard_option
('pve-node'),
3041 vmid
=> get_standard_option
('pve-vmid'),
3045 description
=> "If you want to convert only 1 disk to base image.",
3046 enum
=> [PVE
::QemuServer
::disknames
()],
3051 returns
=> { type
=> 'null'},
3055 my $rpcenv = PVE
::RPCEnvironment
::get
();
3057 my $authuser = $rpcenv->get_user();
3059 my $node = extract_param
($param, 'node');
3061 my $vmid = extract_param
($param, 'vmid');
3063 my $disk = extract_param
($param, 'disk');
3065 my $updatefn = sub {
3067 my $conf = PVE
::QemuServer
::load_config
($vmid);
3069 PVE
::QemuServer
::check_lock
($conf);
3071 die "unable to create template, because VM contains snapshots\n"
3072 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3074 die "you can't convert a template to a template\n"
3075 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3077 die "you can't convert a VM to template if VM is running\n"
3078 if PVE
::QemuServer
::check_running
($vmid);
3081 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3084 $conf->{template
} = 1;
3085 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3087 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3090 PVE
::QemuServer
::lock_config
($vmid, $updatefn);