1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
134 my $volid_is_new = 1;
137 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
138 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
143 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
146 $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
148 $disk->{size
} = $size;
150 $foundvolid = undef if $@;
152 die "volume $volid does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
159 # free allocated images on error
161 syslog
('err', "VM $vmid creating disks failed");
162 foreach my $volid (@$vollist) {
163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
169 # modify vm config if everything went well
170 foreach my $ds (keys %$res) {
171 $conf->{$ds} = $res->{$ds};
177 my $check_vm_modify_config_perm = sub {
178 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
180 return 1 if $authuser eq 'root@pam';
182 foreach my $opt (@$key_list) {
183 # disk checks need to be done somewhere else
184 next if PVE
::QemuServer
::valid_drivename
($opt);
186 if ($opt eq 'sockets' || $opt eq 'cores' ||
187 $opt eq 'cpu' || $opt eq 'smp' ||
188 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
190 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
192 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
193 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
194 } elsif ($opt eq 'args' || $opt eq 'lock') {
195 die "only root can set '$opt' config\n";
196 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
197 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
199 } elsif ($opt =~ m/^net\d+$/) {
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
202 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
209 __PACKAGE__-
>register_method({
213 description
=> "Virtual machine index (per node).",
215 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
219 protected
=> 1, # qemu pid files are only readable by root
221 additionalProperties
=> 0,
223 node
=> get_standard_option
('pve-node'),
232 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
237 my $rpcenv = PVE
::RPCEnvironment
::get
();
238 my $authuser = $rpcenv->get_user();
240 my $vmstatus = PVE
::QemuServer
::vmstatus
();
243 foreach my $vmid (keys %$vmstatus) {
244 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
246 my $data = $vmstatus->{$vmid};
247 $data->{vmid
} = $vmid;
256 __PACKAGE__-
>register_method({
260 description
=> "Create or restore a virtual machine.",
262 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
263 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
264 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
265 user
=> 'all', # check inside
270 additionalProperties
=> 0,
271 properties
=> PVE
::QemuServer
::json_config_properties
(
273 node
=> get_standard_option
('pve-node'),
274 vmid
=> get_standard_option
('pve-vmid'),
276 description
=> "The backup file.",
281 storage
=> get_standard_option
('pve-storage-id', {
282 description
=> "Default storage.",
288 description
=> "Allow to overwrite existing VM.",
289 requires
=> 'archive',
294 description
=> "Assign a unique random ethernet address.",
295 requires
=> 'archive',
299 type
=> 'string', format
=> 'pve-poolid',
300 description
=> "Add the VM to the specified pool.",
310 my $rpcenv = PVE
::RPCEnvironment
::get
();
312 my $authuser = $rpcenv->get_user();
314 my $node = extract_param
($param, 'node');
316 my $vmid = extract_param
($param, 'vmid');
318 my $archive = extract_param
($param, 'archive');
320 my $storage = extract_param
($param, 'storage');
322 my $force = extract_param
($param, 'force');
324 my $unique = extract_param
($param, 'unique');
326 my $pool = extract_param
($param, 'pool');
328 my $filename = PVE
::QemuServer
::config_file
($vmid);
330 my $storecfg = PVE
::Storage
::config
();
332 PVE
::Cluster
::check_cfs_quorum
();
334 if (defined($pool)) {
335 $rpcenv->check_pool_exist($pool);
338 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
339 if defined($storage);
341 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
343 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
345 } elsif ($archive && $force && (-f
$filename) &&
346 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
347 # OK: user has VM.Backup permissions, and want to restore an existing VM
353 &$resolve_cdrom_alias($param);
355 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
357 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
359 foreach my $opt (keys %$param) {
360 if (PVE
::QemuServer
::valid_drivename
($opt)) {
361 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
362 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
364 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
365 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
369 PVE
::QemuServer
::add_random_macs
($param);
371 my $keystr = join(' ', keys %$param);
372 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
374 if ($archive eq '-') {
375 die "pipe requires cli environment\n"
376 if $rpcenv->{type
} ne 'cli';
378 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
380 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
381 if PVE
::Storage
::parse_volume_id
($archive, 1);
383 die "can't find archive file '$archive'\n" if !($path && -f
$path);
388 my $restorefn = sub {
390 # fixme: this test does not work if VM exists on other node!
392 die "unable to restore vm $vmid: config file already exists\n"
395 die "unable to restore vm $vmid: vm is running\n"
396 if PVE
::QemuServer
::check_running
($vmid);
400 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
403 unique
=> $unique });
405 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
408 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
414 die "unable to create vm $vmid: config file already exists\n"
425 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
427 # try to be smart about bootdisk
428 my @disks = PVE
::QemuServer
::disknames
();
430 foreach my $ds (reverse @disks) {
431 next if !$conf->{$ds};
432 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
433 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
437 if (!$conf->{bootdisk
} && $firstdisk) {
438 $conf->{bootdisk
} = $firstdisk;
441 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
447 foreach my $volid (@$vollist) {
448 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
451 die "create failed - $err";
454 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
457 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
460 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
463 __PACKAGE__-
>register_method({
468 description
=> "Directory index",
473 additionalProperties
=> 0,
475 node
=> get_standard_option
('pve-node'),
476 vmid
=> get_standard_option
('pve-vmid'),
484 subdir
=> { type
=> 'string' },
487 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
493 { subdir
=> 'config' },
494 { subdir
=> 'status' },
495 { subdir
=> 'unlink' },
496 { subdir
=> 'vncproxy' },
497 { subdir
=> 'migrate' },
498 { subdir
=> 'resize' },
499 { subdir
=> 'move' },
501 { subdir
=> 'rrddata' },
502 { subdir
=> 'monitor' },
503 { subdir
=> 'snapshot' },
509 __PACKAGE__-
>register_method({
511 path
=> '{vmid}/rrd',
513 protected
=> 1, # fixme: can we avoid that?
515 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
517 description
=> "Read VM RRD statistics (returns PNG)",
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
522 vmid
=> get_standard_option
('pve-vmid'),
524 description
=> "Specify the time frame you are interested in.",
526 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
529 description
=> "The list of datasources you want to display.",
530 type
=> 'string', format
=> 'pve-configid-list',
533 description
=> "The RRD consolidation function",
535 enum
=> [ 'AVERAGE', 'MAX' ],
543 filename
=> { type
=> 'string' },
549 return PVE
::Cluster
::create_rrd_graph
(
550 "pve2-vm/$param->{vmid}", $param->{timeframe
},
551 $param->{ds
}, $param->{cf
});
555 __PACKAGE__-
>register_method({
557 path
=> '{vmid}/rrddata',
559 protected
=> 1, # fixme: can we avoid that?
561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
563 description
=> "Read VM RRD statistics",
565 additionalProperties
=> 0,
567 node
=> get_standard_option
('pve-node'),
568 vmid
=> get_standard_option
('pve-vmid'),
570 description
=> "Specify the time frame you are interested in.",
572 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
575 description
=> "The RRD consolidation function",
577 enum
=> [ 'AVERAGE', 'MAX' ],
592 return PVE
::Cluster
::create_rrd_data
(
593 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
597 __PACKAGE__-
>register_method({
599 path
=> '{vmid}/config',
602 description
=> "Get virtual machine configuration.",
604 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
607 additionalProperties
=> 0,
609 node
=> get_standard_option
('pve-node'),
610 vmid
=> get_standard_option
('pve-vmid'),
618 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
625 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
627 delete $conf->{snapshots
};
632 my $vm_is_volid_owner = sub {
633 my ($storecfg, $vmid, $volid) =@_;
635 if ($volid !~ m
|^/|) {
637 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
638 if ($owner && ($owner == $vmid)) {
646 my $test_deallocate_drive = sub {
647 my ($storecfg, $vmid, $key, $drive, $force) = @_;
649 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
650 my $volid = $drive->{file
};
651 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
652 if ($force || $key =~ m/^unused/) {
653 my $sid = PVE
::Storage
::parse_volume_id
($volid);
662 my $delete_drive = sub {
663 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
665 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
666 my $volid = $drive->{file
};
668 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
669 if ($force || $key =~ m/^unused/) {
671 # check if the disk is really unused
672 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
673 my $path = PVE
::Storage
::path
($storecfg, $volid);
675 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
676 if $used_paths->{$path};
678 PVE
::Storage
::vdisk_free
($storecfg, $volid);
682 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
687 delete $conf->{$key};
690 my $vmconfig_delete_option = sub {
691 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
693 return if !defined($conf->{$opt});
695 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
698 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
700 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
701 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
702 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
706 my $unplugwarning = "";
707 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
708 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
709 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
710 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
711 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
712 $unplugwarning = "<br>verify that your guest support acpi hotplug";
715 if($opt eq 'tablet'){
716 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
718 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
722 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
723 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
725 delete $conf->{$opt};
728 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
731 my $safe_num_ne = sub {
734 return 0 if !defined($a) && !defined($b);
735 return 1 if !defined($a);
736 return 1 if !defined($b);
741 my $vmconfig_update_disk = sub {
742 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
744 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
746 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
747 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
749 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
754 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
756 my $media = $drive->{media
} || 'disk';
757 my $oldmedia = $old_drive->{media
} || 'disk';
758 die "unable to change media type\n" if $media ne $oldmedia;
760 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
761 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
763 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
764 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
767 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
768 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
769 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
770 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
771 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
772 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
773 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
774 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
775 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
776 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
781 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
782 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
784 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
785 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
787 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
789 if (PVE
::QemuServer
::check_running
($vmid)) {
790 if ($drive->{file
} eq 'none') {
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
793 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
794 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
795 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
799 } else { # hotplug new disks
801 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
805 my $vmconfig_update_net = sub {
806 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
808 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
809 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
810 my $newnet = PVE
::QemuServer
::parse_net
($value);
812 if($oldnet->{model
} ne $newnet->{model
}){
813 #if model change, we try to hot-unplug
814 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
817 if($newnet->{bridge
} && $oldnet->{bridge
}){
818 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
820 if($newnet->{rate
} ne $oldnet->{rate
}){
821 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
824 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
825 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
826 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
830 #if bridge/nat mode change, we try to hot-unplug
831 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
836 $conf->{$opt} = $value;
837 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
838 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
840 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
842 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
845 # POST/PUT {vmid}/config implementation
847 # The original API used PUT (idempotent) an we assumed that all operations
848 # are fast. But it turned out that almost any configuration change can
849 # involve hot-plug actions, or disk alloc/free. Such actions can take long
850 # time to complete and have side effects (not idempotent).
852 # The new implementation uses POST and forks a worker process. We added
853 # a new option 'background_delay'. If specified we wait up to
854 # 'background_delay' second for the worker task to complete. It returns null
855 # if the task is finished within that time, else we return the UPID.
857 my $update_vm_api = sub {
858 my ($param, $sync) = @_;
860 my $rpcenv = PVE
::RPCEnvironment
::get
();
862 my $authuser = $rpcenv->get_user();
864 my $node = extract_param
($param, 'node');
866 my $vmid = extract_param
($param, 'vmid');
868 my $digest = extract_param
($param, 'digest');
870 my $background_delay = extract_param
($param, 'background_delay');
872 my @paramarr = (); # used for log message
873 foreach my $key (keys %$param) {
874 push @paramarr, "-$key", $param->{$key};
877 my $skiplock = extract_param
($param, 'skiplock');
878 raise_param_exc
({ skiplock
=> "Only root may use this option." })
879 if $skiplock && $authuser ne 'root@pam';
881 my $delete_str = extract_param
($param, 'delete');
883 my $force = extract_param
($param, 'force');
885 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
887 my $storecfg = PVE
::Storage
::config
();
889 my $defaults = PVE
::QemuServer
::load_defaults
();
891 &$resolve_cdrom_alias($param);
893 # now try to verify all parameters
896 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
897 $opt = 'ide2' if $opt eq 'cdrom';
898 raise_param_exc
({ delete => "you can't use '-$opt' and " .
899 "-delete $opt' at the same time" })
900 if defined($param->{$opt});
902 if (!PVE
::QemuServer
::option_exists
($opt)) {
903 raise_param_exc
({ delete => "unknown option '$opt'" });
909 foreach my $opt (keys %$param) {
910 if (PVE
::QemuServer
::valid_drivename
($opt)) {
912 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
913 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
914 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
915 } elsif ($opt =~ m/^net(\d+)$/) {
917 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
918 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
922 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
924 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
926 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
930 my $conf = PVE
::QemuServer
::load_config
($vmid);
932 die "checksum missmatch (file change by other user?)\n"
933 if $digest && $digest ne $conf->{digest
};
935 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
937 if ($param->{memory
} || defined($param->{balloon
})) {
938 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
939 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
941 die "balloon value too large (must be smaller than assigned memory)\n"
942 if $balloon && $balloon > $maxmem;
945 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
949 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
951 foreach my $opt (@delete) { # delete
952 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
953 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
956 my $running = PVE
::QemuServer
::check_running
($vmid);
958 foreach my $opt (keys %$param) { # add/change
960 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
962 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
964 if (PVE
::QemuServer
::valid_drivename
($opt)) {
966 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
967 $opt, $param->{$opt}, $force);
969 } elsif ($opt =~ m/^net(\d+)$/) { #nics
971 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
972 $opt, $param->{$opt});
976 if($opt eq 'tablet' && $param->{$opt} == 1){
977 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
978 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
979 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
982 $conf->{$opt} = $param->{$opt};
983 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
987 # allow manual ballooning if shares is set to zero
988 if ($running && defined($param->{balloon
}) &&
989 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
990 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
991 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
999 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1001 if ($background_delay) {
1003 # Note: It would be better to do that in the Event based HTTPServer
1004 # to avoid blocking call to sleep.
1006 my $end_time = time() + $background_delay;
1008 my $task = PVE
::Tools
::upid_decode
($upid);
1011 while (time() < $end_time) {
1012 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1014 sleep(1); # this gets interrupted when child process ends
1018 my $status = PVE
::Tools
::upid_read_status
($upid);
1019 return undef if $status eq 'OK';
1028 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1031 my $vm_config_perm_list = [
1036 'VM.Config.Network',
1038 'VM.Config.Options',
1041 __PACKAGE__-
>register_method({
1042 name
=> 'update_vm_async',
1043 path
=> '{vmid}/config',
1047 description
=> "Set virtual machine options (asynchrounous API).",
1049 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1052 additionalProperties
=> 0,
1053 properties
=> PVE
::QemuServer
::json_config_properties
(
1055 node
=> get_standard_option
('pve-node'),
1056 vmid
=> get_standard_option
('pve-vmid'),
1057 skiplock
=> get_standard_option
('skiplock'),
1059 type
=> 'string', format
=> 'pve-configid-list',
1060 description
=> "A list of settings you want to delete.",
1065 description
=> $opt_force_description,
1067 requires
=> 'delete',
1071 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1075 background_delay
=> {
1077 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1088 code
=> $update_vm_api,
1091 __PACKAGE__-
>register_method({
1092 name
=> 'update_vm',
1093 path
=> '{vmid}/config',
1097 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1099 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1102 additionalProperties
=> 0,
1103 properties
=> PVE
::QemuServer
::json_config_properties
(
1105 node
=> get_standard_option
('pve-node'),
1106 vmid
=> get_standard_option
('pve-vmid'),
1107 skiplock
=> get_standard_option
('skiplock'),
1109 type
=> 'string', format
=> 'pve-configid-list',
1110 description
=> "A list of settings you want to delete.",
1115 description
=> $opt_force_description,
1117 requires
=> 'delete',
1121 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1127 returns
=> { type
=> 'null' },
1130 &$update_vm_api($param, 1);
1136 __PACKAGE__-
>register_method({
1137 name
=> 'destroy_vm',
1142 description
=> "Destroy the vm (also delete all used/owned volumes).",
1144 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1147 additionalProperties
=> 0,
1149 node
=> get_standard_option
('pve-node'),
1150 vmid
=> get_standard_option
('pve-vmid'),
1151 skiplock
=> get_standard_option
('skiplock'),
1160 my $rpcenv = PVE
::RPCEnvironment
::get
();
1162 my $authuser = $rpcenv->get_user();
1164 my $vmid = $param->{vmid
};
1166 my $skiplock = $param->{skiplock
};
1167 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1168 if $skiplock && $authuser ne 'root@pam';
1171 my $conf = PVE
::QemuServer
::load_config
($vmid);
1173 my $storecfg = PVE
::Storage
::config
();
1175 my $delVMfromPoolFn = sub {
1176 my $usercfg = cfs_read_file
("user.cfg");
1177 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1178 if (my $data = $usercfg->{pools
}->{$pool}) {
1179 delete $data->{vms
}->{$vmid};
1180 delete $usercfg->{vms
}->{$vmid};
1181 cfs_write_file
("user.cfg", $usercfg);
1189 syslog
('info', "destroy VM $vmid: $upid\n");
1191 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1193 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1196 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1199 __PACKAGE__-
>register_method({
1201 path
=> '{vmid}/unlink',
1205 description
=> "Unlink/delete disk images.",
1207 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1210 additionalProperties
=> 0,
1212 node
=> get_standard_option
('pve-node'),
1213 vmid
=> get_standard_option
('pve-vmid'),
1215 type
=> 'string', format
=> 'pve-configid-list',
1216 description
=> "A list of disk IDs you want to delete.",
1220 description
=> $opt_force_description,
1225 returns
=> { type
=> 'null'},
1229 $param->{delete} = extract_param
($param, 'idlist');
1231 __PACKAGE__-
>update_vm($param);
1238 __PACKAGE__-
>register_method({
1240 path
=> '{vmid}/vncproxy',
1244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1246 description
=> "Creates a TCP VNC proxy connections.",
1248 additionalProperties
=> 0,
1250 node
=> get_standard_option
('pve-node'),
1251 vmid
=> get_standard_option
('pve-vmid'),
1255 additionalProperties
=> 0,
1257 user
=> { type
=> 'string' },
1258 ticket
=> { type
=> 'string' },
1259 cert
=> { type
=> 'string' },
1260 port
=> { type
=> 'integer' },
1261 upid
=> { type
=> 'string' },
1267 my $rpcenv = PVE
::RPCEnvironment
::get
();
1269 my $authuser = $rpcenv->get_user();
1271 my $vmid = $param->{vmid
};
1272 my $node = $param->{node
};
1274 my $authpath = "/vms/$vmid";
1276 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1278 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1281 my $port = PVE
::Tools
::next_vnc_port
();
1285 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1286 $remip = PVE
::Cluster
::remote_node_ip
($node);
1289 # NOTE: kvm VNC traffic is already TLS encrypted
1290 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1297 syslog
('info', "starting vnc proxy $upid\n");
1299 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1301 my $qmstr = join(' ', @$qmcmd);
1303 # also redirect stderr (else we get RFB protocol errors)
1304 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1306 PVE
::Tools
::run_command
($cmd);
1311 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1313 PVE
::Tools
::wait_for_vnc_port
($port);
1324 __PACKAGE__-
>register_method({
1326 path
=> '{vmid}/status',
1329 description
=> "Directory index",
1334 additionalProperties
=> 0,
1336 node
=> get_standard_option
('pve-node'),
1337 vmid
=> get_standard_option
('pve-vmid'),
1345 subdir
=> { type
=> 'string' },
1348 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1354 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1357 { subdir
=> 'current' },
1358 { subdir
=> 'start' },
1359 { subdir
=> 'stop' },
1365 my $vm_is_ha_managed = sub {
1368 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1369 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1375 __PACKAGE__-
>register_method({
1376 name
=> 'vm_status',
1377 path
=> '{vmid}/status/current',
1380 protected
=> 1, # qemu pid files are only readable by root
1381 description
=> "Get virtual machine status.",
1383 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1386 additionalProperties
=> 0,
1388 node
=> get_standard_option
('pve-node'),
1389 vmid
=> get_standard_option
('pve-vmid'),
1392 returns
=> { type
=> 'object' },
1397 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1399 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1400 my $status = $vmstatus->{$param->{vmid
}};
1402 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1407 __PACKAGE__-
>register_method({
1409 path
=> '{vmid}/status/start',
1413 description
=> "Start virtual machine.",
1415 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1418 additionalProperties
=> 0,
1420 node
=> get_standard_option
('pve-node'),
1421 vmid
=> get_standard_option
('pve-vmid'),
1422 skiplock
=> get_standard_option
('skiplock'),
1423 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1424 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1425 machine
=> get_standard_option
('pve-qm-machine'),
1434 my $rpcenv = PVE
::RPCEnvironment
::get
();
1436 my $authuser = $rpcenv->get_user();
1438 my $node = extract_param
($param, 'node');
1440 my $vmid = extract_param
($param, 'vmid');
1442 my $machine = extract_param
($param, 'machine');
1444 my $stateuri = extract_param
($param, 'stateuri');
1445 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1446 if $stateuri && $authuser ne 'root@pam';
1448 my $skiplock = extract_param
($param, 'skiplock');
1449 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1450 if $skiplock && $authuser ne 'root@pam';
1452 my $migratedfrom = extract_param
($param, 'migratedfrom');
1453 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1454 if $migratedfrom && $authuser ne 'root@pam';
1456 my $storecfg = PVE
::Storage
::config
();
1458 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1459 $rpcenv->{type
} ne 'ha') {
1464 my $service = "pvevm:$vmid";
1466 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1468 print "Executing HA start for VM $vmid\n";
1470 PVE
::Tools
::run_command
($cmd);
1475 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1482 syslog
('info', "start VM $vmid: $upid\n");
1484 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1489 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1493 __PACKAGE__-
>register_method({
1495 path
=> '{vmid}/status/stop',
1499 description
=> "Stop virtual machine.",
1501 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1504 additionalProperties
=> 0,
1506 node
=> get_standard_option
('pve-node'),
1507 vmid
=> get_standard_option
('pve-vmid'),
1508 skiplock
=> get_standard_option
('skiplock'),
1509 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1511 description
=> "Wait maximal timeout seconds.",
1517 description
=> "Do not decativate storage volumes.",
1530 my $rpcenv = PVE
::RPCEnvironment
::get
();
1532 my $authuser = $rpcenv->get_user();
1534 my $node = extract_param
($param, 'node');
1536 my $vmid = extract_param
($param, 'vmid');
1538 my $skiplock = extract_param
($param, 'skiplock');
1539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1540 if $skiplock && $authuser ne 'root@pam';
1542 my $keepActive = extract_param
($param, 'keepActive');
1543 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1544 if $keepActive && $authuser ne 'root@pam';
1546 my $migratedfrom = extract_param
($param, 'migratedfrom');
1547 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1548 if $migratedfrom && $authuser ne 'root@pam';
1551 my $storecfg = PVE
::Storage
::config
();
1553 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1558 my $service = "pvevm:$vmid";
1560 my $cmd = ['clusvcadm', '-d', $service];
1562 print "Executing HA stop for VM $vmid\n";
1564 PVE
::Tools
::run_command
($cmd);
1569 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1575 syslog
('info', "stop VM $vmid: $upid\n");
1577 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1578 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1583 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1587 __PACKAGE__-
>register_method({
1589 path
=> '{vmid}/status/reset',
1593 description
=> "Reset virtual machine.",
1595 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1598 additionalProperties
=> 0,
1600 node
=> get_standard_option
('pve-node'),
1601 vmid
=> get_standard_option
('pve-vmid'),
1602 skiplock
=> get_standard_option
('skiplock'),
1611 my $rpcenv = PVE
::RPCEnvironment
::get
();
1613 my $authuser = $rpcenv->get_user();
1615 my $node = extract_param
($param, 'node');
1617 my $vmid = extract_param
($param, 'vmid');
1619 my $skiplock = extract_param
($param, 'skiplock');
1620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1621 if $skiplock && $authuser ne 'root@pam';
1623 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1628 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1633 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1636 __PACKAGE__-
>register_method({
1637 name
=> 'vm_shutdown',
1638 path
=> '{vmid}/status/shutdown',
1642 description
=> "Shutdown virtual machine.",
1644 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1647 additionalProperties
=> 0,
1649 node
=> get_standard_option
('pve-node'),
1650 vmid
=> get_standard_option
('pve-vmid'),
1651 skiplock
=> get_standard_option
('skiplock'),
1653 description
=> "Wait maximal timeout seconds.",
1659 description
=> "Make sure the VM stops.",
1665 description
=> "Do not decativate storage volumes.",
1678 my $rpcenv = PVE
::RPCEnvironment
::get
();
1680 my $authuser = $rpcenv->get_user();
1682 my $node = extract_param
($param, 'node');
1684 my $vmid = extract_param
($param, 'vmid');
1686 my $skiplock = extract_param
($param, 'skiplock');
1687 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1688 if $skiplock && $authuser ne 'root@pam';
1690 my $keepActive = extract_param
($param, 'keepActive');
1691 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1692 if $keepActive && $authuser ne 'root@pam';
1694 my $storecfg = PVE
::Storage
::config
();
1699 syslog
('info', "shutdown VM $vmid: $upid\n");
1701 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1702 1, $param->{forceStop
}, $keepActive);
1707 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1710 __PACKAGE__-
>register_method({
1711 name
=> 'vm_suspend',
1712 path
=> '{vmid}/status/suspend',
1716 description
=> "Suspend virtual machine.",
1718 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1721 additionalProperties
=> 0,
1723 node
=> get_standard_option
('pve-node'),
1724 vmid
=> get_standard_option
('pve-vmid'),
1725 skiplock
=> get_standard_option
('skiplock'),
1734 my $rpcenv = PVE
::RPCEnvironment
::get
();
1736 my $authuser = $rpcenv->get_user();
1738 my $node = extract_param
($param, 'node');
1740 my $vmid = extract_param
($param, 'vmid');
1742 my $skiplock = extract_param
($param, 'skiplock');
1743 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1744 if $skiplock && $authuser ne 'root@pam';
1746 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1751 syslog
('info', "suspend VM $vmid: $upid\n");
1753 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1758 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1761 __PACKAGE__-
>register_method({
1762 name
=> 'vm_resume',
1763 path
=> '{vmid}/status/resume',
1767 description
=> "Resume virtual machine.",
1769 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1772 additionalProperties
=> 0,
1774 node
=> get_standard_option
('pve-node'),
1775 vmid
=> get_standard_option
('pve-vmid'),
1776 skiplock
=> get_standard_option
('skiplock'),
1785 my $rpcenv = PVE
::RPCEnvironment
::get
();
1787 my $authuser = $rpcenv->get_user();
1789 my $node = extract_param
($param, 'node');
1791 my $vmid = extract_param
($param, 'vmid');
1793 my $skiplock = extract_param
($param, 'skiplock');
1794 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1795 if $skiplock && $authuser ne 'root@pam';
1797 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1802 syslog
('info', "resume VM $vmid: $upid\n");
1804 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1809 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1812 __PACKAGE__-
>register_method({
1813 name
=> 'vm_sendkey',
1814 path
=> '{vmid}/sendkey',
1818 description
=> "Send key event to virtual machine.",
1820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1823 additionalProperties
=> 0,
1825 node
=> get_standard_option
('pve-node'),
1826 vmid
=> get_standard_option
('pve-vmid'),
1827 skiplock
=> get_standard_option
('skiplock'),
1829 description
=> "The key (qemu monitor encoding).",
1834 returns
=> { type
=> 'null'},
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 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1855 __PACKAGE__-
>register_method({
1856 name
=> 'vm_feature',
1857 path
=> '{vmid}/feature',
1861 description
=> "Check if feature for virtual machine is available.",
1863 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1866 additionalProperties
=> 0,
1868 node
=> get_standard_option
('pve-node'),
1869 vmid
=> get_standard_option
('pve-vmid'),
1871 description
=> "Feature to check.",
1873 enum
=> [ 'snapshot', 'clone', 'copy' ],
1875 snapname
=> get_standard_option
('pve-snapshot-name', {
1883 hasFeature
=> { type
=> 'boolean' },
1886 items
=> { type
=> 'string' },
1893 my $node = extract_param
($param, 'node');
1895 my $vmid = extract_param
($param, 'vmid');
1897 my $snapname = extract_param
($param, 'snapname');
1899 my $feature = extract_param
($param, 'feature');
1901 my $running = PVE
::QemuServer
::check_running
($vmid);
1903 my $conf = PVE
::QemuServer
::load_config
($vmid);
1906 my $snap = $conf->{snapshots
}->{$snapname};
1907 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1910 my $storecfg = PVE
::Storage
::config
();
1912 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1913 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1916 hasFeature
=> $hasFeature,
1917 nodes
=> [ keys %$nodelist ],
1921 __PACKAGE__-
>register_method({
1923 path
=> '{vmid}/clone',
1927 description
=> "Create a copy of virtual machine/template.",
1929 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1930 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1931 "'Datastore.AllocateSpace' on any used storage.",
1934 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1936 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1937 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1942 additionalProperties
=> 0,
1944 node
=> get_standard_option
('pve-node'),
1945 vmid
=> get_standard_option
('pve-vmid'),
1946 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1949 type
=> 'string', format
=> 'dns-name',
1950 description
=> "Set a name for the new VM.",
1955 description
=> "Description for the new VM.",
1959 type
=> 'string', format
=> 'pve-poolid',
1960 description
=> "Add the new VM to the specified pool.",
1962 snapname
=> get_standard_option
('pve-snapshot-name', {
1966 storage
=> get_standard_option
('pve-storage-id', {
1967 description
=> "Target storage for full clone.",
1972 description
=> "Target format for file storage.",
1976 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1981 description
=> "Create a full copy of all disk. This is always done when " .
1982 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1985 target
=> get_standard_option
('pve-node', {
1986 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1997 my $rpcenv = PVE
::RPCEnvironment
::get
();
1999 my $authuser = $rpcenv->get_user();
2001 my $node = extract_param
($param, 'node');
2003 my $vmid = extract_param
($param, 'vmid');
2005 my $newid = extract_param
($param, 'newid');
2007 my $pool = extract_param
($param, 'pool');
2009 if (defined($pool)) {
2010 $rpcenv->check_pool_exist($pool);
2013 my $snapname = extract_param
($param, 'snapname');
2015 my $storage = extract_param
($param, 'storage');
2017 my $format = extract_param
($param, 'format');
2019 my $target = extract_param
($param, 'target');
2021 my $localnode = PVE
::INotify
::nodename
();
2023 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2025 PVE
::Cluster
::check_node_exists
($target) if $target;
2027 my $storecfg = PVE
::Storage
::config
();
2030 # check if storage is enabled on local node
2031 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2033 # check if storage is available on target node
2034 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2035 # clone only works if target storage is shared
2036 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2037 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2041 PVE
::Cluster
::check_cfs_quorum
();
2043 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2045 # exclusive lock if VM is running - else shared lock is enough;
2046 my $shared_lock = $running ?
0 : 1;
2050 # do all tests after lock
2051 # we also try to do all tests before we fork the worker
2053 my $conf = PVE
::QemuServer
::load_config
($vmid);
2055 PVE
::QemuServer
::check_lock
($conf);
2057 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2059 die "unexpected state change\n" if $verify_running != $running;
2061 die "snapshot '$snapname' does not exist\n"
2062 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2064 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2066 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2068 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2070 my $conffile = PVE
::QemuServer
::config_file
($newid);
2072 die "unable to create VM $newid: config file already exists\n"
2075 my $newconf = { lock => 'clone' };
2079 foreach my $opt (keys %$oldconf) {
2080 my $value = $oldconf->{$opt};
2082 # do not copy snapshot related info
2083 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2084 $opt eq 'vmstate' || $opt eq 'snapstate';
2086 # always change MAC! address
2087 if ($opt =~ m/^net(\d+)$/) {
2088 my $net = PVE
::QemuServer
::parse_net
($value);
2089 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2090 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2091 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2092 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2093 $newconf->{$opt} = $value; # simply copy configuration
2095 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2096 die "Full clone feature is not available"
2097 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2100 $drives->{$opt} = $drive;
2101 push @$vollist, $drive->{file
};
2104 # copy everything else
2105 $newconf->{$opt} = $value;
2109 delete $newconf->{template
};
2111 if ($param->{name
}) {
2112 $newconf->{name
} = $param->{name
};
2114 if ($oldconf->{name
}) {
2115 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2117 $newconf->{name
} = "Copy-of-VM-$vmid";
2121 if ($param->{description
}) {
2122 $newconf->{description
} = $param->{description
};
2125 # create empty/temp config - this fails if VM already exists on other node
2126 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2131 my $newvollist = [];
2134 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2136 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2138 foreach my $opt (keys %$drives) {
2139 my $drive = $drives->{$opt};
2141 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2142 $newid, $storage, $format, $drive->{full
}, $newvollist);
2144 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2146 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2149 delete $newconf->{lock};
2150 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2153 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2154 die "Failed to move config to node '$target' - rename failed: $!\n"
2155 if !rename($conffile, $newconffile);
2158 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2163 sleep 1; # some storage like rbd need to wait before release volume - really?
2165 foreach my $volid (@$newvollist) {
2166 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2169 die "clone failed: $err";
2175 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2178 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2179 # Aquire exclusive lock lock for $newid
2180 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2185 __PACKAGE__-
>register_method({
2186 name
=> 'move_vm_disk',
2187 path
=> '{vmid}/move_disk',
2191 description
=> "Move volume to different storage.",
2193 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2194 "and 'Datastore.AllocateSpace' permissions on the storage.",
2197 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2198 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2202 additionalProperties
=> 0,
2204 node
=> get_standard_option
('pve-node'),
2205 vmid
=> get_standard_option
('pve-vmid'),
2208 description
=> "The disk you want to move.",
2209 enum
=> [ PVE
::QemuServer
::disknames
() ],
2211 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2214 description
=> "Target Format.",
2215 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2220 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2226 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2234 description
=> "the task ID.",
2239 my $rpcenv = PVE
::RPCEnvironment
::get
();
2241 my $authuser = $rpcenv->get_user();
2243 my $node = extract_param
($param, 'node');
2245 my $vmid = extract_param
($param, 'vmid');
2247 my $digest = extract_param
($param, 'digest');
2249 my $disk = extract_param
($param, 'disk');
2251 my $storeid = extract_param
($param, 'storage');
2253 my $format = extract_param
($param, 'format');
2255 my $storecfg = PVE
::Storage
::config
();
2257 my $updatefn = sub {
2259 my $conf = PVE
::QemuServer
::load_config
($vmid);
2261 die "checksum missmatch (file change by other user?)\n"
2262 if $digest && $digest ne $conf->{digest
};
2264 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2266 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2268 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2270 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2273 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2274 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2278 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2279 (!$format || !$oldfmt || $oldfmt eq $format);
2281 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2283 my $running = PVE
::QemuServer
::check_running
($vmid);
2285 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2289 my $newvollist = [];
2292 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2294 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2295 $vmid, $storeid, $format, 1, $newvollist);
2297 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2299 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2301 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2305 foreach my $volid (@$newvollist) {
2306 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2309 die "storage migration failed: $err";
2312 if ($param->{delete}) {
2313 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2318 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2321 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2324 __PACKAGE__-
>register_method({
2325 name
=> 'migrate_vm',
2326 path
=> '{vmid}/migrate',
2330 description
=> "Migrate virtual machine. Creates a new migration task.",
2332 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2335 additionalProperties
=> 0,
2337 node
=> get_standard_option
('pve-node'),
2338 vmid
=> get_standard_option
('pve-vmid'),
2339 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2342 description
=> "Use online/live migration.",
2347 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2354 description
=> "the task ID.",
2359 my $rpcenv = PVE
::RPCEnvironment
::get
();
2361 my $authuser = $rpcenv->get_user();
2363 my $target = extract_param
($param, 'target');
2365 my $localnode = PVE
::INotify
::nodename
();
2366 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2368 PVE
::Cluster
::check_cfs_quorum
();
2370 PVE
::Cluster
::check_node_exists
($target);
2372 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2374 my $vmid = extract_param
($param, 'vmid');
2376 raise_param_exc
({ force
=> "Only root may use this option." })
2377 if $param->{force
} && $authuser ne 'root@pam';
2380 my $conf = PVE
::QemuServer
::load_config
($vmid);
2382 # try to detect errors early
2384 PVE
::QemuServer
::check_lock
($conf);
2386 if (PVE
::QemuServer
::check_running
($vmid)) {
2387 die "cant migrate running VM without --online\n"
2388 if !$param->{online
};
2391 my $storecfg = PVE
::Storage
::config
();
2392 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2394 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2399 my $service = "pvevm:$vmid";
2401 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2403 print "Executing HA migrate for VM $vmid to node $target\n";
2405 PVE
::Tools
::run_command
($cmd);
2410 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2417 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2420 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2425 __PACKAGE__-
>register_method({
2427 path
=> '{vmid}/monitor',
2431 description
=> "Execute Qemu monitor commands.",
2433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2436 additionalProperties
=> 0,
2438 node
=> get_standard_option
('pve-node'),
2439 vmid
=> get_standard_option
('pve-vmid'),
2442 description
=> "The monitor command.",
2446 returns
=> { type
=> 'string'},
2450 my $vmid = $param->{vmid
};
2452 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2456 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2458 $res = "ERROR: $@" if $@;
2463 __PACKAGE__-
>register_method({
2464 name
=> 'resize_vm',
2465 path
=> '{vmid}/resize',
2469 description
=> "Extend volume size.",
2471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2474 additionalProperties
=> 0,
2476 node
=> get_standard_option
('pve-node'),
2477 vmid
=> get_standard_option
('pve-vmid'),
2478 skiplock
=> get_standard_option
('skiplock'),
2481 description
=> "The disk you want to resize.",
2482 enum
=> [PVE
::QemuServer
::disknames
()],
2486 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2487 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.",
2491 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2497 returns
=> { type
=> 'null'},
2501 my $rpcenv = PVE
::RPCEnvironment
::get
();
2503 my $authuser = $rpcenv->get_user();
2505 my $node = extract_param
($param, 'node');
2507 my $vmid = extract_param
($param, 'vmid');
2509 my $digest = extract_param
($param, 'digest');
2511 my $disk = extract_param
($param, 'disk');
2513 my $sizestr = extract_param
($param, 'size');
2515 my $skiplock = extract_param
($param, 'skiplock');
2516 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2517 if $skiplock && $authuser ne 'root@pam';
2519 my $storecfg = PVE
::Storage
::config
();
2521 my $updatefn = sub {
2523 my $conf = PVE
::QemuServer
::load_config
($vmid);
2525 die "checksum missmatch (file change by other user?)\n"
2526 if $digest && $digest ne $conf->{digest
};
2527 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2529 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2531 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2533 my $volid = $drive->{file
};
2535 die "disk '$disk' has no associated volume\n" if !$volid;
2537 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2539 die "you can't online resize a virtio windows bootdisk\n"
2540 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2542 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2544 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2546 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2548 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2549 my ($ext, $newsize, $unit) = ($1, $2, $4);
2552 $newsize = $newsize * 1024;
2553 } elsif ($unit eq 'M') {
2554 $newsize = $newsize * 1024 * 1024;
2555 } elsif ($unit eq 'G') {
2556 $newsize = $newsize * 1024 * 1024 * 1024;
2557 } elsif ($unit eq 'T') {
2558 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2561 $newsize += $size if $ext;
2562 $newsize = int($newsize);
2564 die "unable to skrink disk size\n" if $newsize < $size;
2566 return if $size == $newsize;
2568 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2570 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2572 $drive->{size
} = $newsize;
2573 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2575 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2578 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2582 __PACKAGE__-
>register_method({
2583 name
=> 'snapshot_list',
2584 path
=> '{vmid}/snapshot',
2586 description
=> "List all snapshots.",
2588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2591 protected
=> 1, # qemu pid files are only readable by root
2593 additionalProperties
=> 0,
2595 vmid
=> get_standard_option
('pve-vmid'),
2596 node
=> get_standard_option
('pve-node'),
2605 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2610 my $vmid = $param->{vmid
};
2612 my $conf = PVE
::QemuServer
::load_config
($vmid);
2613 my $snaphash = $conf->{snapshots
} || {};
2617 foreach my $name (keys %$snaphash) {
2618 my $d = $snaphash->{$name};
2621 snaptime
=> $d->{snaptime
} || 0,
2622 vmstate
=> $d->{vmstate
} ?
1 : 0,
2623 description
=> $d->{description
} || '',
2625 $item->{parent
} = $d->{parent
} if $d->{parent
};
2626 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2630 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2631 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2632 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2634 push @$res, $current;
2639 __PACKAGE__-
>register_method({
2641 path
=> '{vmid}/snapshot',
2645 description
=> "Snapshot a VM.",
2647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2650 additionalProperties
=> 0,
2652 node
=> get_standard_option
('pve-node'),
2653 vmid
=> get_standard_option
('pve-vmid'),
2654 snapname
=> get_standard_option
('pve-snapshot-name'),
2658 description
=> "Save the vmstate",
2663 description
=> "Freeze the filesystem",
2668 description
=> "A textual description or comment.",
2674 description
=> "the task ID.",
2679 my $rpcenv = PVE
::RPCEnvironment
::get
();
2681 my $authuser = $rpcenv->get_user();
2683 my $node = extract_param
($param, 'node');
2685 my $vmid = extract_param
($param, 'vmid');
2687 my $snapname = extract_param
($param, 'snapname');
2689 die "unable to use snapshot name 'current' (reserved name)\n"
2690 if $snapname eq 'current';
2693 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2694 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2695 $param->{freezefs
}, $param->{description
});
2698 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2701 __PACKAGE__-
>register_method({
2702 name
=> 'snapshot_cmd_idx',
2703 path
=> '{vmid}/snapshot/{snapname}',
2710 additionalProperties
=> 0,
2712 vmid
=> get_standard_option
('pve-vmid'),
2713 node
=> get_standard_option
('pve-node'),
2714 snapname
=> get_standard_option
('pve-snapshot-name'),
2723 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2730 push @$res, { cmd
=> 'rollback' };
2731 push @$res, { cmd
=> 'config' };
2736 __PACKAGE__-
>register_method({
2737 name
=> 'update_snapshot_config',
2738 path
=> '{vmid}/snapshot/{snapname}/config',
2742 description
=> "Update snapshot metadata.",
2744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2747 additionalProperties
=> 0,
2749 node
=> get_standard_option
('pve-node'),
2750 vmid
=> get_standard_option
('pve-vmid'),
2751 snapname
=> get_standard_option
('pve-snapshot-name'),
2755 description
=> "A textual description or comment.",
2759 returns
=> { type
=> 'null' },
2763 my $rpcenv = PVE
::RPCEnvironment
::get
();
2765 my $authuser = $rpcenv->get_user();
2767 my $vmid = extract_param
($param, 'vmid');
2769 my $snapname = extract_param
($param, 'snapname');
2771 return undef if !defined($param->{description
});
2773 my $updatefn = sub {
2775 my $conf = PVE
::QemuServer
::load_config
($vmid);
2777 PVE
::QemuServer
::check_lock
($conf);
2779 my $snap = $conf->{snapshots
}->{$snapname};
2781 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2783 $snap->{description
} = $param->{description
} if defined($param->{description
});
2785 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2788 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2793 __PACKAGE__-
>register_method({
2794 name
=> 'get_snapshot_config',
2795 path
=> '{vmid}/snapshot/{snapname}/config',
2798 description
=> "Get snapshot configuration",
2800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2803 additionalProperties
=> 0,
2805 node
=> get_standard_option
('pve-node'),
2806 vmid
=> get_standard_option
('pve-vmid'),
2807 snapname
=> get_standard_option
('pve-snapshot-name'),
2810 returns
=> { type
=> "object" },
2814 my $rpcenv = PVE
::RPCEnvironment
::get
();
2816 my $authuser = $rpcenv->get_user();
2818 my $vmid = extract_param
($param, 'vmid');
2820 my $snapname = extract_param
($param, 'snapname');
2822 my $conf = PVE
::QemuServer
::load_config
($vmid);
2824 my $snap = $conf->{snapshots
}->{$snapname};
2826 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2831 __PACKAGE__-
>register_method({
2833 path
=> '{vmid}/snapshot/{snapname}/rollback',
2837 description
=> "Rollback VM state to specified snapshot.",
2839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2842 additionalProperties
=> 0,
2844 node
=> get_standard_option
('pve-node'),
2845 vmid
=> get_standard_option
('pve-vmid'),
2846 snapname
=> get_standard_option
('pve-snapshot-name'),
2851 description
=> "the task ID.",
2856 my $rpcenv = PVE
::RPCEnvironment
::get
();
2858 my $authuser = $rpcenv->get_user();
2860 my $node = extract_param
($param, 'node');
2862 my $vmid = extract_param
($param, 'vmid');
2864 my $snapname = extract_param
($param, 'snapname');
2867 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2868 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2871 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2874 __PACKAGE__-
>register_method({
2875 name
=> 'delsnapshot',
2876 path
=> '{vmid}/snapshot/{snapname}',
2880 description
=> "Delete a VM snapshot.",
2882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2885 additionalProperties
=> 0,
2887 node
=> get_standard_option
('pve-node'),
2888 vmid
=> get_standard_option
('pve-vmid'),
2889 snapname
=> get_standard_option
('pve-snapshot-name'),
2893 description
=> "For removal from config file, even if removing disk snapshots fails.",
2899 description
=> "the task ID.",
2904 my $rpcenv = PVE
::RPCEnvironment
::get
();
2906 my $authuser = $rpcenv->get_user();
2908 my $node = extract_param
($param, 'node');
2910 my $vmid = extract_param
($param, 'vmid');
2912 my $snapname = extract_param
($param, 'snapname');
2915 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2916 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2919 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2922 __PACKAGE__-
>register_method({
2924 path
=> '{vmid}/template',
2928 description
=> "Create a Template.",
2930 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2931 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2934 additionalProperties
=> 0,
2936 node
=> get_standard_option
('pve-node'),
2937 vmid
=> get_standard_option
('pve-vmid'),
2941 description
=> "If you want to convert only 1 disk to base image.",
2942 enum
=> [PVE
::QemuServer
::disknames
()],
2947 returns
=> { type
=> 'null'},
2951 my $rpcenv = PVE
::RPCEnvironment
::get
();
2953 my $authuser = $rpcenv->get_user();
2955 my $node = extract_param
($param, 'node');
2957 my $vmid = extract_param
($param, 'vmid');
2959 my $disk = extract_param
($param, 'disk');
2961 my $updatefn = sub {
2963 my $conf = PVE
::QemuServer
::load_config
($vmid);
2965 PVE
::QemuServer
::check_lock
($conf);
2967 die "unable to create template, because VM contains snapshots\n"
2968 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2970 die "you can't convert a template to a template\n"
2971 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2973 die "you can't convert a VM to template if VM is running\n"
2974 if PVE
::QemuServer
::check_running
($vmid);
2977 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2980 $conf->{template
} = 1;
2981 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2983 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2986 PVE
::QemuServer
::lock_config
($vmid, $updatefn);