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 $volid_is_new = 1;
134 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
135 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
140 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
142 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
144 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
146 die "volume $volid does not exists\n" if !$size;
148 $disk->{size
} = $size;
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
155 # free allocated images on error
157 syslog
('err', "VM $vmid creating disks failed");
158 foreach my $volid (@$vollist) {
159 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
165 # modify vm config if everything went well
166 foreach my $ds (keys %$res) {
167 $conf->{$ds} = $res->{$ds};
173 my $check_vm_modify_config_perm = sub {
174 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
176 return 1 if $authuser eq 'root@pam';
178 foreach my $opt (@$key_list) {
179 # disk checks need to be done somewhere else
180 next if PVE
::QemuServer
::valid_drivename
($opt);
182 if ($opt eq 'sockets' || $opt eq 'cores' ||
183 $opt eq 'cpu' || $opt eq 'smp' ||
184 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
186 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
188 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
190 } elsif ($opt eq 'args' || $opt eq 'lock') {
191 die "only root can set '$opt' config\n";
192 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
193 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
194 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
195 } elsif ($opt =~ m/^net\d+$/) {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
205 __PACKAGE__-
>register_method({
209 description
=> "Virtual machine index (per node).",
211 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
215 protected
=> 1, # qemu pid files are only readable by root
217 additionalProperties
=> 0,
219 node
=> get_standard_option
('pve-node'),
228 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
233 my $rpcenv = PVE
::RPCEnvironment
::get
();
234 my $authuser = $rpcenv->get_user();
236 my $vmstatus = PVE
::QemuServer
::vmstatus
();
239 foreach my $vmid (keys %$vmstatus) {
240 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
242 my $data = $vmstatus->{$vmid};
243 $data->{vmid
} = $vmid;
252 __PACKAGE__-
>register_method({
256 description
=> "Create or restore a virtual machine.",
258 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
259 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
260 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
261 user
=> 'all', # check inside
266 additionalProperties
=> 0,
267 properties
=> PVE
::QemuServer
::json_config_properties
(
269 node
=> get_standard_option
('pve-node'),
270 vmid
=> get_standard_option
('pve-vmid'),
272 description
=> "The backup file.",
277 storage
=> get_standard_option
('pve-storage-id', {
278 description
=> "Default storage.",
284 description
=> "Allow to overwrite existing VM.",
285 requires
=> 'archive',
290 description
=> "Assign a unique random ethernet address.",
291 requires
=> 'archive',
295 type
=> 'string', format
=> 'pve-poolid',
296 description
=> "Add the VM to the specified pool.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 my $node = extract_param
($param, 'node');
312 my $vmid = extract_param
($param, 'vmid');
314 my $archive = extract_param
($param, 'archive');
316 my $storage = extract_param
($param, 'storage');
318 my $force = extract_param
($param, 'force');
320 my $unique = extract_param
($param, 'unique');
322 my $pool = extract_param
($param, 'pool');
324 my $filename = PVE
::QemuServer
::config_file
($vmid);
326 my $storecfg = PVE
::Storage
::config
();
328 PVE
::Cluster
::check_cfs_quorum
();
330 if (defined($pool)) {
331 $rpcenv->check_pool_exist($pool);
334 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
335 if defined($storage);
337 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
339 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
341 } elsif ($archive && $force && (-f
$filename) &&
342 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
343 # OK: user has VM.Backup permissions, and want to restore an existing VM
349 &$resolve_cdrom_alias($param);
351 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
353 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
355 foreach my $opt (keys %$param) {
356 if (PVE
::QemuServer
::valid_drivename
($opt)) {
357 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
358 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
360 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
361 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
365 PVE
::QemuServer
::add_random_macs
($param);
367 my $keystr = join(' ', keys %$param);
368 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
370 if ($archive eq '-') {
371 die "pipe requires cli environment\n"
372 if $rpcenv->{type
} ne 'cli';
374 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
377 if PVE
::Storage
::parse_volume_id
($archive, 1);
379 die "can't find archive file '$archive'\n" if !($path && -f
$path);
384 my $restorefn = sub {
386 # fixme: this test does not work if VM exists on other node!
388 die "unable to restore vm $vmid: config file already exists\n"
391 die "unable to restore vm $vmid: vm is running\n"
392 if PVE
::QemuServer
::check_running
($vmid);
396 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
399 unique
=> $unique });
401 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
404 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
410 die "unable to create vm $vmid: config file already exists\n"
421 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
423 # try to be smart about bootdisk
424 my @disks = PVE
::QemuServer
::disknames
();
426 foreach my $ds (reverse @disks) {
427 next if !$conf->{$ds};
428 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
429 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
433 if (!$conf->{bootdisk
} && $firstdisk) {
434 $conf->{bootdisk
} = $firstdisk;
437 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
443 foreach my $volid (@$vollist) {
444 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
447 die "create failed - $err";
450 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
453 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
456 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
459 __PACKAGE__-
>register_method({
464 description
=> "Directory index",
469 additionalProperties
=> 0,
471 node
=> get_standard_option
('pve-node'),
472 vmid
=> get_standard_option
('pve-vmid'),
480 subdir
=> { type
=> 'string' },
483 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
489 { subdir
=> 'config' },
490 { subdir
=> 'status' },
491 { subdir
=> 'unlink' },
492 { subdir
=> 'vncproxy' },
493 { subdir
=> 'migrate' },
494 { subdir
=> 'resize' },
495 { subdir
=> 'move' },
497 { subdir
=> 'rrddata' },
498 { subdir
=> 'monitor' },
499 { subdir
=> 'snapshot' },
500 { subdir
=> 'spiceproxy' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
771 ($drive->{mbps
} || 0)*1024*1024,
772 ($drive->{mbps_rd
} || 0)*1024*1024,
773 ($drive->{mbps_wr
} || 0)*1024*1024,
775 $drive->{iops_rd
} || 0,
776 $drive->{iops_wr
} || 0)
777 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
782 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
783 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
785 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
786 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
788 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
790 if (PVE
::QemuServer
::check_running
($vmid)) {
791 if ($drive->{file
} eq 'none') {
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
794 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
795 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
800 } else { # hotplug new disks
802 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
806 my $vmconfig_update_net = sub {
807 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
809 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
810 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
811 my $newnet = PVE
::QemuServer
::parse_net
($value);
813 if($oldnet->{model
} ne $newnet->{model
}){
814 #if model change, we try to hot-unplug
815 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
818 if($newnet->{bridge
} && $oldnet->{bridge
}){
819 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
821 if($newnet->{rate
} ne $oldnet->{rate
}){
822 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
825 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
826 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
827 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
831 #if bridge/nat mode change, we try to hot-unplug
832 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
837 $conf->{$opt} = $value;
838 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
839 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
841 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
843 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
846 # POST/PUT {vmid}/config implementation
848 # The original API used PUT (idempotent) an we assumed that all operations
849 # are fast. But it turned out that almost any configuration change can
850 # involve hot-plug actions, or disk alloc/free. Such actions can take long
851 # time to complete and have side effects (not idempotent).
853 # The new implementation uses POST and forks a worker process. We added
854 # a new option 'background_delay'. If specified we wait up to
855 # 'background_delay' second for the worker task to complete. It returns null
856 # if the task is finished within that time, else we return the UPID.
858 my $update_vm_api = sub {
859 my ($param, $sync) = @_;
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $node = extract_param
($param, 'node');
867 my $vmid = extract_param
($param, 'vmid');
869 my $digest = extract_param
($param, 'digest');
871 my $background_delay = extract_param
($param, 'background_delay');
873 my @paramarr = (); # used for log message
874 foreach my $key (keys %$param) {
875 push @paramarr, "-$key", $param->{$key};
878 my $skiplock = extract_param
($param, 'skiplock');
879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
880 if $skiplock && $authuser ne 'root@pam';
882 my $delete_str = extract_param
($param, 'delete');
884 my $force = extract_param
($param, 'force');
886 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
888 my $storecfg = PVE
::Storage
::config
();
890 my $defaults = PVE
::QemuServer
::load_defaults
();
892 &$resolve_cdrom_alias($param);
894 # now try to verify all parameters
897 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
898 $opt = 'ide2' if $opt eq 'cdrom';
899 raise_param_exc
({ delete => "you can't use '-$opt' and " .
900 "-delete $opt' at the same time" })
901 if defined($param->{$opt});
903 if (!PVE
::QemuServer
::option_exists
($opt)) {
904 raise_param_exc
({ delete => "unknown option '$opt'" });
910 foreach my $opt (keys %$param) {
911 if (PVE
::QemuServer
::valid_drivename
($opt)) {
913 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
914 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
915 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
916 } elsif ($opt =~ m/^net(\d+)$/) {
918 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
919 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
923 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
927 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
931 my $conf = PVE
::QemuServer
::load_config
($vmid);
933 die "checksum missmatch (file change by other user?)\n"
934 if $digest && $digest ne $conf->{digest
};
936 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
938 if ($param->{memory
} || defined($param->{balloon
})) {
939 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
940 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
942 die "balloon value too large (must be smaller than assigned memory)\n"
943 if $balloon && $balloon > $maxmem;
946 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
950 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
952 foreach my $opt (@delete) { # delete
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
957 my $running = PVE
::QemuServer
::check_running
($vmid);
959 foreach my $opt (keys %$param) { # add/change
961 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt}, $force);
970 } elsif ($opt =~ m/^net(\d+)$/) { #nics
972 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
973 $opt, $param->{$opt});
977 if($opt eq 'tablet' && $param->{$opt} == 1){
978 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
979 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
980 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
983 $conf->{$opt} = $param->{$opt};
984 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
988 # allow manual ballooning if shares is set to zero
989 if ($running && defined($param->{balloon
}) &&
990 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
991 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
992 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1000 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1002 if ($background_delay) {
1004 # Note: It would be better to do that in the Event based HTTPServer
1005 # to avoid blocking call to sleep.
1007 my $end_time = time() + $background_delay;
1009 my $task = PVE
::Tools
::upid_decode
($upid);
1012 while (time() < $end_time) {
1013 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1015 sleep(1); # this gets interrupted when child process ends
1019 my $status = PVE
::Tools
::upid_read_status
($upid);
1020 return undef if $status eq 'OK';
1029 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1032 my $vm_config_perm_list = [
1037 'VM.Config.Network',
1039 'VM.Config.Options',
1042 __PACKAGE__-
>register_method({
1043 name
=> 'update_vm_async',
1044 path
=> '{vmid}/config',
1048 description
=> "Set virtual machine options (asynchrounous API).",
1050 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1053 additionalProperties
=> 0,
1054 properties
=> PVE
::QemuServer
::json_config_properties
(
1056 node
=> get_standard_option
('pve-node'),
1057 vmid
=> get_standard_option
('pve-vmid'),
1058 skiplock
=> get_standard_option
('skiplock'),
1060 type
=> 'string', format
=> 'pve-configid-list',
1061 description
=> "A list of settings you want to delete.",
1066 description
=> $opt_force_description,
1068 requires
=> 'delete',
1072 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1076 background_delay
=> {
1078 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1089 code
=> $update_vm_api,
1092 __PACKAGE__-
>register_method({
1093 name
=> 'update_vm',
1094 path
=> '{vmid}/config',
1098 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1100 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1103 additionalProperties
=> 0,
1104 properties
=> PVE
::QemuServer
::json_config_properties
(
1106 node
=> get_standard_option
('pve-node'),
1107 vmid
=> get_standard_option
('pve-vmid'),
1108 skiplock
=> get_standard_option
('skiplock'),
1110 type
=> 'string', format
=> 'pve-configid-list',
1111 description
=> "A list of settings you want to delete.",
1116 description
=> $opt_force_description,
1118 requires
=> 'delete',
1122 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1128 returns
=> { type
=> 'null' },
1131 &$update_vm_api($param, 1);
1137 __PACKAGE__-
>register_method({
1138 name
=> 'destroy_vm',
1143 description
=> "Destroy the vm (also delete all used/owned volumes).",
1145 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1152 skiplock
=> get_standard_option
('skiplock'),
1161 my $rpcenv = PVE
::RPCEnvironment
::get
();
1163 my $authuser = $rpcenv->get_user();
1165 my $vmid = $param->{vmid
};
1167 my $skiplock = $param->{skiplock
};
1168 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1169 if $skiplock && $authuser ne 'root@pam';
1172 my $conf = PVE
::QemuServer
::load_config
($vmid);
1174 my $storecfg = PVE
::Storage
::config
();
1176 my $delVMfromPoolFn = sub {
1177 my $usercfg = cfs_read_file
("user.cfg");
1178 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1179 if (my $data = $usercfg->{pools
}->{$pool}) {
1180 delete $data->{vms
}->{$vmid};
1181 delete $usercfg->{vms
}->{$vmid};
1182 cfs_write_file
("user.cfg", $usercfg);
1190 syslog
('info', "destroy VM $vmid: $upid\n");
1192 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1194 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1197 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1200 __PACKAGE__-
>register_method({
1202 path
=> '{vmid}/unlink',
1206 description
=> "Unlink/delete disk images.",
1208 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1211 additionalProperties
=> 0,
1213 node
=> get_standard_option
('pve-node'),
1214 vmid
=> get_standard_option
('pve-vmid'),
1216 type
=> 'string', format
=> 'pve-configid-list',
1217 description
=> "A list of disk IDs you want to delete.",
1221 description
=> $opt_force_description,
1226 returns
=> { type
=> 'null'},
1230 $param->{delete} = extract_param
($param, 'idlist');
1232 __PACKAGE__-
>update_vm($param);
1239 __PACKAGE__-
>register_method({
1241 path
=> '{vmid}/vncproxy',
1245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1247 description
=> "Creates a TCP VNC proxy connections.",
1249 additionalProperties
=> 0,
1251 node
=> get_standard_option
('pve-node'),
1252 vmid
=> get_standard_option
('pve-vmid'),
1256 additionalProperties
=> 0,
1258 user
=> { type
=> 'string' },
1259 ticket
=> { type
=> 'string' },
1260 cert
=> { type
=> 'string' },
1261 port
=> { type
=> 'integer' },
1262 upid
=> { type
=> 'string' },
1268 my $rpcenv = PVE
::RPCEnvironment
::get
();
1270 my $authuser = $rpcenv->get_user();
1272 my $vmid = $param->{vmid
};
1273 my $node = $param->{node
};
1275 my $authpath = "/vms/$vmid";
1277 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1279 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1282 my $port = PVE
::Tools
::next_vnc_port
();
1286 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1287 $remip = PVE
::Cluster
::remote_node_ip
($node);
1290 # NOTE: kvm VNC traffic is already TLS encrypted
1291 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1298 syslog
('info', "starting vnc proxy $upid\n");
1300 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1302 my $qmstr = join(' ', @$qmcmd);
1304 # also redirect stderr (else we get RFB protocol errors)
1305 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1307 PVE
::Tools
::run_command
($cmd);
1312 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1314 PVE
::Tools
::wait_for_vnc_port
($port);
1325 __PACKAGE__-
>register_method({
1326 name
=> 'spiceproxy',
1327 path
=> '{vmid}/spiceproxy',
1330 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1332 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1334 description
=> "Returns a SPICE configuration to connect to the VM.",
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid'),
1343 additionalProperties
=> 1,
1345 type
=> { type
=> 'string' },
1346 password
=> { type
=> 'string' },
1347 proxy
=> { type
=> 'string' },
1348 host
=> { type
=> 'string' },
1349 port
=> { type
=> 'integer' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1364 # Note: we currectly use "proxyto => 'node'", so this code will never trigger
1365 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1366 $remip = PVE
::Cluster
::remote_node_ip
($node);
1369 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1373 # Note: this only works if VM is on local node
1374 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1377 # allow access for group www-data to the spice socket,
1378 # so that spiceproxy can access it
1379 my $socket = PVE
::QemuServer
::spice_socket
($vmid);
1380 my $gid = getgrnam('www-data') || die "getgrnam failed - $!\n";
1381 chown 0, $gid, $socket;
1382 chmod 0770, $socket;
1385 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1390 host
=> $proxyticket,
1391 proxy
=> "http://$host:3128",
1392 port
=> 1, # not used for now
1393 password
=> $ticket,
1394 'delete-this-file' => 1,
1398 __PACKAGE__-
>register_method({
1400 path
=> '{vmid}/status',
1403 description
=> "Directory index",
1408 additionalProperties
=> 0,
1410 node
=> get_standard_option
('pve-node'),
1411 vmid
=> get_standard_option
('pve-vmid'),
1419 subdir
=> { type
=> 'string' },
1422 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1428 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1431 { subdir
=> 'current' },
1432 { subdir
=> 'start' },
1433 { subdir
=> 'stop' },
1439 my $vm_is_ha_managed = sub {
1442 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1443 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1449 __PACKAGE__-
>register_method({
1450 name
=> 'vm_status',
1451 path
=> '{vmid}/status/current',
1454 protected
=> 1, # qemu pid files are only readable by root
1455 description
=> "Get virtual machine status.",
1457 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1460 additionalProperties
=> 0,
1462 node
=> get_standard_option
('pve-node'),
1463 vmid
=> get_standard_option
('pve-vmid'),
1466 returns
=> { type
=> 'object' },
1471 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1473 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1474 my $status = $vmstatus->{$param->{vmid
}};
1476 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1478 if ($conf->{vga
} && ($conf->{vga
} eq 'qxl')) {
1479 $status->{spice
} = 1;
1485 __PACKAGE__-
>register_method({
1487 path
=> '{vmid}/status/start',
1491 description
=> "Start virtual machine.",
1493 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1496 additionalProperties
=> 0,
1498 node
=> get_standard_option
('pve-node'),
1499 vmid
=> get_standard_option
('pve-vmid'),
1500 skiplock
=> get_standard_option
('skiplock'),
1501 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1502 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1503 machine
=> get_standard_option
('pve-qm-machine'),
1512 my $rpcenv = PVE
::RPCEnvironment
::get
();
1514 my $authuser = $rpcenv->get_user();
1516 my $node = extract_param
($param, 'node');
1518 my $vmid = extract_param
($param, 'vmid');
1520 my $machine = extract_param
($param, 'machine');
1522 my $stateuri = extract_param
($param, 'stateuri');
1523 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1524 if $stateuri && $authuser ne 'root@pam';
1526 my $skiplock = extract_param
($param, 'skiplock');
1527 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1528 if $skiplock && $authuser ne 'root@pam';
1530 my $migratedfrom = extract_param
($param, 'migratedfrom');
1531 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1532 if $migratedfrom && $authuser ne 'root@pam';
1534 my $storecfg = PVE
::Storage
::config
();
1536 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1537 $rpcenv->{type
} ne 'ha') {
1542 my $service = "pvevm:$vmid";
1544 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1546 print "Executing HA start for VM $vmid\n";
1548 PVE
::Tools
::run_command
($cmd);
1553 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1560 syslog
('info', "start VM $vmid: $upid\n");
1562 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1567 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1571 __PACKAGE__-
>register_method({
1573 path
=> '{vmid}/status/stop',
1577 description
=> "Stop virtual machine.",
1579 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1582 additionalProperties
=> 0,
1584 node
=> get_standard_option
('pve-node'),
1585 vmid
=> get_standard_option
('pve-vmid'),
1586 skiplock
=> get_standard_option
('skiplock'),
1587 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1589 description
=> "Wait maximal timeout seconds.",
1595 description
=> "Do not decativate storage volumes.",
1608 my $rpcenv = PVE
::RPCEnvironment
::get
();
1610 my $authuser = $rpcenv->get_user();
1612 my $node = extract_param
($param, 'node');
1614 my $vmid = extract_param
($param, 'vmid');
1616 my $skiplock = extract_param
($param, 'skiplock');
1617 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1618 if $skiplock && $authuser ne 'root@pam';
1620 my $keepActive = extract_param
($param, 'keepActive');
1621 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1622 if $keepActive && $authuser ne 'root@pam';
1624 my $migratedfrom = extract_param
($param, 'migratedfrom');
1625 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1626 if $migratedfrom && $authuser ne 'root@pam';
1629 my $storecfg = PVE
::Storage
::config
();
1631 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1636 my $service = "pvevm:$vmid";
1638 my $cmd = ['clusvcadm', '-d', $service];
1640 print "Executing HA stop for VM $vmid\n";
1642 PVE
::Tools
::run_command
($cmd);
1647 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1653 syslog
('info', "stop VM $vmid: $upid\n");
1655 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1656 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1661 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1665 __PACKAGE__-
>register_method({
1667 path
=> '{vmid}/status/reset',
1671 description
=> "Reset virtual machine.",
1673 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1676 additionalProperties
=> 0,
1678 node
=> get_standard_option
('pve-node'),
1679 vmid
=> get_standard_option
('pve-vmid'),
1680 skiplock
=> get_standard_option
('skiplock'),
1689 my $rpcenv = PVE
::RPCEnvironment
::get
();
1691 my $authuser = $rpcenv->get_user();
1693 my $node = extract_param
($param, 'node');
1695 my $vmid = extract_param
($param, 'vmid');
1697 my $skiplock = extract_param
($param, 'skiplock');
1698 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1699 if $skiplock && $authuser ne 'root@pam';
1701 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1706 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1711 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1714 __PACKAGE__-
>register_method({
1715 name
=> 'vm_shutdown',
1716 path
=> '{vmid}/status/shutdown',
1720 description
=> "Shutdown virtual machine.",
1722 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1725 additionalProperties
=> 0,
1727 node
=> get_standard_option
('pve-node'),
1728 vmid
=> get_standard_option
('pve-vmid'),
1729 skiplock
=> get_standard_option
('skiplock'),
1731 description
=> "Wait maximal timeout seconds.",
1737 description
=> "Make sure the VM stops.",
1743 description
=> "Do not decativate storage volumes.",
1756 my $rpcenv = PVE
::RPCEnvironment
::get
();
1758 my $authuser = $rpcenv->get_user();
1760 my $node = extract_param
($param, 'node');
1762 my $vmid = extract_param
($param, 'vmid');
1764 my $skiplock = extract_param
($param, 'skiplock');
1765 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1766 if $skiplock && $authuser ne 'root@pam';
1768 my $keepActive = extract_param
($param, 'keepActive');
1769 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1770 if $keepActive && $authuser ne 'root@pam';
1772 my $storecfg = PVE
::Storage
::config
();
1777 syslog
('info', "shutdown VM $vmid: $upid\n");
1779 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1780 1, $param->{forceStop
}, $keepActive);
1785 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1788 __PACKAGE__-
>register_method({
1789 name
=> 'vm_suspend',
1790 path
=> '{vmid}/status/suspend',
1794 description
=> "Suspend virtual machine.",
1796 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1799 additionalProperties
=> 0,
1801 node
=> get_standard_option
('pve-node'),
1802 vmid
=> get_standard_option
('pve-vmid'),
1803 skiplock
=> get_standard_option
('skiplock'),
1812 my $rpcenv = PVE
::RPCEnvironment
::get
();
1814 my $authuser = $rpcenv->get_user();
1816 my $node = extract_param
($param, 'node');
1818 my $vmid = extract_param
($param, 'vmid');
1820 my $skiplock = extract_param
($param, 'skiplock');
1821 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1822 if $skiplock && $authuser ne 'root@pam';
1824 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1829 syslog
('info', "suspend VM $vmid: $upid\n");
1831 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1836 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1839 __PACKAGE__-
>register_method({
1840 name
=> 'vm_resume',
1841 path
=> '{vmid}/status/resume',
1845 description
=> "Resume virtual machine.",
1847 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1850 additionalProperties
=> 0,
1852 node
=> get_standard_option
('pve-node'),
1853 vmid
=> get_standard_option
('pve-vmid'),
1854 skiplock
=> get_standard_option
('skiplock'),
1863 my $rpcenv = PVE
::RPCEnvironment
::get
();
1865 my $authuser = $rpcenv->get_user();
1867 my $node = extract_param
($param, 'node');
1869 my $vmid = extract_param
($param, 'vmid');
1871 my $skiplock = extract_param
($param, 'skiplock');
1872 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1873 if $skiplock && $authuser ne 'root@pam';
1875 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1880 syslog
('info', "resume VM $vmid: $upid\n");
1882 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1887 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1890 __PACKAGE__-
>register_method({
1891 name
=> 'vm_sendkey',
1892 path
=> '{vmid}/sendkey',
1896 description
=> "Send key event to virtual machine.",
1898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1901 additionalProperties
=> 0,
1903 node
=> get_standard_option
('pve-node'),
1904 vmid
=> get_standard_option
('pve-vmid'),
1905 skiplock
=> get_standard_option
('skiplock'),
1907 description
=> "The key (qemu monitor encoding).",
1912 returns
=> { type
=> 'null'},
1916 my $rpcenv = PVE
::RPCEnvironment
::get
();
1918 my $authuser = $rpcenv->get_user();
1920 my $node = extract_param
($param, 'node');
1922 my $vmid = extract_param
($param, 'vmid');
1924 my $skiplock = extract_param
($param, 'skiplock');
1925 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1926 if $skiplock && $authuser ne 'root@pam';
1928 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1933 __PACKAGE__-
>register_method({
1934 name
=> 'vm_feature',
1935 path
=> '{vmid}/feature',
1939 description
=> "Check if feature for virtual machine is available.",
1941 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1944 additionalProperties
=> 0,
1946 node
=> get_standard_option
('pve-node'),
1947 vmid
=> get_standard_option
('pve-vmid'),
1949 description
=> "Feature to check.",
1951 enum
=> [ 'snapshot', 'clone', 'copy' ],
1953 snapname
=> get_standard_option
('pve-snapshot-name', {
1961 hasFeature
=> { type
=> 'boolean' },
1964 items
=> { type
=> 'string' },
1971 my $node = extract_param
($param, 'node');
1973 my $vmid = extract_param
($param, 'vmid');
1975 my $snapname = extract_param
($param, 'snapname');
1977 my $feature = extract_param
($param, 'feature');
1979 my $running = PVE
::QemuServer
::check_running
($vmid);
1981 my $conf = PVE
::QemuServer
::load_config
($vmid);
1984 my $snap = $conf->{snapshots
}->{$snapname};
1985 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1988 my $storecfg = PVE
::Storage
::config
();
1990 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1991 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1994 hasFeature
=> $hasFeature,
1995 nodes
=> [ keys %$nodelist ],
1999 __PACKAGE__-
>register_method({
2001 path
=> '{vmid}/clone',
2005 description
=> "Create a copy of virtual machine/template.",
2007 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2008 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2009 "'Datastore.AllocateSpace' on any used storage.",
2012 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2014 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2015 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2020 additionalProperties
=> 0,
2022 node
=> get_standard_option
('pve-node'),
2023 vmid
=> get_standard_option
('pve-vmid'),
2024 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2027 type
=> 'string', format
=> 'dns-name',
2028 description
=> "Set a name for the new VM.",
2033 description
=> "Description for the new VM.",
2037 type
=> 'string', format
=> 'pve-poolid',
2038 description
=> "Add the new VM to the specified pool.",
2040 snapname
=> get_standard_option
('pve-snapshot-name', {
2044 storage
=> get_standard_option
('pve-storage-id', {
2045 description
=> "Target storage for full clone.",
2050 description
=> "Target format for file storage.",
2054 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2059 description
=> "Create a full copy of all disk. This is always done when " .
2060 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2063 target
=> get_standard_option
('pve-node', {
2064 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2075 my $rpcenv = PVE
::RPCEnvironment
::get
();
2077 my $authuser = $rpcenv->get_user();
2079 my $node = extract_param
($param, 'node');
2081 my $vmid = extract_param
($param, 'vmid');
2083 my $newid = extract_param
($param, 'newid');
2085 my $pool = extract_param
($param, 'pool');
2087 if (defined($pool)) {
2088 $rpcenv->check_pool_exist($pool);
2091 my $snapname = extract_param
($param, 'snapname');
2093 my $storage = extract_param
($param, 'storage');
2095 my $format = extract_param
($param, 'format');
2097 my $target = extract_param
($param, 'target');
2099 my $localnode = PVE
::INotify
::nodename
();
2101 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2103 PVE
::Cluster
::check_node_exists
($target) if $target;
2105 my $storecfg = PVE
::Storage
::config
();
2108 # check if storage is enabled on local node
2109 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2111 # check if storage is available on target node
2112 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2113 # clone only works if target storage is shared
2114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2115 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2119 PVE
::Cluster
::check_cfs_quorum
();
2121 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2123 # exclusive lock if VM is running - else shared lock is enough;
2124 my $shared_lock = $running ?
0 : 1;
2128 # do all tests after lock
2129 # we also try to do all tests before we fork the worker
2131 my $conf = PVE
::QemuServer
::load_config
($vmid);
2133 PVE
::QemuServer
::check_lock
($conf);
2135 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2137 die "unexpected state change\n" if $verify_running != $running;
2139 die "snapshot '$snapname' does not exist\n"
2140 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2142 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2144 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2146 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2148 my $conffile = PVE
::QemuServer
::config_file
($newid);
2150 die "unable to create VM $newid: config file already exists\n"
2153 my $newconf = { lock => 'clone' };
2157 foreach my $opt (keys %$oldconf) {
2158 my $value = $oldconf->{$opt};
2160 # do not copy snapshot related info
2161 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2162 $opt eq 'vmstate' || $opt eq 'snapstate';
2164 # always change MAC! address
2165 if ($opt =~ m/^net(\d+)$/) {
2166 my $net = PVE
::QemuServer
::parse_net
($value);
2167 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2168 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2169 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2170 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2171 $newconf->{$opt} = $value; # simply copy configuration
2173 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2174 die "Full clone feature is not available"
2175 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2178 $drives->{$opt} = $drive;
2179 push @$vollist, $drive->{file
};
2182 # copy everything else
2183 $newconf->{$opt} = $value;
2187 delete $newconf->{template
};
2189 if ($param->{name
}) {
2190 $newconf->{name
} = $param->{name
};
2192 if ($oldconf->{name
}) {
2193 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2195 $newconf->{name
} = "Copy-of-VM-$vmid";
2199 if ($param->{description
}) {
2200 $newconf->{description
} = $param->{description
};
2203 # create empty/temp config - this fails if VM already exists on other node
2204 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2209 my $newvollist = [];
2212 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2214 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2216 foreach my $opt (keys %$drives) {
2217 my $drive = $drives->{$opt};
2219 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2220 $newid, $storage, $format, $drive->{full
}, $newvollist);
2222 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2224 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2227 delete $newconf->{lock};
2228 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2231 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2232 die "Failed to move config to node '$target' - rename failed: $!\n"
2233 if !rename($conffile, $newconffile);
2236 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2241 sleep 1; # some storage like rbd need to wait before release volume - really?
2243 foreach my $volid (@$newvollist) {
2244 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2247 die "clone failed: $err";
2253 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2256 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2257 # Aquire exclusive lock lock for $newid
2258 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2263 __PACKAGE__-
>register_method({
2264 name
=> 'move_vm_disk',
2265 path
=> '{vmid}/move_disk',
2269 description
=> "Move volume to different storage.",
2271 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2272 "and 'Datastore.AllocateSpace' permissions on the storage.",
2275 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2276 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2280 additionalProperties
=> 0,
2282 node
=> get_standard_option
('pve-node'),
2283 vmid
=> get_standard_option
('pve-vmid'),
2286 description
=> "The disk you want to move.",
2287 enum
=> [ PVE
::QemuServer
::disknames
() ],
2289 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2292 description
=> "Target Format.",
2293 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2298 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2304 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2312 description
=> "the task ID.",
2317 my $rpcenv = PVE
::RPCEnvironment
::get
();
2319 my $authuser = $rpcenv->get_user();
2321 my $node = extract_param
($param, 'node');
2323 my $vmid = extract_param
($param, 'vmid');
2325 my $digest = extract_param
($param, 'digest');
2327 my $disk = extract_param
($param, 'disk');
2329 my $storeid = extract_param
($param, 'storage');
2331 my $format = extract_param
($param, 'format');
2333 my $storecfg = PVE
::Storage
::config
();
2335 my $updatefn = sub {
2337 my $conf = PVE
::QemuServer
::load_config
($vmid);
2339 die "checksum missmatch (file change by other user?)\n"
2340 if $digest && $digest ne $conf->{digest
};
2342 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2344 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2346 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2348 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2351 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2352 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2356 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2357 (!$format || !$oldfmt || $oldfmt eq $format);
2359 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2361 my $running = PVE
::QemuServer
::check_running
($vmid);
2363 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2367 my $newvollist = [];
2370 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2372 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2373 $vmid, $storeid, $format, 1, $newvollist);
2375 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2377 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2379 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2383 foreach my $volid (@$newvollist) {
2384 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2387 die "storage migration failed: $err";
2390 if ($param->{delete}) {
2391 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2396 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2399 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2402 __PACKAGE__-
>register_method({
2403 name
=> 'migrate_vm',
2404 path
=> '{vmid}/migrate',
2408 description
=> "Migrate virtual machine. Creates a new migration task.",
2410 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2413 additionalProperties
=> 0,
2415 node
=> get_standard_option
('pve-node'),
2416 vmid
=> get_standard_option
('pve-vmid'),
2417 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2420 description
=> "Use online/live migration.",
2425 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2432 description
=> "the task ID.",
2437 my $rpcenv = PVE
::RPCEnvironment
::get
();
2439 my $authuser = $rpcenv->get_user();
2441 my $target = extract_param
($param, 'target');
2443 my $localnode = PVE
::INotify
::nodename
();
2444 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2446 PVE
::Cluster
::check_cfs_quorum
();
2448 PVE
::Cluster
::check_node_exists
($target);
2450 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2452 my $vmid = extract_param
($param, 'vmid');
2454 raise_param_exc
({ force
=> "Only root may use this option." })
2455 if $param->{force
} && $authuser ne 'root@pam';
2458 my $conf = PVE
::QemuServer
::load_config
($vmid);
2460 # try to detect errors early
2462 PVE
::QemuServer
::check_lock
($conf);
2464 if (PVE
::QemuServer
::check_running
($vmid)) {
2465 die "cant migrate running VM without --online\n"
2466 if !$param->{online
};
2469 my $storecfg = PVE
::Storage
::config
();
2470 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2472 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2477 my $service = "pvevm:$vmid";
2479 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2481 print "Executing HA migrate for VM $vmid to node $target\n";
2483 PVE
::Tools
::run_command
($cmd);
2488 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2495 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2498 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2503 __PACKAGE__-
>register_method({
2505 path
=> '{vmid}/monitor',
2509 description
=> "Execute Qemu monitor commands.",
2511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2514 additionalProperties
=> 0,
2516 node
=> get_standard_option
('pve-node'),
2517 vmid
=> get_standard_option
('pve-vmid'),
2520 description
=> "The monitor command.",
2524 returns
=> { type
=> 'string'},
2528 my $vmid = $param->{vmid
};
2530 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2534 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2536 $res = "ERROR: $@" if $@;
2541 __PACKAGE__-
>register_method({
2542 name
=> 'resize_vm',
2543 path
=> '{vmid}/resize',
2547 description
=> "Extend volume size.",
2549 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid'),
2556 skiplock
=> get_standard_option
('skiplock'),
2559 description
=> "The disk you want to resize.",
2560 enum
=> [PVE
::QemuServer
::disknames
()],
2564 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2565 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.",
2569 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2575 returns
=> { type
=> 'null'},
2579 my $rpcenv = PVE
::RPCEnvironment
::get
();
2581 my $authuser = $rpcenv->get_user();
2583 my $node = extract_param
($param, 'node');
2585 my $vmid = extract_param
($param, 'vmid');
2587 my $digest = extract_param
($param, 'digest');
2589 my $disk = extract_param
($param, 'disk');
2591 my $sizestr = extract_param
($param, 'size');
2593 my $skiplock = extract_param
($param, 'skiplock');
2594 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2595 if $skiplock && $authuser ne 'root@pam';
2597 my $storecfg = PVE
::Storage
::config
();
2599 my $updatefn = sub {
2601 my $conf = PVE
::QemuServer
::load_config
($vmid);
2603 die "checksum missmatch (file change by other user?)\n"
2604 if $digest && $digest ne $conf->{digest
};
2605 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2607 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2609 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2611 my $volid = $drive->{file
};
2613 die "disk '$disk' has no associated volume\n" if !$volid;
2615 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2617 die "you can't online resize a virtio windows bootdisk\n"
2618 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2620 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2622 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2624 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2626 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2627 my ($ext, $newsize, $unit) = ($1, $2, $4);
2630 $newsize = $newsize * 1024;
2631 } elsif ($unit eq 'M') {
2632 $newsize = $newsize * 1024 * 1024;
2633 } elsif ($unit eq 'G') {
2634 $newsize = $newsize * 1024 * 1024 * 1024;
2635 } elsif ($unit eq 'T') {
2636 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2639 $newsize += $size if $ext;
2640 $newsize = int($newsize);
2642 die "unable to skrink disk size\n" if $newsize < $size;
2644 return if $size == $newsize;
2646 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2648 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2650 $drive->{size
} = $newsize;
2651 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2653 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2656 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2660 __PACKAGE__-
>register_method({
2661 name
=> 'snapshot_list',
2662 path
=> '{vmid}/snapshot',
2664 description
=> "List all snapshots.",
2666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2669 protected
=> 1, # qemu pid files are only readable by root
2671 additionalProperties
=> 0,
2673 vmid
=> get_standard_option
('pve-vmid'),
2674 node
=> get_standard_option
('pve-node'),
2683 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2688 my $vmid = $param->{vmid
};
2690 my $conf = PVE
::QemuServer
::load_config
($vmid);
2691 my $snaphash = $conf->{snapshots
} || {};
2695 foreach my $name (keys %$snaphash) {
2696 my $d = $snaphash->{$name};
2699 snaptime
=> $d->{snaptime
} || 0,
2700 vmstate
=> $d->{vmstate
} ?
1 : 0,
2701 description
=> $d->{description
} || '',
2703 $item->{parent
} = $d->{parent
} if $d->{parent
};
2704 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2708 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2709 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2710 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2712 push @$res, $current;
2717 __PACKAGE__-
>register_method({
2719 path
=> '{vmid}/snapshot',
2723 description
=> "Snapshot a VM.",
2725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2728 additionalProperties
=> 0,
2730 node
=> get_standard_option
('pve-node'),
2731 vmid
=> get_standard_option
('pve-vmid'),
2732 snapname
=> get_standard_option
('pve-snapshot-name'),
2736 description
=> "Save the vmstate",
2741 description
=> "Freeze the filesystem",
2746 description
=> "A textual description or comment.",
2752 description
=> "the task ID.",
2757 my $rpcenv = PVE
::RPCEnvironment
::get
();
2759 my $authuser = $rpcenv->get_user();
2761 my $node = extract_param
($param, 'node');
2763 my $vmid = extract_param
($param, 'vmid');
2765 my $snapname = extract_param
($param, 'snapname');
2767 die "unable to use snapshot name 'current' (reserved name)\n"
2768 if $snapname eq 'current';
2771 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2772 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2773 $param->{freezefs
}, $param->{description
});
2776 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2779 __PACKAGE__-
>register_method({
2780 name
=> 'snapshot_cmd_idx',
2781 path
=> '{vmid}/snapshot/{snapname}',
2788 additionalProperties
=> 0,
2790 vmid
=> get_standard_option
('pve-vmid'),
2791 node
=> get_standard_option
('pve-node'),
2792 snapname
=> get_standard_option
('pve-snapshot-name'),
2801 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2808 push @$res, { cmd
=> 'rollback' };
2809 push @$res, { cmd
=> 'config' };
2814 __PACKAGE__-
>register_method({
2815 name
=> 'update_snapshot_config',
2816 path
=> '{vmid}/snapshot/{snapname}/config',
2820 description
=> "Update snapshot metadata.",
2822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2825 additionalProperties
=> 0,
2827 node
=> get_standard_option
('pve-node'),
2828 vmid
=> get_standard_option
('pve-vmid'),
2829 snapname
=> get_standard_option
('pve-snapshot-name'),
2833 description
=> "A textual description or comment.",
2837 returns
=> { type
=> 'null' },
2841 my $rpcenv = PVE
::RPCEnvironment
::get
();
2843 my $authuser = $rpcenv->get_user();
2845 my $vmid = extract_param
($param, 'vmid');
2847 my $snapname = extract_param
($param, 'snapname');
2849 return undef if !defined($param->{description
});
2851 my $updatefn = sub {
2853 my $conf = PVE
::QemuServer
::load_config
($vmid);
2855 PVE
::QemuServer
::check_lock
($conf);
2857 my $snap = $conf->{snapshots
}->{$snapname};
2859 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2861 $snap->{description
} = $param->{description
} if defined($param->{description
});
2863 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2866 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2871 __PACKAGE__-
>register_method({
2872 name
=> 'get_snapshot_config',
2873 path
=> '{vmid}/snapshot/{snapname}/config',
2876 description
=> "Get snapshot configuration",
2878 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2881 additionalProperties
=> 0,
2883 node
=> get_standard_option
('pve-node'),
2884 vmid
=> get_standard_option
('pve-vmid'),
2885 snapname
=> get_standard_option
('pve-snapshot-name'),
2888 returns
=> { type
=> "object" },
2892 my $rpcenv = PVE
::RPCEnvironment
::get
();
2894 my $authuser = $rpcenv->get_user();
2896 my $vmid = extract_param
($param, 'vmid');
2898 my $snapname = extract_param
($param, 'snapname');
2900 my $conf = PVE
::QemuServer
::load_config
($vmid);
2902 my $snap = $conf->{snapshots
}->{$snapname};
2904 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2909 __PACKAGE__-
>register_method({
2911 path
=> '{vmid}/snapshot/{snapname}/rollback',
2915 description
=> "Rollback VM state to specified snapshot.",
2917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2920 additionalProperties
=> 0,
2922 node
=> get_standard_option
('pve-node'),
2923 vmid
=> get_standard_option
('pve-vmid'),
2924 snapname
=> get_standard_option
('pve-snapshot-name'),
2929 description
=> "the task ID.",
2934 my $rpcenv = PVE
::RPCEnvironment
::get
();
2936 my $authuser = $rpcenv->get_user();
2938 my $node = extract_param
($param, 'node');
2940 my $vmid = extract_param
($param, 'vmid');
2942 my $snapname = extract_param
($param, 'snapname');
2945 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2946 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2949 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2952 __PACKAGE__-
>register_method({
2953 name
=> 'delsnapshot',
2954 path
=> '{vmid}/snapshot/{snapname}',
2958 description
=> "Delete a VM snapshot.",
2960 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2963 additionalProperties
=> 0,
2965 node
=> get_standard_option
('pve-node'),
2966 vmid
=> get_standard_option
('pve-vmid'),
2967 snapname
=> get_standard_option
('pve-snapshot-name'),
2971 description
=> "For removal from config file, even if removing disk snapshots fails.",
2977 description
=> "the task ID.",
2982 my $rpcenv = PVE
::RPCEnvironment
::get
();
2984 my $authuser = $rpcenv->get_user();
2986 my $node = extract_param
($param, 'node');
2988 my $vmid = extract_param
($param, 'vmid');
2990 my $snapname = extract_param
($param, 'snapname');
2993 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2994 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2997 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3000 __PACKAGE__-
>register_method({
3002 path
=> '{vmid}/template',
3006 description
=> "Create a Template.",
3008 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3009 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3012 additionalProperties
=> 0,
3014 node
=> get_standard_option
('pve-node'),
3015 vmid
=> get_standard_option
('pve-vmid'),
3019 description
=> "If you want to convert only 1 disk to base image.",
3020 enum
=> [PVE
::QemuServer
::disknames
()],
3025 returns
=> { type
=> 'null'},
3029 my $rpcenv = PVE
::RPCEnvironment
::get
();
3031 my $authuser = $rpcenv->get_user();
3033 my $node = extract_param
($param, 'node');
3035 my $vmid = extract_param
($param, 'vmid');
3037 my $disk = extract_param
($param, 'disk');
3039 my $updatefn = sub {
3041 my $conf = PVE
::QemuServer
::load_config
($vmid);
3043 PVE
::QemuServer
::check_lock
($conf);
3045 die "unable to create template, because VM contains snapshots\n"
3046 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3048 die "you can't convert a template to a template\n"
3049 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3051 die "you can't convert a VM to template if VM is running\n"
3052 if PVE
::QemuServer
::check_running
($vmid);
3055 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3058 $conf->{template
} = 1;
3059 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3061 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3064 PVE
::QemuServer
::lock_config
($vmid, $updatefn);