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
1397 __PACKAGE__-
>register_method({
1399 path
=> '{vmid}/status',
1402 description
=> "Directory index",
1407 additionalProperties
=> 0,
1409 node
=> get_standard_option
('pve-node'),
1410 vmid
=> get_standard_option
('pve-vmid'),
1418 subdir
=> { type
=> 'string' },
1421 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1427 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1430 { subdir
=> 'current' },
1431 { subdir
=> 'start' },
1432 { subdir
=> 'stop' },
1438 my $vm_is_ha_managed = sub {
1441 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1442 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1448 __PACKAGE__-
>register_method({
1449 name
=> 'vm_status',
1450 path
=> '{vmid}/status/current',
1453 protected
=> 1, # qemu pid files are only readable by root
1454 description
=> "Get virtual machine status.",
1456 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1459 additionalProperties
=> 0,
1461 node
=> get_standard_option
('pve-node'),
1462 vmid
=> get_standard_option
('pve-vmid'),
1465 returns
=> { type
=> 'object' },
1470 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1472 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1473 my $status = $vmstatus->{$param->{vmid
}};
1475 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1477 if ($conf->{vga
} && ($conf->{vga
} eq 'qxl')) {
1478 $status->{spice
} = 1;
1484 __PACKAGE__-
>register_method({
1486 path
=> '{vmid}/status/start',
1490 description
=> "Start virtual machine.",
1492 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1495 additionalProperties
=> 0,
1497 node
=> get_standard_option
('pve-node'),
1498 vmid
=> get_standard_option
('pve-vmid'),
1499 skiplock
=> get_standard_option
('skiplock'),
1500 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1501 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1502 machine
=> get_standard_option
('pve-qm-machine'),
1511 my $rpcenv = PVE
::RPCEnvironment
::get
();
1513 my $authuser = $rpcenv->get_user();
1515 my $node = extract_param
($param, 'node');
1517 my $vmid = extract_param
($param, 'vmid');
1519 my $machine = extract_param
($param, 'machine');
1521 my $stateuri = extract_param
($param, 'stateuri');
1522 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1523 if $stateuri && $authuser ne 'root@pam';
1525 my $skiplock = extract_param
($param, 'skiplock');
1526 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1527 if $skiplock && $authuser ne 'root@pam';
1529 my $migratedfrom = extract_param
($param, 'migratedfrom');
1530 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1531 if $migratedfrom && $authuser ne 'root@pam';
1533 my $storecfg = PVE
::Storage
::config
();
1535 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1536 $rpcenv->{type
} ne 'ha') {
1541 my $service = "pvevm:$vmid";
1543 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1545 print "Executing HA start for VM $vmid\n";
1547 PVE
::Tools
::run_command
($cmd);
1552 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1559 syslog
('info', "start VM $vmid: $upid\n");
1561 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1566 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1570 __PACKAGE__-
>register_method({
1572 path
=> '{vmid}/status/stop',
1576 description
=> "Stop virtual machine.",
1578 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1581 additionalProperties
=> 0,
1583 node
=> get_standard_option
('pve-node'),
1584 vmid
=> get_standard_option
('pve-vmid'),
1585 skiplock
=> get_standard_option
('skiplock'),
1586 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1588 description
=> "Wait maximal timeout seconds.",
1594 description
=> "Do not decativate storage volumes.",
1607 my $rpcenv = PVE
::RPCEnvironment
::get
();
1609 my $authuser = $rpcenv->get_user();
1611 my $node = extract_param
($param, 'node');
1613 my $vmid = extract_param
($param, 'vmid');
1615 my $skiplock = extract_param
($param, 'skiplock');
1616 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1617 if $skiplock && $authuser ne 'root@pam';
1619 my $keepActive = extract_param
($param, 'keepActive');
1620 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1621 if $keepActive && $authuser ne 'root@pam';
1623 my $migratedfrom = extract_param
($param, 'migratedfrom');
1624 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1625 if $migratedfrom && $authuser ne 'root@pam';
1628 my $storecfg = PVE
::Storage
::config
();
1630 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1635 my $service = "pvevm:$vmid";
1637 my $cmd = ['clusvcadm', '-d', $service];
1639 print "Executing HA stop for VM $vmid\n";
1641 PVE
::Tools
::run_command
($cmd);
1646 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1652 syslog
('info', "stop VM $vmid: $upid\n");
1654 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1655 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1660 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1664 __PACKAGE__-
>register_method({
1666 path
=> '{vmid}/status/reset',
1670 description
=> "Reset virtual machine.",
1672 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1675 additionalProperties
=> 0,
1677 node
=> get_standard_option
('pve-node'),
1678 vmid
=> get_standard_option
('pve-vmid'),
1679 skiplock
=> get_standard_option
('skiplock'),
1688 my $rpcenv = PVE
::RPCEnvironment
::get
();
1690 my $authuser = $rpcenv->get_user();
1692 my $node = extract_param
($param, 'node');
1694 my $vmid = extract_param
($param, 'vmid');
1696 my $skiplock = extract_param
($param, 'skiplock');
1697 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1698 if $skiplock && $authuser ne 'root@pam';
1700 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1705 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1710 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1713 __PACKAGE__-
>register_method({
1714 name
=> 'vm_shutdown',
1715 path
=> '{vmid}/status/shutdown',
1719 description
=> "Shutdown virtual machine.",
1721 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1724 additionalProperties
=> 0,
1726 node
=> get_standard_option
('pve-node'),
1727 vmid
=> get_standard_option
('pve-vmid'),
1728 skiplock
=> get_standard_option
('skiplock'),
1730 description
=> "Wait maximal timeout seconds.",
1736 description
=> "Make sure the VM stops.",
1742 description
=> "Do not decativate storage volumes.",
1755 my $rpcenv = PVE
::RPCEnvironment
::get
();
1757 my $authuser = $rpcenv->get_user();
1759 my $node = extract_param
($param, 'node');
1761 my $vmid = extract_param
($param, 'vmid');
1763 my $skiplock = extract_param
($param, 'skiplock');
1764 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1765 if $skiplock && $authuser ne 'root@pam';
1767 my $keepActive = extract_param
($param, 'keepActive');
1768 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1769 if $keepActive && $authuser ne 'root@pam';
1771 my $storecfg = PVE
::Storage
::config
();
1776 syslog
('info', "shutdown VM $vmid: $upid\n");
1778 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1779 1, $param->{forceStop
}, $keepActive);
1784 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1787 __PACKAGE__-
>register_method({
1788 name
=> 'vm_suspend',
1789 path
=> '{vmid}/status/suspend',
1793 description
=> "Suspend virtual machine.",
1795 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1798 additionalProperties
=> 0,
1800 node
=> get_standard_option
('pve-node'),
1801 vmid
=> get_standard_option
('pve-vmid'),
1802 skiplock
=> get_standard_option
('skiplock'),
1811 my $rpcenv = PVE
::RPCEnvironment
::get
();
1813 my $authuser = $rpcenv->get_user();
1815 my $node = extract_param
($param, 'node');
1817 my $vmid = extract_param
($param, 'vmid');
1819 my $skiplock = extract_param
($param, 'skiplock');
1820 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1821 if $skiplock && $authuser ne 'root@pam';
1823 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1828 syslog
('info', "suspend VM $vmid: $upid\n");
1830 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1835 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1838 __PACKAGE__-
>register_method({
1839 name
=> 'vm_resume',
1840 path
=> '{vmid}/status/resume',
1844 description
=> "Resume virtual machine.",
1846 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1849 additionalProperties
=> 0,
1851 node
=> get_standard_option
('pve-node'),
1852 vmid
=> get_standard_option
('pve-vmid'),
1853 skiplock
=> get_standard_option
('skiplock'),
1862 my $rpcenv = PVE
::RPCEnvironment
::get
();
1864 my $authuser = $rpcenv->get_user();
1866 my $node = extract_param
($param, 'node');
1868 my $vmid = extract_param
($param, 'vmid');
1870 my $skiplock = extract_param
($param, 'skiplock');
1871 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1872 if $skiplock && $authuser ne 'root@pam';
1874 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1879 syslog
('info', "resume VM $vmid: $upid\n");
1881 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1886 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1889 __PACKAGE__-
>register_method({
1890 name
=> 'vm_sendkey',
1891 path
=> '{vmid}/sendkey',
1895 description
=> "Send key event to virtual machine.",
1897 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1900 additionalProperties
=> 0,
1902 node
=> get_standard_option
('pve-node'),
1903 vmid
=> get_standard_option
('pve-vmid'),
1904 skiplock
=> get_standard_option
('skiplock'),
1906 description
=> "The key (qemu monitor encoding).",
1911 returns
=> { type
=> 'null'},
1915 my $rpcenv = PVE
::RPCEnvironment
::get
();
1917 my $authuser = $rpcenv->get_user();
1919 my $node = extract_param
($param, 'node');
1921 my $vmid = extract_param
($param, 'vmid');
1923 my $skiplock = extract_param
($param, 'skiplock');
1924 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1925 if $skiplock && $authuser ne 'root@pam';
1927 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1932 __PACKAGE__-
>register_method({
1933 name
=> 'vm_feature',
1934 path
=> '{vmid}/feature',
1938 description
=> "Check if feature for virtual machine is available.",
1940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1943 additionalProperties
=> 0,
1945 node
=> get_standard_option
('pve-node'),
1946 vmid
=> get_standard_option
('pve-vmid'),
1948 description
=> "Feature to check.",
1950 enum
=> [ 'snapshot', 'clone', 'copy' ],
1952 snapname
=> get_standard_option
('pve-snapshot-name', {
1960 hasFeature
=> { type
=> 'boolean' },
1963 items
=> { type
=> 'string' },
1970 my $node = extract_param
($param, 'node');
1972 my $vmid = extract_param
($param, 'vmid');
1974 my $snapname = extract_param
($param, 'snapname');
1976 my $feature = extract_param
($param, 'feature');
1978 my $running = PVE
::QemuServer
::check_running
($vmid);
1980 my $conf = PVE
::QemuServer
::load_config
($vmid);
1983 my $snap = $conf->{snapshots
}->{$snapname};
1984 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1987 my $storecfg = PVE
::Storage
::config
();
1989 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1990 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1993 hasFeature
=> $hasFeature,
1994 nodes
=> [ keys %$nodelist ],
1998 __PACKAGE__-
>register_method({
2000 path
=> '{vmid}/clone',
2004 description
=> "Create a copy of virtual machine/template.",
2006 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2007 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2008 "'Datastore.AllocateSpace' on any used storage.",
2011 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2013 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2014 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2019 additionalProperties
=> 0,
2021 node
=> get_standard_option
('pve-node'),
2022 vmid
=> get_standard_option
('pve-vmid'),
2023 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2026 type
=> 'string', format
=> 'dns-name',
2027 description
=> "Set a name for the new VM.",
2032 description
=> "Description for the new VM.",
2036 type
=> 'string', format
=> 'pve-poolid',
2037 description
=> "Add the new VM to the specified pool.",
2039 snapname
=> get_standard_option
('pve-snapshot-name', {
2043 storage
=> get_standard_option
('pve-storage-id', {
2044 description
=> "Target storage for full clone.",
2049 description
=> "Target format for file storage.",
2053 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2058 description
=> "Create a full copy of all disk. This is always done when " .
2059 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2062 target
=> get_standard_option
('pve-node', {
2063 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2074 my $rpcenv = PVE
::RPCEnvironment
::get
();
2076 my $authuser = $rpcenv->get_user();
2078 my $node = extract_param
($param, 'node');
2080 my $vmid = extract_param
($param, 'vmid');
2082 my $newid = extract_param
($param, 'newid');
2084 my $pool = extract_param
($param, 'pool');
2086 if (defined($pool)) {
2087 $rpcenv->check_pool_exist($pool);
2090 my $snapname = extract_param
($param, 'snapname');
2092 my $storage = extract_param
($param, 'storage');
2094 my $format = extract_param
($param, 'format');
2096 my $target = extract_param
($param, 'target');
2098 my $localnode = PVE
::INotify
::nodename
();
2100 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2102 PVE
::Cluster
::check_node_exists
($target) if $target;
2104 my $storecfg = PVE
::Storage
::config
();
2107 # check if storage is enabled on local node
2108 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2110 # check if storage is available on target node
2111 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2112 # clone only works if target storage is shared
2113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2114 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2118 PVE
::Cluster
::check_cfs_quorum
();
2120 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2122 # exclusive lock if VM is running - else shared lock is enough;
2123 my $shared_lock = $running ?
0 : 1;
2127 # do all tests after lock
2128 # we also try to do all tests before we fork the worker
2130 my $conf = PVE
::QemuServer
::load_config
($vmid);
2132 PVE
::QemuServer
::check_lock
($conf);
2134 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2136 die "unexpected state change\n" if $verify_running != $running;
2138 die "snapshot '$snapname' does not exist\n"
2139 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2141 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2143 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2145 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2147 my $conffile = PVE
::QemuServer
::config_file
($newid);
2149 die "unable to create VM $newid: config file already exists\n"
2152 my $newconf = { lock => 'clone' };
2156 foreach my $opt (keys %$oldconf) {
2157 my $value = $oldconf->{$opt};
2159 # do not copy snapshot related info
2160 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2161 $opt eq 'vmstate' || $opt eq 'snapstate';
2163 # always change MAC! address
2164 if ($opt =~ m/^net(\d+)$/) {
2165 my $net = PVE
::QemuServer
::parse_net
($value);
2166 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2167 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2168 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2169 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2170 $newconf->{$opt} = $value; # simply copy configuration
2172 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2173 die "Full clone feature is not available"
2174 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2177 $drives->{$opt} = $drive;
2178 push @$vollist, $drive->{file
};
2181 # copy everything else
2182 $newconf->{$opt} = $value;
2186 delete $newconf->{template
};
2188 if ($param->{name
}) {
2189 $newconf->{name
} = $param->{name
};
2191 if ($oldconf->{name
}) {
2192 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2194 $newconf->{name
} = "Copy-of-VM-$vmid";
2198 if ($param->{description
}) {
2199 $newconf->{description
} = $param->{description
};
2202 # create empty/temp config - this fails if VM already exists on other node
2203 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2208 my $newvollist = [];
2211 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2213 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2215 foreach my $opt (keys %$drives) {
2216 my $drive = $drives->{$opt};
2218 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2219 $newid, $storage, $format, $drive->{full
}, $newvollist);
2221 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2223 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2226 delete $newconf->{lock};
2227 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2230 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2231 die "Failed to move config to node '$target' - rename failed: $!\n"
2232 if !rename($conffile, $newconffile);
2235 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2240 sleep 1; # some storage like rbd need to wait before release volume - really?
2242 foreach my $volid (@$newvollist) {
2243 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2246 die "clone failed: $err";
2252 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2255 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2256 # Aquire exclusive lock lock for $newid
2257 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2262 __PACKAGE__-
>register_method({
2263 name
=> 'move_vm_disk',
2264 path
=> '{vmid}/move_disk',
2268 description
=> "Move volume to different storage.",
2270 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2271 "and 'Datastore.AllocateSpace' permissions on the storage.",
2274 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2275 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2279 additionalProperties
=> 0,
2281 node
=> get_standard_option
('pve-node'),
2282 vmid
=> get_standard_option
('pve-vmid'),
2285 description
=> "The disk you want to move.",
2286 enum
=> [ PVE
::QemuServer
::disknames
() ],
2288 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2291 description
=> "Target Format.",
2292 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2297 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2303 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2311 description
=> "the task ID.",
2316 my $rpcenv = PVE
::RPCEnvironment
::get
();
2318 my $authuser = $rpcenv->get_user();
2320 my $node = extract_param
($param, 'node');
2322 my $vmid = extract_param
($param, 'vmid');
2324 my $digest = extract_param
($param, 'digest');
2326 my $disk = extract_param
($param, 'disk');
2328 my $storeid = extract_param
($param, 'storage');
2330 my $format = extract_param
($param, 'format');
2332 my $storecfg = PVE
::Storage
::config
();
2334 my $updatefn = sub {
2336 my $conf = PVE
::QemuServer
::load_config
($vmid);
2338 die "checksum missmatch (file change by other user?)\n"
2339 if $digest && $digest ne $conf->{digest
};
2341 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2343 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2345 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2347 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2350 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2351 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2355 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2356 (!$format || !$oldfmt || $oldfmt eq $format);
2358 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2360 my $running = PVE
::QemuServer
::check_running
($vmid);
2362 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2366 my $newvollist = [];
2369 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2371 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2372 $vmid, $storeid, $format, 1, $newvollist);
2374 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2376 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2378 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2382 foreach my $volid (@$newvollist) {
2383 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2386 die "storage migration failed: $err";
2389 if ($param->{delete}) {
2390 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2395 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2398 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2401 __PACKAGE__-
>register_method({
2402 name
=> 'migrate_vm',
2403 path
=> '{vmid}/migrate',
2407 description
=> "Migrate virtual machine. Creates a new migration task.",
2409 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2412 additionalProperties
=> 0,
2414 node
=> get_standard_option
('pve-node'),
2415 vmid
=> get_standard_option
('pve-vmid'),
2416 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2419 description
=> "Use online/live migration.",
2424 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2431 description
=> "the task ID.",
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2438 my $authuser = $rpcenv->get_user();
2440 my $target = extract_param
($param, 'target');
2442 my $localnode = PVE
::INotify
::nodename
();
2443 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2445 PVE
::Cluster
::check_cfs_quorum
();
2447 PVE
::Cluster
::check_node_exists
($target);
2449 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2451 my $vmid = extract_param
($param, 'vmid');
2453 raise_param_exc
({ force
=> "Only root may use this option." })
2454 if $param->{force
} && $authuser ne 'root@pam';
2457 my $conf = PVE
::QemuServer
::load_config
($vmid);
2459 # try to detect errors early
2461 PVE
::QemuServer
::check_lock
($conf);
2463 if (PVE
::QemuServer
::check_running
($vmid)) {
2464 die "cant migrate running VM without --online\n"
2465 if !$param->{online
};
2468 my $storecfg = PVE
::Storage
::config
();
2469 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2471 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2476 my $service = "pvevm:$vmid";
2478 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2480 print "Executing HA migrate for VM $vmid to node $target\n";
2482 PVE
::Tools
::run_command
($cmd);
2487 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2494 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2497 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2502 __PACKAGE__-
>register_method({
2504 path
=> '{vmid}/monitor',
2508 description
=> "Execute Qemu monitor commands.",
2510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2513 additionalProperties
=> 0,
2515 node
=> get_standard_option
('pve-node'),
2516 vmid
=> get_standard_option
('pve-vmid'),
2519 description
=> "The monitor command.",
2523 returns
=> { type
=> 'string'},
2527 my $vmid = $param->{vmid
};
2529 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2533 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2535 $res = "ERROR: $@" if $@;
2540 __PACKAGE__-
>register_method({
2541 name
=> 'resize_vm',
2542 path
=> '{vmid}/resize',
2546 description
=> "Extend volume size.",
2548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2551 additionalProperties
=> 0,
2553 node
=> get_standard_option
('pve-node'),
2554 vmid
=> get_standard_option
('pve-vmid'),
2555 skiplock
=> get_standard_option
('skiplock'),
2558 description
=> "The disk you want to resize.",
2559 enum
=> [PVE
::QemuServer
::disknames
()],
2563 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2564 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.",
2568 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2574 returns
=> { type
=> 'null'},
2578 my $rpcenv = PVE
::RPCEnvironment
::get
();
2580 my $authuser = $rpcenv->get_user();
2582 my $node = extract_param
($param, 'node');
2584 my $vmid = extract_param
($param, 'vmid');
2586 my $digest = extract_param
($param, 'digest');
2588 my $disk = extract_param
($param, 'disk');
2590 my $sizestr = extract_param
($param, 'size');
2592 my $skiplock = extract_param
($param, 'skiplock');
2593 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2594 if $skiplock && $authuser ne 'root@pam';
2596 my $storecfg = PVE
::Storage
::config
();
2598 my $updatefn = sub {
2600 my $conf = PVE
::QemuServer
::load_config
($vmid);
2602 die "checksum missmatch (file change by other user?)\n"
2603 if $digest && $digest ne $conf->{digest
};
2604 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2606 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2608 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2610 my $volid = $drive->{file
};
2612 die "disk '$disk' has no associated volume\n" if !$volid;
2614 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2616 die "you can't online resize a virtio windows bootdisk\n"
2617 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2619 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2621 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2623 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2625 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2626 my ($ext, $newsize, $unit) = ($1, $2, $4);
2629 $newsize = $newsize * 1024;
2630 } elsif ($unit eq 'M') {
2631 $newsize = $newsize * 1024 * 1024;
2632 } elsif ($unit eq 'G') {
2633 $newsize = $newsize * 1024 * 1024 * 1024;
2634 } elsif ($unit eq 'T') {
2635 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2638 $newsize += $size if $ext;
2639 $newsize = int($newsize);
2641 die "unable to skrink disk size\n" if $newsize < $size;
2643 return if $size == $newsize;
2645 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2647 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2649 $drive->{size
} = $newsize;
2650 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2652 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2655 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2659 __PACKAGE__-
>register_method({
2660 name
=> 'snapshot_list',
2661 path
=> '{vmid}/snapshot',
2663 description
=> "List all snapshots.",
2665 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2668 protected
=> 1, # qemu pid files are only readable by root
2670 additionalProperties
=> 0,
2672 vmid
=> get_standard_option
('pve-vmid'),
2673 node
=> get_standard_option
('pve-node'),
2682 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2687 my $vmid = $param->{vmid
};
2689 my $conf = PVE
::QemuServer
::load_config
($vmid);
2690 my $snaphash = $conf->{snapshots
} || {};
2694 foreach my $name (keys %$snaphash) {
2695 my $d = $snaphash->{$name};
2698 snaptime
=> $d->{snaptime
} || 0,
2699 vmstate
=> $d->{vmstate
} ?
1 : 0,
2700 description
=> $d->{description
} || '',
2702 $item->{parent
} = $d->{parent
} if $d->{parent
};
2703 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2707 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2708 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2709 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2711 push @$res, $current;
2716 __PACKAGE__-
>register_method({
2718 path
=> '{vmid}/snapshot',
2722 description
=> "Snapshot a VM.",
2724 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2727 additionalProperties
=> 0,
2729 node
=> get_standard_option
('pve-node'),
2730 vmid
=> get_standard_option
('pve-vmid'),
2731 snapname
=> get_standard_option
('pve-snapshot-name'),
2735 description
=> "Save the vmstate",
2740 description
=> "Freeze the filesystem",
2745 description
=> "A textual description or comment.",
2751 description
=> "the task ID.",
2756 my $rpcenv = PVE
::RPCEnvironment
::get
();
2758 my $authuser = $rpcenv->get_user();
2760 my $node = extract_param
($param, 'node');
2762 my $vmid = extract_param
($param, 'vmid');
2764 my $snapname = extract_param
($param, 'snapname');
2766 die "unable to use snapshot name 'current' (reserved name)\n"
2767 if $snapname eq 'current';
2770 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2771 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2772 $param->{freezefs
}, $param->{description
});
2775 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2778 __PACKAGE__-
>register_method({
2779 name
=> 'snapshot_cmd_idx',
2780 path
=> '{vmid}/snapshot/{snapname}',
2787 additionalProperties
=> 0,
2789 vmid
=> get_standard_option
('pve-vmid'),
2790 node
=> get_standard_option
('pve-node'),
2791 snapname
=> get_standard_option
('pve-snapshot-name'),
2800 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2807 push @$res, { cmd
=> 'rollback' };
2808 push @$res, { cmd
=> 'config' };
2813 __PACKAGE__-
>register_method({
2814 name
=> 'update_snapshot_config',
2815 path
=> '{vmid}/snapshot/{snapname}/config',
2819 description
=> "Update snapshot metadata.",
2821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2824 additionalProperties
=> 0,
2826 node
=> get_standard_option
('pve-node'),
2827 vmid
=> get_standard_option
('pve-vmid'),
2828 snapname
=> get_standard_option
('pve-snapshot-name'),
2832 description
=> "A textual description or comment.",
2836 returns
=> { type
=> 'null' },
2840 my $rpcenv = PVE
::RPCEnvironment
::get
();
2842 my $authuser = $rpcenv->get_user();
2844 my $vmid = extract_param
($param, 'vmid');
2846 my $snapname = extract_param
($param, 'snapname');
2848 return undef if !defined($param->{description
});
2850 my $updatefn = sub {
2852 my $conf = PVE
::QemuServer
::load_config
($vmid);
2854 PVE
::QemuServer
::check_lock
($conf);
2856 my $snap = $conf->{snapshots
}->{$snapname};
2858 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2860 $snap->{description
} = $param->{description
} if defined($param->{description
});
2862 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2865 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2870 __PACKAGE__-
>register_method({
2871 name
=> 'get_snapshot_config',
2872 path
=> '{vmid}/snapshot/{snapname}/config',
2875 description
=> "Get snapshot configuration",
2877 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2880 additionalProperties
=> 0,
2882 node
=> get_standard_option
('pve-node'),
2883 vmid
=> get_standard_option
('pve-vmid'),
2884 snapname
=> get_standard_option
('pve-snapshot-name'),
2887 returns
=> { type
=> "object" },
2891 my $rpcenv = PVE
::RPCEnvironment
::get
();
2893 my $authuser = $rpcenv->get_user();
2895 my $vmid = extract_param
($param, 'vmid');
2897 my $snapname = extract_param
($param, 'snapname');
2899 my $conf = PVE
::QemuServer
::load_config
($vmid);
2901 my $snap = $conf->{snapshots
}->{$snapname};
2903 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2908 __PACKAGE__-
>register_method({
2910 path
=> '{vmid}/snapshot/{snapname}/rollback',
2914 description
=> "Rollback VM state to specified snapshot.",
2916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2919 additionalProperties
=> 0,
2921 node
=> get_standard_option
('pve-node'),
2922 vmid
=> get_standard_option
('pve-vmid'),
2923 snapname
=> get_standard_option
('pve-snapshot-name'),
2928 description
=> "the task ID.",
2933 my $rpcenv = PVE
::RPCEnvironment
::get
();
2935 my $authuser = $rpcenv->get_user();
2937 my $node = extract_param
($param, 'node');
2939 my $vmid = extract_param
($param, 'vmid');
2941 my $snapname = extract_param
($param, 'snapname');
2944 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2945 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2948 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2951 __PACKAGE__-
>register_method({
2952 name
=> 'delsnapshot',
2953 path
=> '{vmid}/snapshot/{snapname}',
2957 description
=> "Delete a VM snapshot.",
2959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2962 additionalProperties
=> 0,
2964 node
=> get_standard_option
('pve-node'),
2965 vmid
=> get_standard_option
('pve-vmid'),
2966 snapname
=> get_standard_option
('pve-snapshot-name'),
2970 description
=> "For removal from config file, even if removing disk snapshots fails.",
2976 description
=> "the task ID.",
2981 my $rpcenv = PVE
::RPCEnvironment
::get
();
2983 my $authuser = $rpcenv->get_user();
2985 my $node = extract_param
($param, 'node');
2987 my $vmid = extract_param
($param, 'vmid');
2989 my $snapname = extract_param
($param, 'snapname');
2992 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2993 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2996 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2999 __PACKAGE__-
>register_method({
3001 path
=> '{vmid}/template',
3005 description
=> "Create a Template.",
3007 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3008 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3011 additionalProperties
=> 0,
3013 node
=> get_standard_option
('pve-node'),
3014 vmid
=> get_standard_option
('pve-vmid'),
3018 description
=> "If you want to convert only 1 disk to base image.",
3019 enum
=> [PVE
::QemuServer
::disknames
()],
3024 returns
=> { type
=> 'null'},
3028 my $rpcenv = PVE
::RPCEnvironment
::get
();
3030 my $authuser = $rpcenv->get_user();
3032 my $node = extract_param
($param, 'node');
3034 my $vmid = extract_param
($param, 'vmid');
3036 my $disk = extract_param
($param, 'disk');
3038 my $updatefn = sub {
3040 my $conf = PVE
::QemuServer
::load_config
($vmid);
3042 PVE
::QemuServer
::check_lock
($conf);
3044 die "unable to create template, because VM contains snapshots\n"
3045 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3047 die "you can't convert a template to a template\n"
3048 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3050 die "you can't convert a VM to template if VM is running\n"
3051 if PVE
::QemuServer
::check_running
($vmid);
3054 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3057 $conf->{template
} = 1;
3058 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3060 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3063 PVE
::QemuServer
::lock_config
($vmid, $updatefn);