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 $authpath = "/vms/$vmid";
1278 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1280 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1283 my $port = PVE
::Tools
::next_vnc_port
();
1287 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1288 $remip = PVE
::Cluster
::remote_node_ip
($node);
1291 # NOTE: kvm VNC traffic is already TLS encrypted
1292 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1299 syslog
('info', "starting vnc proxy $upid\n");
1301 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1303 my $qmstr = join(' ', @$qmcmd);
1305 # also redirect stderr (else we get RFB protocol errors)
1306 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1308 PVE
::Tools
::run_command
($cmd);
1313 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1315 PVE
::Tools
::wait_for_vnc_port
($port);
1326 __PACKAGE__-
>register_method({
1327 name
=> 'spiceproxy',
1328 path
=> '{vmid}/spiceproxy',
1331 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1335 description
=> "Returns a SPICE configuration to connect to the VM.",
1337 additionalProperties
=> 0,
1339 node
=> get_standard_option
('pve-node'),
1340 vmid
=> get_standard_option
('pve-vmid'),
1342 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).",
1343 type
=> 'string', format
=> 'dns-name',
1349 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1350 additionalProperties
=> 1,
1352 type
=> { type
=> 'string' },
1353 password
=> { type
=> 'string' },
1354 proxy
=> { type
=> 'string' },
1355 host
=> { type
=> 'string' },
1356 'tls-port' => { type
=> 'integer' },
1362 my $rpcenv = PVE
::RPCEnvironment
::get
();
1364 my $authuser = $rpcenv->get_user();
1366 my $vmid = $param->{vmid
};
1367 my $node = $param->{node
};
1368 my $proxy = $param->{proxy
};
1370 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1374 my $port = PVE
::QemuServer
::spice_port
($vmid);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1376 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1379 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1384 my $filename = "/etc/pve/local/pve-ssl.pem";
1385 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1387 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1388 $cacert =~ s/\n/\\n/g;
1392 title
=> "VM $vmid",
1393 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1394 proxy
=> "http://$proxy:3128",
1395 'tls-port' => $port,
1396 'host-subject' => $subject,
1398 password
=> $ticket,
1399 'delete-this-file' => 1,
1403 __PACKAGE__-
>register_method({
1405 path
=> '{vmid}/status',
1408 description
=> "Directory index",
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid'),
1424 subdir
=> { type
=> 'string' },
1427 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1433 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1436 { subdir
=> 'current' },
1437 { subdir
=> 'start' },
1438 { subdir
=> 'stop' },
1444 my $vm_is_ha_managed = sub {
1447 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1448 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1454 __PACKAGE__-
>register_method({
1455 name
=> 'vm_status',
1456 path
=> '{vmid}/status/current',
1459 protected
=> 1, # qemu pid files are only readable by root
1460 description
=> "Get virtual machine status.",
1462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1465 additionalProperties
=> 0,
1467 node
=> get_standard_option
('pve-node'),
1468 vmid
=> get_standard_option
('pve-vmid'),
1471 returns
=> { type
=> 'object' },
1476 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1478 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1479 my $status = $vmstatus->{$param->{vmid
}};
1481 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1483 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1488 __PACKAGE__-
>register_method({
1490 path
=> '{vmid}/status/start',
1494 description
=> "Start virtual machine.",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1503 skiplock
=> get_standard_option
('skiplock'),
1504 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1505 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1506 machine
=> get_standard_option
('pve-qm-machine'),
1515 my $rpcenv = PVE
::RPCEnvironment
::get
();
1517 my $authuser = $rpcenv->get_user();
1519 my $node = extract_param
($param, 'node');
1521 my $vmid = extract_param
($param, 'vmid');
1523 my $machine = extract_param
($param, 'machine');
1525 my $stateuri = extract_param
($param, 'stateuri');
1526 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1527 if $stateuri && $authuser ne 'root@pam';
1529 my $skiplock = extract_param
($param, 'skiplock');
1530 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1531 if $skiplock && $authuser ne 'root@pam';
1533 my $migratedfrom = extract_param
($param, 'migratedfrom');
1534 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1535 if $migratedfrom && $authuser ne 'root@pam';
1537 # read spice ticket from STDIN
1539 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1542 $spice_ticket = $line if $line;
1545 my $storecfg = PVE
::Storage
::config
();
1547 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1548 $rpcenv->{type
} ne 'ha') {
1553 my $service = "pvevm:$vmid";
1555 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1557 print "Executing HA start for VM $vmid\n";
1559 PVE
::Tools
::run_command
($cmd);
1564 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1571 syslog
('info', "start VM $vmid: $upid\n");
1573 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1574 $machine, $spice_ticket);
1579 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1583 __PACKAGE__-
>register_method({
1585 path
=> '{vmid}/status/stop',
1589 description
=> "Stop virtual machine.",
1591 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1594 additionalProperties
=> 0,
1596 node
=> get_standard_option
('pve-node'),
1597 vmid
=> get_standard_option
('pve-vmid'),
1598 skiplock
=> get_standard_option
('skiplock'),
1599 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1601 description
=> "Wait maximal timeout seconds.",
1607 description
=> "Do not decativate storage volumes.",
1620 my $rpcenv = PVE
::RPCEnvironment
::get
();
1622 my $authuser = $rpcenv->get_user();
1624 my $node = extract_param
($param, 'node');
1626 my $vmid = extract_param
($param, 'vmid');
1628 my $skiplock = extract_param
($param, 'skiplock');
1629 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1630 if $skiplock && $authuser ne 'root@pam';
1632 my $keepActive = extract_param
($param, 'keepActive');
1633 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1634 if $keepActive && $authuser ne 'root@pam';
1636 my $migratedfrom = extract_param
($param, 'migratedfrom');
1637 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1638 if $migratedfrom && $authuser ne 'root@pam';
1641 my $storecfg = PVE
::Storage
::config
();
1643 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1648 my $service = "pvevm:$vmid";
1650 my $cmd = ['clusvcadm', '-d', $service];
1652 print "Executing HA stop for VM $vmid\n";
1654 PVE
::Tools
::run_command
($cmd);
1659 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1665 syslog
('info', "stop VM $vmid: $upid\n");
1667 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1668 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1673 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1677 __PACKAGE__-
>register_method({
1679 path
=> '{vmid}/status/reset',
1683 description
=> "Reset virtual machine.",
1685 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid'),
1692 skiplock
=> get_standard_option
('skiplock'),
1701 my $rpcenv = PVE
::RPCEnvironment
::get
();
1703 my $authuser = $rpcenv->get_user();
1705 my $node = extract_param
($param, 'node');
1707 my $vmid = extract_param
($param, 'vmid');
1709 my $skiplock = extract_param
($param, 'skiplock');
1710 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1711 if $skiplock && $authuser ne 'root@pam';
1713 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1718 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1723 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1726 __PACKAGE__-
>register_method({
1727 name
=> 'vm_shutdown',
1728 path
=> '{vmid}/status/shutdown',
1732 description
=> "Shutdown virtual machine.",
1734 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1737 additionalProperties
=> 0,
1739 node
=> get_standard_option
('pve-node'),
1740 vmid
=> get_standard_option
('pve-vmid'),
1741 skiplock
=> get_standard_option
('skiplock'),
1743 description
=> "Wait maximal timeout seconds.",
1749 description
=> "Make sure the VM stops.",
1755 description
=> "Do not decativate storage volumes.",
1768 my $rpcenv = PVE
::RPCEnvironment
::get
();
1770 my $authuser = $rpcenv->get_user();
1772 my $node = extract_param
($param, 'node');
1774 my $vmid = extract_param
($param, 'vmid');
1776 my $skiplock = extract_param
($param, 'skiplock');
1777 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1778 if $skiplock && $authuser ne 'root@pam';
1780 my $keepActive = extract_param
($param, 'keepActive');
1781 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1782 if $keepActive && $authuser ne 'root@pam';
1784 my $storecfg = PVE
::Storage
::config
();
1789 syslog
('info', "shutdown VM $vmid: $upid\n");
1791 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1792 1, $param->{forceStop
}, $keepActive);
1797 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1800 __PACKAGE__-
>register_method({
1801 name
=> 'vm_suspend',
1802 path
=> '{vmid}/status/suspend',
1806 description
=> "Suspend virtual machine.",
1808 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1811 additionalProperties
=> 0,
1813 node
=> get_standard_option
('pve-node'),
1814 vmid
=> get_standard_option
('pve-vmid'),
1815 skiplock
=> get_standard_option
('skiplock'),
1824 my $rpcenv = PVE
::RPCEnvironment
::get
();
1826 my $authuser = $rpcenv->get_user();
1828 my $node = extract_param
($param, 'node');
1830 my $vmid = extract_param
($param, 'vmid');
1832 my $skiplock = extract_param
($param, 'skiplock');
1833 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1834 if $skiplock && $authuser ne 'root@pam';
1836 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1841 syslog
('info', "suspend VM $vmid: $upid\n");
1843 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1848 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1851 __PACKAGE__-
>register_method({
1852 name
=> 'vm_resume',
1853 path
=> '{vmid}/status/resume',
1857 description
=> "Resume virtual machine.",
1859 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1866 skiplock
=> get_standard_option
('skiplock'),
1875 my $rpcenv = PVE
::RPCEnvironment
::get
();
1877 my $authuser = $rpcenv->get_user();
1879 my $node = extract_param
($param, 'node');
1881 my $vmid = extract_param
($param, 'vmid');
1883 my $skiplock = extract_param
($param, 'skiplock');
1884 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1885 if $skiplock && $authuser ne 'root@pam';
1887 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1892 syslog
('info', "resume VM $vmid: $upid\n");
1894 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1899 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1902 __PACKAGE__-
>register_method({
1903 name
=> 'vm_sendkey',
1904 path
=> '{vmid}/sendkey',
1908 description
=> "Send key event to virtual machine.",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid'),
1917 skiplock
=> get_standard_option
('skiplock'),
1919 description
=> "The key (qemu monitor encoding).",
1924 returns
=> { type
=> 'null'},
1928 my $rpcenv = PVE
::RPCEnvironment
::get
();
1930 my $authuser = $rpcenv->get_user();
1932 my $node = extract_param
($param, 'node');
1934 my $vmid = extract_param
($param, 'vmid');
1936 my $skiplock = extract_param
($param, 'skiplock');
1937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1938 if $skiplock && $authuser ne 'root@pam';
1940 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1945 __PACKAGE__-
>register_method({
1946 name
=> 'vm_feature',
1947 path
=> '{vmid}/feature',
1951 description
=> "Check if feature for virtual machine is available.",
1953 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1956 additionalProperties
=> 0,
1958 node
=> get_standard_option
('pve-node'),
1959 vmid
=> get_standard_option
('pve-vmid'),
1961 description
=> "Feature to check.",
1963 enum
=> [ 'snapshot', 'clone', 'copy' ],
1965 snapname
=> get_standard_option
('pve-snapshot-name', {
1973 hasFeature
=> { type
=> 'boolean' },
1976 items
=> { type
=> 'string' },
1983 my $node = extract_param
($param, 'node');
1985 my $vmid = extract_param
($param, 'vmid');
1987 my $snapname = extract_param
($param, 'snapname');
1989 my $feature = extract_param
($param, 'feature');
1991 my $running = PVE
::QemuServer
::check_running
($vmid);
1993 my $conf = PVE
::QemuServer
::load_config
($vmid);
1996 my $snap = $conf->{snapshots
}->{$snapname};
1997 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2000 my $storecfg = PVE
::Storage
::config
();
2002 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2003 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2006 hasFeature
=> $hasFeature,
2007 nodes
=> [ keys %$nodelist ],
2011 __PACKAGE__-
>register_method({
2013 path
=> '{vmid}/clone',
2017 description
=> "Create a copy of virtual machine/template.",
2019 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2020 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2021 "'Datastore.AllocateSpace' on any used storage.",
2024 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2026 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2027 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2032 additionalProperties
=> 0,
2034 node
=> get_standard_option
('pve-node'),
2035 vmid
=> get_standard_option
('pve-vmid'),
2036 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2039 type
=> 'string', format
=> 'dns-name',
2040 description
=> "Set a name for the new VM.",
2045 description
=> "Description for the new VM.",
2049 type
=> 'string', format
=> 'pve-poolid',
2050 description
=> "Add the new VM to the specified pool.",
2052 snapname
=> get_standard_option
('pve-snapshot-name', {
2056 storage
=> get_standard_option
('pve-storage-id', {
2057 description
=> "Target storage for full clone.",
2062 description
=> "Target format for file storage.",
2066 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2071 description
=> "Create a full copy of all disk. This is always done when " .
2072 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2075 target
=> get_standard_option
('pve-node', {
2076 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2095 my $newid = extract_param
($param, 'newid');
2097 my $pool = extract_param
($param, 'pool');
2099 if (defined($pool)) {
2100 $rpcenv->check_pool_exist($pool);
2103 my $snapname = extract_param
($param, 'snapname');
2105 my $storage = extract_param
($param, 'storage');
2107 my $format = extract_param
($param, 'format');
2109 my $target = extract_param
($param, 'target');
2111 my $localnode = PVE
::INotify
::nodename
();
2113 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2115 PVE
::Cluster
::check_node_exists
($target) if $target;
2117 my $storecfg = PVE
::Storage
::config
();
2120 # check if storage is enabled on local node
2121 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2123 # check if storage is available on target node
2124 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2125 # clone only works if target storage is shared
2126 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2127 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2131 PVE
::Cluster
::check_cfs_quorum
();
2133 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2135 # exclusive lock if VM is running - else shared lock is enough;
2136 my $shared_lock = $running ?
0 : 1;
2140 # do all tests after lock
2141 # we also try to do all tests before we fork the worker
2143 my $conf = PVE
::QemuServer
::load_config
($vmid);
2145 PVE
::QemuServer
::check_lock
($conf);
2147 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2149 die "unexpected state change\n" if $verify_running != $running;
2151 die "snapshot '$snapname' does not exist\n"
2152 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2154 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2156 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2158 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2160 my $conffile = PVE
::QemuServer
::config_file
($newid);
2162 die "unable to create VM $newid: config file already exists\n"
2165 my $newconf = { lock => 'clone' };
2169 foreach my $opt (keys %$oldconf) {
2170 my $value = $oldconf->{$opt};
2172 # do not copy snapshot related info
2173 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2174 $opt eq 'vmstate' || $opt eq 'snapstate';
2176 # always change MAC! address
2177 if ($opt =~ m/^net(\d+)$/) {
2178 my $net = PVE
::QemuServer
::parse_net
($value);
2179 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2180 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2181 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2182 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2183 $newconf->{$opt} = $value; # simply copy configuration
2185 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2186 die "Full clone feature is not available"
2187 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2190 $drives->{$opt} = $drive;
2191 push @$vollist, $drive->{file
};
2194 # copy everything else
2195 $newconf->{$opt} = $value;
2199 delete $newconf->{template
};
2201 if ($param->{name
}) {
2202 $newconf->{name
} = $param->{name
};
2204 if ($oldconf->{name
}) {
2205 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2207 $newconf->{name
} = "Copy-of-VM-$vmid";
2211 if ($param->{description
}) {
2212 $newconf->{description
} = $param->{description
};
2215 # create empty/temp config - this fails if VM already exists on other node
2216 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2221 my $newvollist = [];
2224 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2226 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2228 foreach my $opt (keys %$drives) {
2229 my $drive = $drives->{$opt};
2231 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2232 $newid, $storage, $format, $drive->{full
}, $newvollist);
2234 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2236 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2239 delete $newconf->{lock};
2240 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2243 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2244 die "Failed to move config to node '$target' - rename failed: $!\n"
2245 if !rename($conffile, $newconffile);
2248 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2253 sleep 1; # some storage like rbd need to wait before release volume - really?
2255 foreach my $volid (@$newvollist) {
2256 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2259 die "clone failed: $err";
2265 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2268 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2269 # Aquire exclusive lock lock for $newid
2270 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2275 __PACKAGE__-
>register_method({
2276 name
=> 'move_vm_disk',
2277 path
=> '{vmid}/move_disk',
2281 description
=> "Move volume to different storage.",
2283 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2284 "and 'Datastore.AllocateSpace' permissions on the storage.",
2287 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2288 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2292 additionalProperties
=> 0,
2294 node
=> get_standard_option
('pve-node'),
2295 vmid
=> get_standard_option
('pve-vmid'),
2298 description
=> "The disk you want to move.",
2299 enum
=> [ PVE
::QemuServer
::disknames
() ],
2301 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2304 description
=> "Target Format.",
2305 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2310 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2316 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2324 description
=> "the task ID.",
2329 my $rpcenv = PVE
::RPCEnvironment
::get
();
2331 my $authuser = $rpcenv->get_user();
2333 my $node = extract_param
($param, 'node');
2335 my $vmid = extract_param
($param, 'vmid');
2337 my $digest = extract_param
($param, 'digest');
2339 my $disk = extract_param
($param, 'disk');
2341 my $storeid = extract_param
($param, 'storage');
2343 my $format = extract_param
($param, 'format');
2345 my $storecfg = PVE
::Storage
::config
();
2347 my $updatefn = sub {
2349 my $conf = PVE
::QemuServer
::load_config
($vmid);
2351 die "checksum missmatch (file change by other user?)\n"
2352 if $digest && $digest ne $conf->{digest
};
2354 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2356 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2358 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2360 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2363 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2364 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2368 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2369 (!$format || !$oldfmt || $oldfmt eq $format);
2371 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2373 my $running = PVE
::QemuServer
::check_running
($vmid);
2375 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2379 my $newvollist = [];
2382 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2384 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2385 $vmid, $storeid, $format, 1, $newvollist);
2387 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2389 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2391 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2395 foreach my $volid (@$newvollist) {
2396 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2399 die "storage migration failed: $err";
2402 if ($param->{delete}) {
2403 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2408 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2411 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2414 __PACKAGE__-
>register_method({
2415 name
=> 'migrate_vm',
2416 path
=> '{vmid}/migrate',
2420 description
=> "Migrate virtual machine. Creates a new migration task.",
2422 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2425 additionalProperties
=> 0,
2427 node
=> get_standard_option
('pve-node'),
2428 vmid
=> get_standard_option
('pve-vmid'),
2429 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2432 description
=> "Use online/live migration.",
2437 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2444 description
=> "the task ID.",
2449 my $rpcenv = PVE
::RPCEnvironment
::get
();
2451 my $authuser = $rpcenv->get_user();
2453 my $target = extract_param
($param, 'target');
2455 my $localnode = PVE
::INotify
::nodename
();
2456 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2458 PVE
::Cluster
::check_cfs_quorum
();
2460 PVE
::Cluster
::check_node_exists
($target);
2462 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2464 my $vmid = extract_param
($param, 'vmid');
2466 raise_param_exc
({ force
=> "Only root may use this option." })
2467 if $param->{force
} && $authuser ne 'root@pam';
2470 my $conf = PVE
::QemuServer
::load_config
($vmid);
2472 # try to detect errors early
2474 PVE
::QemuServer
::check_lock
($conf);
2476 if (PVE
::QemuServer
::check_running
($vmid)) {
2477 die "cant migrate running VM without --online\n"
2478 if !$param->{online
};
2481 my $storecfg = PVE
::Storage
::config
();
2482 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2484 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2489 my $service = "pvevm:$vmid";
2491 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2493 print "Executing HA migrate for VM $vmid to node $target\n";
2495 PVE
::Tools
::run_command
($cmd);
2500 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2507 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2510 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2515 __PACKAGE__-
>register_method({
2517 path
=> '{vmid}/monitor',
2521 description
=> "Execute Qemu monitor commands.",
2523 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2526 additionalProperties
=> 0,
2528 node
=> get_standard_option
('pve-node'),
2529 vmid
=> get_standard_option
('pve-vmid'),
2532 description
=> "The monitor command.",
2536 returns
=> { type
=> 'string'},
2540 my $vmid = $param->{vmid
};
2542 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2546 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2548 $res = "ERROR: $@" if $@;
2553 __PACKAGE__-
>register_method({
2554 name
=> 'resize_vm',
2555 path
=> '{vmid}/resize',
2559 description
=> "Extend volume size.",
2561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2564 additionalProperties
=> 0,
2566 node
=> get_standard_option
('pve-node'),
2567 vmid
=> get_standard_option
('pve-vmid'),
2568 skiplock
=> get_standard_option
('skiplock'),
2571 description
=> "The disk you want to resize.",
2572 enum
=> [PVE
::QemuServer
::disknames
()],
2576 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2577 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.",
2581 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2587 returns
=> { type
=> 'null'},
2591 my $rpcenv = PVE
::RPCEnvironment
::get
();
2593 my $authuser = $rpcenv->get_user();
2595 my $node = extract_param
($param, 'node');
2597 my $vmid = extract_param
($param, 'vmid');
2599 my $digest = extract_param
($param, 'digest');
2601 my $disk = extract_param
($param, 'disk');
2603 my $sizestr = extract_param
($param, 'size');
2605 my $skiplock = extract_param
($param, 'skiplock');
2606 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2607 if $skiplock && $authuser ne 'root@pam';
2609 my $storecfg = PVE
::Storage
::config
();
2611 my $updatefn = sub {
2613 my $conf = PVE
::QemuServer
::load_config
($vmid);
2615 die "checksum missmatch (file change by other user?)\n"
2616 if $digest && $digest ne $conf->{digest
};
2617 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2619 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2621 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2623 my $volid = $drive->{file
};
2625 die "disk '$disk' has no associated volume\n" if !$volid;
2627 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2629 die "you can't online resize a virtio windows bootdisk\n"
2630 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2632 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2634 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2636 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2638 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2639 my ($ext, $newsize, $unit) = ($1, $2, $4);
2642 $newsize = $newsize * 1024;
2643 } elsif ($unit eq 'M') {
2644 $newsize = $newsize * 1024 * 1024;
2645 } elsif ($unit eq 'G') {
2646 $newsize = $newsize * 1024 * 1024 * 1024;
2647 } elsif ($unit eq 'T') {
2648 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2651 $newsize += $size if $ext;
2652 $newsize = int($newsize);
2654 die "unable to skrink disk size\n" if $newsize < $size;
2656 return if $size == $newsize;
2658 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2660 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2662 $drive->{size
} = $newsize;
2663 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2665 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2668 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2672 __PACKAGE__-
>register_method({
2673 name
=> 'snapshot_list',
2674 path
=> '{vmid}/snapshot',
2676 description
=> "List all snapshots.",
2678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2681 protected
=> 1, # qemu pid files are only readable by root
2683 additionalProperties
=> 0,
2685 vmid
=> get_standard_option
('pve-vmid'),
2686 node
=> get_standard_option
('pve-node'),
2695 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2700 my $vmid = $param->{vmid
};
2702 my $conf = PVE
::QemuServer
::load_config
($vmid);
2703 my $snaphash = $conf->{snapshots
} || {};
2707 foreach my $name (keys %$snaphash) {
2708 my $d = $snaphash->{$name};
2711 snaptime
=> $d->{snaptime
} || 0,
2712 vmstate
=> $d->{vmstate
} ?
1 : 0,
2713 description
=> $d->{description
} || '',
2715 $item->{parent
} = $d->{parent
} if $d->{parent
};
2716 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2720 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2721 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2722 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2724 push @$res, $current;
2729 __PACKAGE__-
>register_method({
2731 path
=> '{vmid}/snapshot',
2735 description
=> "Snapshot a VM.",
2737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2740 additionalProperties
=> 0,
2742 node
=> get_standard_option
('pve-node'),
2743 vmid
=> get_standard_option
('pve-vmid'),
2744 snapname
=> get_standard_option
('pve-snapshot-name'),
2748 description
=> "Save the vmstate",
2753 description
=> "Freeze the filesystem",
2758 description
=> "A textual description or comment.",
2764 description
=> "the task ID.",
2769 my $rpcenv = PVE
::RPCEnvironment
::get
();
2771 my $authuser = $rpcenv->get_user();
2773 my $node = extract_param
($param, 'node');
2775 my $vmid = extract_param
($param, 'vmid');
2777 my $snapname = extract_param
($param, 'snapname');
2779 die "unable to use snapshot name 'current' (reserved name)\n"
2780 if $snapname eq 'current';
2783 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2784 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2785 $param->{freezefs
}, $param->{description
});
2788 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2791 __PACKAGE__-
>register_method({
2792 name
=> 'snapshot_cmd_idx',
2793 path
=> '{vmid}/snapshot/{snapname}',
2800 additionalProperties
=> 0,
2802 vmid
=> get_standard_option
('pve-vmid'),
2803 node
=> get_standard_option
('pve-node'),
2804 snapname
=> get_standard_option
('pve-snapshot-name'),
2813 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2820 push @$res, { cmd
=> 'rollback' };
2821 push @$res, { cmd
=> 'config' };
2826 __PACKAGE__-
>register_method({
2827 name
=> 'update_snapshot_config',
2828 path
=> '{vmid}/snapshot/{snapname}/config',
2832 description
=> "Update snapshot metadata.",
2834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2837 additionalProperties
=> 0,
2839 node
=> get_standard_option
('pve-node'),
2840 vmid
=> get_standard_option
('pve-vmid'),
2841 snapname
=> get_standard_option
('pve-snapshot-name'),
2845 description
=> "A textual description or comment.",
2849 returns
=> { type
=> 'null' },
2853 my $rpcenv = PVE
::RPCEnvironment
::get
();
2855 my $authuser = $rpcenv->get_user();
2857 my $vmid = extract_param
($param, 'vmid');
2859 my $snapname = extract_param
($param, 'snapname');
2861 return undef if !defined($param->{description
});
2863 my $updatefn = sub {
2865 my $conf = PVE
::QemuServer
::load_config
($vmid);
2867 PVE
::QemuServer
::check_lock
($conf);
2869 my $snap = $conf->{snapshots
}->{$snapname};
2871 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2873 $snap->{description
} = $param->{description
} if defined($param->{description
});
2875 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2878 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2883 __PACKAGE__-
>register_method({
2884 name
=> 'get_snapshot_config',
2885 path
=> '{vmid}/snapshot/{snapname}/config',
2888 description
=> "Get snapshot configuration",
2890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2893 additionalProperties
=> 0,
2895 node
=> get_standard_option
('pve-node'),
2896 vmid
=> get_standard_option
('pve-vmid'),
2897 snapname
=> get_standard_option
('pve-snapshot-name'),
2900 returns
=> { type
=> "object" },
2904 my $rpcenv = PVE
::RPCEnvironment
::get
();
2906 my $authuser = $rpcenv->get_user();
2908 my $vmid = extract_param
($param, 'vmid');
2910 my $snapname = extract_param
($param, 'snapname');
2912 my $conf = PVE
::QemuServer
::load_config
($vmid);
2914 my $snap = $conf->{snapshots
}->{$snapname};
2916 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2921 __PACKAGE__-
>register_method({
2923 path
=> '{vmid}/snapshot/{snapname}/rollback',
2927 description
=> "Rollback VM state to specified snapshot.",
2929 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2932 additionalProperties
=> 0,
2934 node
=> get_standard_option
('pve-node'),
2935 vmid
=> get_standard_option
('pve-vmid'),
2936 snapname
=> get_standard_option
('pve-snapshot-name'),
2941 description
=> "the task ID.",
2946 my $rpcenv = PVE
::RPCEnvironment
::get
();
2948 my $authuser = $rpcenv->get_user();
2950 my $node = extract_param
($param, 'node');
2952 my $vmid = extract_param
($param, 'vmid');
2954 my $snapname = extract_param
($param, 'snapname');
2957 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2958 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2961 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2964 __PACKAGE__-
>register_method({
2965 name
=> 'delsnapshot',
2966 path
=> '{vmid}/snapshot/{snapname}',
2970 description
=> "Delete a VM snapshot.",
2972 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2975 additionalProperties
=> 0,
2977 node
=> get_standard_option
('pve-node'),
2978 vmid
=> get_standard_option
('pve-vmid'),
2979 snapname
=> get_standard_option
('pve-snapshot-name'),
2983 description
=> "For removal from config file, even if removing disk snapshots fails.",
2989 description
=> "the task ID.",
2994 my $rpcenv = PVE
::RPCEnvironment
::get
();
2996 my $authuser = $rpcenv->get_user();
2998 my $node = extract_param
($param, 'node');
3000 my $vmid = extract_param
($param, 'vmid');
3002 my $snapname = extract_param
($param, 'snapname');
3005 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3006 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3009 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3012 __PACKAGE__-
>register_method({
3014 path
=> '{vmid}/template',
3018 description
=> "Create a Template.",
3020 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3021 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3024 additionalProperties
=> 0,
3026 node
=> get_standard_option
('pve-node'),
3027 vmid
=> get_standard_option
('pve-vmid'),
3031 description
=> "If you want to convert only 1 disk to base image.",
3032 enum
=> [PVE
::QemuServer
::disknames
()],
3037 returns
=> { type
=> 'null'},
3041 my $rpcenv = PVE
::RPCEnvironment
::get
();
3043 my $authuser = $rpcenv->get_user();
3045 my $node = extract_param
($param, 'node');
3047 my $vmid = extract_param
($param, 'vmid');
3049 my $disk = extract_param
($param, 'disk');
3051 my $updatefn = sub {
3053 my $conf = PVE
::QemuServer
::load_config
($vmid);
3055 PVE
::QemuServer
::check_lock
($conf);
3057 die "unable to create template, because VM contains snapshots\n"
3058 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3060 die "you can't convert a template to a template\n"
3061 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3063 die "you can't convert a VM to template if VM is running\n"
3064 if PVE
::QemuServer
::check_running
($vmid);
3067 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3070 $conf->{template
} = 1;
3071 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3073 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3076 PVE
::QemuServer
::lock_config
($vmid, $updatefn);