1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
22 use PVE
::API2
::Firewall
::VM
;
24 use Data
::Dumper
; # fixme: remove
26 use base
qw(PVE::RESTHandler);
28 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.";
30 my $resolve_cdrom_alias = sub {
33 if (my $value = $param->{cdrom
}) {
34 $value .= ",media=cdrom" if $value !~ m/media=/;
35 $param->{ide2
} = $value;
36 delete $param->{cdrom
};
40 my $test_deallocate_drive = sub {
41 my ($storecfg, $vmid, $key, $drive, $force) = @_;
43 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
44 my $volid = $drive->{file
};
45 if ( PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
46 if ($force || $key =~ m/^unused/) {
47 my $sid = PVE
::Storage
::parse_volume_id
($volid);
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
75 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
80 my $check_storage_access_clone = sub {
81 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
85 PVE
::QemuServer
::foreach_drive
($conf, sub {
86 my ($ds, $drive) = @_;
88 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
90 my $volid = $drive->{file
};
92 return if !$volid || $volid eq 'none';
95 if ($volid eq 'cdrom') {
96 $rpcenv->check($authuser, "/", ['Sys.Console']);
98 # we simply allow access
99 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
100 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
101 $sharedvm = 0 if !$scfg->{shared
};
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
109 $sid = $storage if $storage;
110 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
117 # Note: $pool is only needed when creating a VM, because pool permissions
118 # are automatically inherited if VM already exists inside a pool.
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
125 PVE
::QemuServer
::foreach_drive
($settings, sub {
126 my ($ds, $disk) = @_;
128 my $volid = $disk->{file
};
130 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
131 delete $disk->{size
};
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
134 my ($storeid, $size) = ($2 || $default_storage, $3);
135 die "no storage ID specified (and no default storage)\n" if !$storeid;
136 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
137 my $fmt = $disk->{format
} || $defformat;
138 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
139 $fmt, undef, $size*1024*1024);
140 $disk->{file
} = $volid;
141 $disk->{size
} = $size*1024*1024*1024;
142 push @$vollist, $volid;
143 delete $disk->{format
}; # no longer needed
144 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
149 my $volid_is_new = 1;
152 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
153 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
158 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
160 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
162 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
164 die "volume $volid does not exists\n" if !$size;
166 $disk->{size
} = $size;
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 # free allocated images on error
175 syslog
('err', "VM $vmid creating disks failed");
176 foreach my $volid (@$vollist) {
177 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
183 # modify vm config if everything went well
184 foreach my $ds (keys %$res) {
185 $conf->{$ds} = $res->{$ds};
191 my $check_vm_modify_config_perm = sub {
192 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
194 return 1 if $authuser eq 'root@pam';
196 foreach my $opt (@$key_list) {
197 # disk checks need to be done somewhere else
198 next if PVE
::QemuServer
::valid_drivename
($opt);
200 if ($opt eq 'sockets' || $opt eq 'cores' ||
201 $opt eq 'cpu' || $opt eq 'smp' ||
202 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
203 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
204 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
205 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
206 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
207 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
208 } elsif ($opt eq 'args' || $opt eq 'lock') {
209 die "only root can set '$opt' config\n";
210 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
211 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
212 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
213 } elsif ($opt =~ m/^net\d+$/) {
214 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
216 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
223 __PACKAGE__-
>register_method({
227 description
=> "Virtual machine index (per node).",
229 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
233 protected
=> 1, # qemu pid files are only readable by root
235 additionalProperties
=> 0,
237 node
=> get_standard_option
('pve-node'),
246 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
251 my $rpcenv = PVE
::RPCEnvironment
::get
();
252 my $authuser = $rpcenv->get_user();
254 my $vmstatus = PVE
::QemuServer
::vmstatus
();
257 foreach my $vmid (keys %$vmstatus) {
258 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
260 my $data = $vmstatus->{$vmid};
261 $data->{vmid
} = int($vmid);
270 __PACKAGE__-
>register_method({
274 description
=> "Create or restore a virtual machine.",
276 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
277 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
278 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
279 user
=> 'all', # check inside
284 additionalProperties
=> 0,
285 properties
=> PVE
::QemuServer
::json_config_properties
(
287 node
=> get_standard_option
('pve-node'),
288 vmid
=> get_standard_option
('pve-vmid'),
290 description
=> "The backup file.",
295 storage
=> get_standard_option
('pve-storage-id', {
296 description
=> "Default storage.",
302 description
=> "Allow to overwrite existing VM.",
303 requires
=> 'archive',
308 description
=> "Assign a unique random ethernet address.",
309 requires
=> 'archive',
313 type
=> 'string', format
=> 'pve-poolid',
314 description
=> "Add the VM to the specified pool.",
324 my $rpcenv = PVE
::RPCEnvironment
::get
();
326 my $authuser = $rpcenv->get_user();
328 my $node = extract_param
($param, 'node');
330 my $vmid = extract_param
($param, 'vmid');
332 my $archive = extract_param
($param, 'archive');
334 my $storage = extract_param
($param, 'storage');
336 my $force = extract_param
($param, 'force');
338 my $unique = extract_param
($param, 'unique');
340 my $pool = extract_param
($param, 'pool');
342 my $filename = PVE
::QemuServer
::config_file
($vmid);
344 my $storecfg = PVE
::Storage
::config
();
346 PVE
::Cluster
::check_cfs_quorum
();
348 if (defined($pool)) {
349 $rpcenv->check_pool_exist($pool);
352 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
353 if defined($storage);
355 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
357 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
359 } elsif ($archive && $force && (-f
$filename) &&
360 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
361 # OK: user has VM.Backup permissions, and want to restore an existing VM
367 &$resolve_cdrom_alias($param);
369 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
371 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
373 foreach my $opt (keys %$param) {
374 if (PVE
::QemuServer
::valid_drivename
($opt)) {
375 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
376 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
378 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
379 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
383 PVE
::QemuServer
::add_random_macs
($param);
385 my $keystr = join(' ', keys %$param);
386 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
388 if ($archive eq '-') {
389 die "pipe requires cli environment\n"
390 if $rpcenv->{type
} ne 'cli';
392 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
393 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
397 my $restorefn = sub {
399 # fixme: this test does not work if VM exists on other node!
401 die "unable to restore vm $vmid: config file already exists\n"
404 die "unable to restore vm $vmid: vm is running\n"
405 if PVE
::QemuServer
::check_running
($vmid);
409 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
412 unique
=> $unique });
414 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
417 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
423 die "unable to create vm $vmid: config file already exists\n"
434 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
436 # try to be smart about bootdisk
437 my @disks = PVE
::QemuServer
::disknames
();
439 foreach my $ds (reverse @disks) {
440 next if !$conf->{$ds};
441 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
442 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
446 if (!$conf->{bootdisk
} && $firstdisk) {
447 $conf->{bootdisk
} = $firstdisk;
450 # auto generate uuid if user did not specify smbios1 option
451 if (!$conf->{smbios1
}) {
452 my ($uuid, $uuid_str);
453 UUID
::generate
($uuid);
454 UUID
::unparse
($uuid, $uuid_str);
455 $conf->{smbios1
} = "uuid=$uuid_str";
458 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
464 foreach my $volid (@$vollist) {
465 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
468 die "create failed - $err";
471 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
474 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
477 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
480 __PACKAGE__-
>register_method({
485 description
=> "Directory index",
490 additionalProperties
=> 0,
492 node
=> get_standard_option
('pve-node'),
493 vmid
=> get_standard_option
('pve-vmid'),
501 subdir
=> { type
=> 'string' },
504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
510 { subdir
=> 'config' },
511 { subdir
=> 'status' },
512 { subdir
=> 'unlink' },
513 { subdir
=> 'vncproxy' },
514 { subdir
=> 'migrate' },
515 { subdir
=> 'resize' },
516 { subdir
=> 'move' },
518 { subdir
=> 'rrddata' },
519 { subdir
=> 'monitor' },
520 { subdir
=> 'snapshot' },
521 { subdir
=> 'spiceproxy' },
522 { subdir
=> 'sendkey' },
523 { subdir
=> 'firewall' },
529 __PACKAGE__-
>register_method ({
530 subclass
=> "PVE::API2::Firewall::VM",
531 path
=> '{vmid}/firewall',
534 __PACKAGE__-
>register_method({
536 path
=> '{vmid}/rrd',
538 protected
=> 1, # fixme: can we avoid that?
540 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
542 description
=> "Read VM RRD statistics (returns PNG)",
544 additionalProperties
=> 0,
546 node
=> get_standard_option
('pve-node'),
547 vmid
=> get_standard_option
('pve-vmid'),
549 description
=> "Specify the time frame you are interested in.",
551 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
554 description
=> "The list of datasources you want to display.",
555 type
=> 'string', format
=> 'pve-configid-list',
558 description
=> "The RRD consolidation function",
560 enum
=> [ 'AVERAGE', 'MAX' ],
568 filename
=> { type
=> 'string' },
574 return PVE
::Cluster
::create_rrd_graph
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
},
576 $param->{ds
}, $param->{cf
});
580 __PACKAGE__-
>register_method({
582 path
=> '{vmid}/rrddata',
584 protected
=> 1, # fixme: can we avoid that?
586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
588 description
=> "Read VM RRD statistics",
590 additionalProperties
=> 0,
592 node
=> get_standard_option
('pve-node'),
593 vmid
=> get_standard_option
('pve-vmid'),
595 description
=> "Specify the time frame you are interested in.",
597 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
600 description
=> "The RRD consolidation function",
602 enum
=> [ 'AVERAGE', 'MAX' ],
617 return PVE
::Cluster
::create_rrd_data
(
618 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
622 __PACKAGE__-
>register_method({
624 path
=> '{vmid}/config',
627 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
632 additionalProperties
=> 0,
634 node
=> get_standard_option
('pve-node'),
635 vmid
=> get_standard_option
('pve-vmid'),
643 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
650 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
652 delete $conf->{snapshots
};
653 delete $conf->{pending
};
658 __PACKAGE__-
>register_method({
659 name
=> 'vm_pending',
660 path
=> '{vmid}/pending',
663 description
=> "Get virtual machine configuration, including pending changes.",
665 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
668 additionalProperties
=> 0,
670 node
=> get_standard_option
('pve-node'),
671 vmid
=> get_standard_option
('pve-vmid'),
680 description
=> "Configuration option name.",
684 description
=> "Current value.",
689 description
=> "Pending value.",
694 description
=> "Indicated a pending delete request.",
704 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
706 my $pending_delete_hash = {};
707 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
708 $pending_delete_hash->{$opt} = 1;
713 foreach my $opt (keys $conf) {
714 next if ref($conf->{$opt});
715 my $item = { key
=> $opt };
716 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
717 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
718 $item->{delete} = 1 if $pending_delete_hash->{$opt};
722 foreach my $opt (keys $conf->{pending
}) {
723 next if $opt eq 'delete';
724 next if ref($conf->{pending
}->{$opt}); # just to be sure
725 next if $conf->{$opt};
726 my $item = { key
=> $opt };
727 $item->{pending
} = $conf->{pending
}->{$opt};
731 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
732 next if $conf->{pending
}->{$opt}; # just to be sure
733 next if $conf->{$opt};
734 my $item = { key
=> $opt, delete => 1};
741 my $delete_drive = sub {
742 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
744 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
745 my $volid = $drive->{file
};
747 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
748 if ($force || $key =~ m/^unused/) {
750 # check if the disk is really unused
751 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
752 my $path = PVE
::Storage
::path
($storecfg, $volid);
754 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
755 if $used_paths->{$path};
757 PVE
::Storage
::vdisk_free
($storecfg, $volid);
761 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
766 delete $conf->{$key};
769 my $vmconfig_delete_option = sub {
770 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
772 return if !defined($conf->{$opt});
774 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
777 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
779 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
780 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
781 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
785 my $unplugwarning = "";
786 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
787 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
788 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
789 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
790 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
791 $unplugwarning = "<br>verify that your guest support acpi hotplug";
794 if ($opt eq 'tablet') {
795 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
797 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
801 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
802 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
804 delete $conf->{$opt};
807 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 my $safe_num_ne = sub {
813 return 0 if !defined($a) && !defined($b);
814 return 1 if !defined($a);
815 return 1 if !defined($b);
820 my $vmconfig_update_disk = sub {
821 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
823 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
825 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
826 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
828 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
833 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
835 my $media = $drive->{media
} || 'disk';
836 my $oldmedia = $old_drive->{media
} || 'disk';
837 die "unable to change media type\n" if $media ne $oldmedia;
839 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
840 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
842 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
843 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
846 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
847 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
848 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
849 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
850 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
851 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
852 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
853 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
854 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
855 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
856 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
857 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
858 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
859 ($drive->{mbps
} || 0)*1024*1024,
860 ($drive->{mbps_rd
} || 0)*1024*1024,
861 ($drive->{mbps_wr
} || 0)*1024*1024,
863 $drive->{iops_rd
} || 0,
864 $drive->{iops_wr
} || 0,
865 ($drive->{mbps_max
} || 0)*1024*1024,
866 ($drive->{mbps_rd_max
} || 0)*1024*1024,
867 ($drive->{mbps_wr_max
} || 0)*1024*1024,
868 $drive->{iops_max
} || 0,
869 $drive->{iops_rd_max
} || 0,
870 $drive->{iops_wr_max
} || 0)
871 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
876 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
877 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
879 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
880 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
882 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
884 if (PVE
::QemuServer
::check_running
($vmid)) {
885 if ($drive->{file
} eq 'none') {
886 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
888 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
889 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
890 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
894 } else { # hotplug new disks
896 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
900 # POST/PUT {vmid}/config implementation
902 # The original API used PUT (idempotent) an we assumed that all operations
903 # are fast. But it turned out that almost any configuration change can
904 # involve hot-plug actions, or disk alloc/free. Such actions can take long
905 # time to complete and have side effects (not idempotent).
907 # The new implementation uses POST and forks a worker process. We added
908 # a new option 'background_delay'. If specified we wait up to
909 # 'background_delay' second for the worker task to complete. It returns null
910 # if the task is finished within that time, else we return the UPID.
912 my $update_vm_api = sub {
913 my ($param, $sync) = @_;
915 my $rpcenv = PVE
::RPCEnvironment
::get
();
917 my $authuser = $rpcenv->get_user();
919 my $node = extract_param
($param, 'node');
921 my $vmid = extract_param
($param, 'vmid');
923 my $digest = extract_param
($param, 'digest');
925 my $background_delay = extract_param
($param, 'background_delay');
927 my @paramarr = (); # used for log message
928 foreach my $key (keys %$param) {
929 push @paramarr, "-$key", $param->{$key};
932 my $skiplock = extract_param
($param, 'skiplock');
933 raise_param_exc
({ skiplock
=> "Only root may use this option." })
934 if $skiplock && $authuser ne 'root@pam';
936 my $delete_str = extract_param
($param, 'delete');
938 my $force = extract_param
($param, 'force');
940 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
942 my $storecfg = PVE
::Storage
::config
();
944 my $defaults = PVE
::QemuServer
::load_defaults
();
946 &$resolve_cdrom_alias($param);
948 # now try to verify all parameters
951 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
952 $opt = 'ide2' if $opt eq 'cdrom';
953 raise_param_exc
({ delete => "you can't use '-$opt' and " .
954 "-delete $opt' at the same time" })
955 if defined($param->{$opt});
957 if (!PVE
::QemuServer
::option_exists
($opt)) {
958 raise_param_exc
({ delete => "unknown option '$opt'" });
964 foreach my $opt (keys %$param) {
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
968 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
969 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
970 } elsif ($opt =~ m/^net(\d+)$/) {
972 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
973 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
977 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
979 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
981 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
985 my $conf = PVE
::QemuServer
::load_config
($vmid);
987 die "checksum missmatch (file change by other user?)\n"
988 if $digest && $digest ne $conf->{digest
};
990 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
992 if ($param->{memory
} || defined($param->{balloon
})) {
993 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
994 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
996 die "balloon value too large (must be smaller than assigned memory)\n"
997 if $balloon && $balloon > $maxmem;
1000 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1004 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1006 # write updates to pending section
1008 my $modified = {}; # record what $option we modify
1010 foreach my $opt (@delete) {
1011 $modified->{$opt} = 1;
1012 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1013 if ($opt =~ m/^unused/) {
1014 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1015 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1016 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
1017 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1018 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
1019 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1021 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
1022 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1023 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1024 if defined($conf->{pending
}->{$opt});
1025 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
1026 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1028 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
1029 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1033 foreach my $opt (keys %$param) { # add/change
1034 $modified->{$opt} = 1;
1035 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1036 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1038 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1039 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1040 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1041 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1043 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1045 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1046 if defined($conf->{pending
}->{$opt});
1048 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1050 $conf->{pending
}->{$opt} = $param->{$opt};
1052 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1053 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1056 # remove pending changes when nothing changed
1057 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1058 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1059 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
1061 return if !scalar(keys %{$conf->{pending
}});
1063 my $running = PVE
::QemuServer
::check_running
($vmid);
1065 # apply pending changes
1067 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1071 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1072 raise_param_exc
($errors) if scalar(keys %$errors);
1074 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1076 return; # TODO: remove old code below
1078 foreach my $opt (keys %$param) { # add/change
1080 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1082 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1084 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1086 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1087 $opt, $param->{$opt}, $force);
1089 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1091 # &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1092 # $opt, $param->{$opt});
1096 if($opt eq 'tablet' && $param->{$opt} == 1){
1097 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1098 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1099 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1102 if($opt eq 'cores' && $conf->{maxcpus
}){
1103 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1106 $conf->{$opt} = $param->{$opt};
1107 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1111 # allow manual ballooning if shares is set to zero
1112 if ($running && defined($param->{balloon
}) &&
1113 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1114 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1115 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1123 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1125 if ($background_delay) {
1127 # Note: It would be better to do that in the Event based HTTPServer
1128 # to avoid blocking call to sleep.
1130 my $end_time = time() + $background_delay;
1132 my $task = PVE
::Tools
::upid_decode
($upid);
1135 while (time() < $end_time) {
1136 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1138 sleep(1); # this gets interrupted when child process ends
1142 my $status = PVE
::Tools
::upid_read_status
($upid);
1143 return undef if $status eq 'OK';
1152 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1155 my $vm_config_perm_list = [
1160 'VM.Config.Network',
1162 'VM.Config.Options',
1165 __PACKAGE__-
>register_method({
1166 name
=> 'update_vm_async',
1167 path
=> '{vmid}/config',
1171 description
=> "Set virtual machine options (asynchrounous API).",
1173 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1176 additionalProperties
=> 0,
1177 properties
=> PVE
::QemuServer
::json_config_properties
(
1179 node
=> get_standard_option
('pve-node'),
1180 vmid
=> get_standard_option
('pve-vmid'),
1181 skiplock
=> get_standard_option
('skiplock'),
1183 type
=> 'string', format
=> 'pve-configid-list',
1184 description
=> "A list of settings you want to delete.",
1189 description
=> $opt_force_description,
1191 requires
=> 'delete',
1195 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1199 background_delay
=> {
1201 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1212 code
=> $update_vm_api,
1215 __PACKAGE__-
>register_method({
1216 name
=> 'update_vm',
1217 path
=> '{vmid}/config',
1221 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1223 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1226 additionalProperties
=> 0,
1227 properties
=> PVE
::QemuServer
::json_config_properties
(
1229 node
=> get_standard_option
('pve-node'),
1230 vmid
=> get_standard_option
('pve-vmid'),
1231 skiplock
=> get_standard_option
('skiplock'),
1233 type
=> 'string', format
=> 'pve-configid-list',
1234 description
=> "A list of settings you want to delete.",
1239 description
=> $opt_force_description,
1241 requires
=> 'delete',
1245 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1251 returns
=> { type
=> 'null' },
1254 &$update_vm_api($param, 1);
1260 __PACKAGE__-
>register_method({
1261 name
=> 'destroy_vm',
1266 description
=> "Destroy the vm (also delete all used/owned volumes).",
1268 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1271 additionalProperties
=> 0,
1273 node
=> get_standard_option
('pve-node'),
1274 vmid
=> get_standard_option
('pve-vmid'),
1275 skiplock
=> get_standard_option
('skiplock'),
1284 my $rpcenv = PVE
::RPCEnvironment
::get
();
1286 my $authuser = $rpcenv->get_user();
1288 my $vmid = $param->{vmid
};
1290 my $skiplock = $param->{skiplock
};
1291 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1292 if $skiplock && $authuser ne 'root@pam';
1295 my $conf = PVE
::QemuServer
::load_config
($vmid);
1297 my $storecfg = PVE
::Storage
::config
();
1299 my $delVMfromPoolFn = sub {
1300 my $usercfg = cfs_read_file
("user.cfg");
1301 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1302 if (my $data = $usercfg->{pools
}->{$pool}) {
1303 delete $data->{vms
}->{$vmid};
1304 delete $usercfg->{vms
}->{$vmid};
1305 cfs_write_file
("user.cfg", $usercfg);
1313 syslog
('info', "destroy VM $vmid: $upid\n");
1315 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1317 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1320 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1323 __PACKAGE__-
>register_method({
1325 path
=> '{vmid}/unlink',
1329 description
=> "Unlink/delete disk images.",
1331 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1334 additionalProperties
=> 0,
1336 node
=> get_standard_option
('pve-node'),
1337 vmid
=> get_standard_option
('pve-vmid'),
1339 type
=> 'string', format
=> 'pve-configid-list',
1340 description
=> "A list of disk IDs you want to delete.",
1344 description
=> $opt_force_description,
1349 returns
=> { type
=> 'null'},
1353 $param->{delete} = extract_param
($param, 'idlist');
1355 __PACKAGE__-
>update_vm($param);
1362 __PACKAGE__-
>register_method({
1364 path
=> '{vmid}/vncproxy',
1368 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1370 description
=> "Creates a TCP VNC proxy connections.",
1372 additionalProperties
=> 0,
1374 node
=> get_standard_option
('pve-node'),
1375 vmid
=> get_standard_option
('pve-vmid'),
1379 description
=> "starts websockify instead of vncproxy",
1384 additionalProperties
=> 0,
1386 user
=> { type
=> 'string' },
1387 ticket
=> { type
=> 'string' },
1388 cert
=> { type
=> 'string' },
1389 port
=> { type
=> 'integer' },
1390 upid
=> { type
=> 'string' },
1396 my $rpcenv = PVE
::RPCEnvironment
::get
();
1398 my $authuser = $rpcenv->get_user();
1400 my $vmid = $param->{vmid
};
1401 my $node = $param->{node
};
1402 my $websocket = $param->{websocket
};
1404 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1406 my $authpath = "/vms/$vmid";
1408 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1410 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1413 my $port = PVE
::Tools
::next_vnc_port
();
1418 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1419 $remip = PVE
::Cluster
::remote_node_ip
($node);
1420 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1421 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1429 syslog
('info', "starting vnc proxy $upid\n");
1433 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1435 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1437 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1438 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1439 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1440 '-timeout', $timeout, '-authpath', $authpath,
1441 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1444 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1446 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1448 my $qmstr = join(' ', @$qmcmd);
1450 # also redirect stderr (else we get RFB protocol errors)
1451 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1454 PVE
::Tools
::run_command
($cmd);
1459 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1461 PVE
::Tools
::wait_for_vnc_port
($port);
1472 __PACKAGE__-
>register_method({
1473 name
=> 'vncwebsocket',
1474 path
=> '{vmid}/vncwebsocket',
1477 description
=> "You also need to pass a valid ticket (vncticket).",
1478 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1480 description
=> "Opens a weksocket for VNC traffic.",
1482 additionalProperties
=> 0,
1484 node
=> get_standard_option
('pve-node'),
1485 vmid
=> get_standard_option
('pve-vmid'),
1487 description
=> "Ticket from previous call to vncproxy.",
1492 description
=> "Port number returned by previous vncproxy call.",
1502 port
=> { type
=> 'string' },
1508 my $rpcenv = PVE
::RPCEnvironment
::get
();
1510 my $authuser = $rpcenv->get_user();
1512 my $vmid = $param->{vmid
};
1513 my $node = $param->{node
};
1515 my $authpath = "/vms/$vmid";
1517 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1519 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1521 # Note: VNC ports are acessible from outside, so we do not gain any
1522 # security if we verify that $param->{port} belongs to VM $vmid. This
1523 # check is done by verifying the VNC ticket (inside VNC protocol).
1525 my $port = $param->{port
};
1527 return { port
=> $port };
1530 __PACKAGE__-
>register_method({
1531 name
=> 'spiceproxy',
1532 path
=> '{vmid}/spiceproxy',
1537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1539 description
=> "Returns a SPICE configuration to connect to the VM.",
1541 additionalProperties
=> 0,
1543 node
=> get_standard_option
('pve-node'),
1544 vmid
=> get_standard_option
('pve-vmid'),
1545 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1548 returns
=> get_standard_option
('remote-viewer-config'),
1552 my $rpcenv = PVE
::RPCEnvironment
::get
();
1554 my $authuser = $rpcenv->get_user();
1556 my $vmid = $param->{vmid
};
1557 my $node = $param->{node
};
1558 my $proxy = $param->{proxy
};
1560 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1561 my $title = "VM $vmid - $conf->{'name'}",
1563 my $port = PVE
::QemuServer
::spice_port
($vmid);
1565 my ($ticket, undef, $remote_viewer_config) =
1566 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1568 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1569 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1571 return $remote_viewer_config;
1574 __PACKAGE__-
>register_method({
1576 path
=> '{vmid}/status',
1579 description
=> "Directory index",
1584 additionalProperties
=> 0,
1586 node
=> get_standard_option
('pve-node'),
1587 vmid
=> get_standard_option
('pve-vmid'),
1595 subdir
=> { type
=> 'string' },
1598 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1604 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1607 { subdir
=> 'current' },
1608 { subdir
=> 'start' },
1609 { subdir
=> 'stop' },
1615 my $vm_is_ha_managed = sub {
1618 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1619 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1625 __PACKAGE__-
>register_method({
1626 name
=> 'vm_status',
1627 path
=> '{vmid}/status/current',
1630 protected
=> 1, # qemu pid files are only readable by root
1631 description
=> "Get virtual machine status.",
1633 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1636 additionalProperties
=> 0,
1638 node
=> get_standard_option
('pve-node'),
1639 vmid
=> get_standard_option
('pve-vmid'),
1642 returns
=> { type
=> 'object' },
1647 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1649 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1650 my $status = $vmstatus->{$param->{vmid
}};
1652 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1654 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1659 __PACKAGE__-
>register_method({
1661 path
=> '{vmid}/status/start',
1665 description
=> "Start virtual machine.",
1667 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1670 additionalProperties
=> 0,
1672 node
=> get_standard_option
('pve-node'),
1673 vmid
=> get_standard_option
('pve-vmid'),
1674 skiplock
=> get_standard_option
('skiplock'),
1675 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1676 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1677 machine
=> get_standard_option
('pve-qm-machine'),
1686 my $rpcenv = PVE
::RPCEnvironment
::get
();
1688 my $authuser = $rpcenv->get_user();
1690 my $node = extract_param
($param, 'node');
1692 my $vmid = extract_param
($param, 'vmid');
1694 my $machine = extract_param
($param, 'machine');
1696 my $stateuri = extract_param
($param, 'stateuri');
1697 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1698 if $stateuri && $authuser ne 'root@pam';
1700 my $skiplock = extract_param
($param, 'skiplock');
1701 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1702 if $skiplock && $authuser ne 'root@pam';
1704 my $migratedfrom = extract_param
($param, 'migratedfrom');
1705 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1706 if $migratedfrom && $authuser ne 'root@pam';
1708 # read spice ticket from STDIN
1710 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1711 if (defined(my $line = <>)) {
1713 $spice_ticket = $line;
1717 my $storecfg = PVE
::Storage
::config
();
1719 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1720 $rpcenv->{type
} ne 'ha') {
1725 my $service = "pvevm:$vmid";
1727 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1729 print "Executing HA start for VM $vmid\n";
1731 PVE
::Tools
::run_command
($cmd);
1736 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1743 syslog
('info', "start VM $vmid: $upid\n");
1745 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1746 $machine, $spice_ticket);
1751 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1755 __PACKAGE__-
>register_method({
1757 path
=> '{vmid}/status/stop',
1761 description
=> "Stop virtual machine.",
1763 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1766 additionalProperties
=> 0,
1768 node
=> get_standard_option
('pve-node'),
1769 vmid
=> get_standard_option
('pve-vmid'),
1770 skiplock
=> get_standard_option
('skiplock'),
1771 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1773 description
=> "Wait maximal timeout seconds.",
1779 description
=> "Do not decativate storage volumes.",
1792 my $rpcenv = PVE
::RPCEnvironment
::get
();
1794 my $authuser = $rpcenv->get_user();
1796 my $node = extract_param
($param, 'node');
1798 my $vmid = extract_param
($param, 'vmid');
1800 my $skiplock = extract_param
($param, 'skiplock');
1801 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1802 if $skiplock && $authuser ne 'root@pam';
1804 my $keepActive = extract_param
($param, 'keepActive');
1805 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1806 if $keepActive && $authuser ne 'root@pam';
1808 my $migratedfrom = extract_param
($param, 'migratedfrom');
1809 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1810 if $migratedfrom && $authuser ne 'root@pam';
1813 my $storecfg = PVE
::Storage
::config
();
1815 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1820 my $service = "pvevm:$vmid";
1822 my $cmd = ['clusvcadm', '-d', $service];
1824 print "Executing HA stop for VM $vmid\n";
1826 PVE
::Tools
::run_command
($cmd);
1831 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1837 syslog
('info', "stop VM $vmid: $upid\n");
1839 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1840 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1845 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1849 __PACKAGE__-
>register_method({
1851 path
=> '{vmid}/status/reset',
1855 description
=> "Reset virtual machine.",
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1860 additionalProperties
=> 0,
1862 node
=> get_standard_option
('pve-node'),
1863 vmid
=> get_standard_option
('pve-vmid'),
1864 skiplock
=> get_standard_option
('skiplock'),
1873 my $rpcenv = PVE
::RPCEnvironment
::get
();
1875 my $authuser = $rpcenv->get_user();
1877 my $node = extract_param
($param, 'node');
1879 my $vmid = extract_param
($param, 'vmid');
1881 my $skiplock = extract_param
($param, 'skiplock');
1882 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1883 if $skiplock && $authuser ne 'root@pam';
1885 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1890 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1895 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1898 __PACKAGE__-
>register_method({
1899 name
=> 'vm_shutdown',
1900 path
=> '{vmid}/status/shutdown',
1904 description
=> "Shutdown virtual machine.",
1906 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1909 additionalProperties
=> 0,
1911 node
=> get_standard_option
('pve-node'),
1912 vmid
=> get_standard_option
('pve-vmid'),
1913 skiplock
=> get_standard_option
('skiplock'),
1915 description
=> "Wait maximal timeout seconds.",
1921 description
=> "Make sure the VM stops.",
1927 description
=> "Do not decativate storage volumes.",
1940 my $rpcenv = PVE
::RPCEnvironment
::get
();
1942 my $authuser = $rpcenv->get_user();
1944 my $node = extract_param
($param, 'node');
1946 my $vmid = extract_param
($param, 'vmid');
1948 my $skiplock = extract_param
($param, 'skiplock');
1949 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1950 if $skiplock && $authuser ne 'root@pam';
1952 my $keepActive = extract_param
($param, 'keepActive');
1953 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1954 if $keepActive && $authuser ne 'root@pam';
1956 my $storecfg = PVE
::Storage
::config
();
1961 syslog
('info', "shutdown VM $vmid: $upid\n");
1963 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1964 1, $param->{forceStop
}, $keepActive);
1969 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1972 __PACKAGE__-
>register_method({
1973 name
=> 'vm_suspend',
1974 path
=> '{vmid}/status/suspend',
1978 description
=> "Suspend virtual machine.",
1980 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1983 additionalProperties
=> 0,
1985 node
=> get_standard_option
('pve-node'),
1986 vmid
=> get_standard_option
('pve-vmid'),
1987 skiplock
=> get_standard_option
('skiplock'),
1996 my $rpcenv = PVE
::RPCEnvironment
::get
();
1998 my $authuser = $rpcenv->get_user();
2000 my $node = extract_param
($param, 'node');
2002 my $vmid = extract_param
($param, 'vmid');
2004 my $skiplock = extract_param
($param, 'skiplock');
2005 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2006 if $skiplock && $authuser ne 'root@pam';
2008 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2013 syslog
('info', "suspend VM $vmid: $upid\n");
2015 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2020 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2023 __PACKAGE__-
>register_method({
2024 name
=> 'vm_resume',
2025 path
=> '{vmid}/status/resume',
2029 description
=> "Resume virtual machine.",
2031 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2038 skiplock
=> get_standard_option
('skiplock'),
2047 my $rpcenv = PVE
::RPCEnvironment
::get
();
2049 my $authuser = $rpcenv->get_user();
2051 my $node = extract_param
($param, 'node');
2053 my $vmid = extract_param
($param, 'vmid');
2055 my $skiplock = extract_param
($param, 'skiplock');
2056 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2057 if $skiplock && $authuser ne 'root@pam';
2059 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2064 syslog
('info', "resume VM $vmid: $upid\n");
2066 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
2071 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2074 __PACKAGE__-
>register_method({
2075 name
=> 'vm_sendkey',
2076 path
=> '{vmid}/sendkey',
2080 description
=> "Send key event to virtual machine.",
2082 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid'),
2089 skiplock
=> get_standard_option
('skiplock'),
2091 description
=> "The key (qemu monitor encoding).",
2096 returns
=> { type
=> 'null'},
2100 my $rpcenv = PVE
::RPCEnvironment
::get
();
2102 my $authuser = $rpcenv->get_user();
2104 my $node = extract_param
($param, 'node');
2106 my $vmid = extract_param
($param, 'vmid');
2108 my $skiplock = extract_param
($param, 'skiplock');
2109 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2110 if $skiplock && $authuser ne 'root@pam';
2112 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2117 __PACKAGE__-
>register_method({
2118 name
=> 'vm_feature',
2119 path
=> '{vmid}/feature',
2123 description
=> "Check if feature for virtual machine is available.",
2125 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2128 additionalProperties
=> 0,
2130 node
=> get_standard_option
('pve-node'),
2131 vmid
=> get_standard_option
('pve-vmid'),
2133 description
=> "Feature to check.",
2135 enum
=> [ 'snapshot', 'clone', 'copy' ],
2137 snapname
=> get_standard_option
('pve-snapshot-name', {
2145 hasFeature
=> { type
=> 'boolean' },
2148 items
=> { type
=> 'string' },
2155 my $node = extract_param
($param, 'node');
2157 my $vmid = extract_param
($param, 'vmid');
2159 my $snapname = extract_param
($param, 'snapname');
2161 my $feature = extract_param
($param, 'feature');
2163 my $running = PVE
::QemuServer
::check_running
($vmid);
2165 my $conf = PVE
::QemuServer
::load_config
($vmid);
2168 my $snap = $conf->{snapshots
}->{$snapname};
2169 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2172 my $storecfg = PVE
::Storage
::config
();
2174 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2175 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2178 hasFeature
=> $hasFeature,
2179 nodes
=> [ keys %$nodelist ],
2183 __PACKAGE__-
>register_method({
2185 path
=> '{vmid}/clone',
2189 description
=> "Create a copy of virtual machine/template.",
2191 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2192 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2193 "'Datastore.AllocateSpace' on any used storage.",
2196 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2198 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2199 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid'),
2208 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2211 type
=> 'string', format
=> 'dns-name',
2212 description
=> "Set a name for the new VM.",
2217 description
=> "Description for the new VM.",
2221 type
=> 'string', format
=> 'pve-poolid',
2222 description
=> "Add the new VM to the specified pool.",
2224 snapname
=> get_standard_option
('pve-snapshot-name', {
2227 storage
=> get_standard_option
('pve-storage-id', {
2228 description
=> "Target storage for full clone.",
2233 description
=> "Target format for file storage.",
2237 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2242 description
=> "Create a full copy of all disk. This is always done when " .
2243 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2246 target
=> get_standard_option
('pve-node', {
2247 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2258 my $rpcenv = PVE
::RPCEnvironment
::get
();
2260 my $authuser = $rpcenv->get_user();
2262 my $node = extract_param
($param, 'node');
2264 my $vmid = extract_param
($param, 'vmid');
2266 my $newid = extract_param
($param, 'newid');
2268 my $pool = extract_param
($param, 'pool');
2270 if (defined($pool)) {
2271 $rpcenv->check_pool_exist($pool);
2274 my $snapname = extract_param
($param, 'snapname');
2276 my $storage = extract_param
($param, 'storage');
2278 my $format = extract_param
($param, 'format');
2280 my $target = extract_param
($param, 'target');
2282 my $localnode = PVE
::INotify
::nodename
();
2284 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2286 PVE
::Cluster
::check_node_exists
($target) if $target;
2288 my $storecfg = PVE
::Storage
::config
();
2291 # check if storage is enabled on local node
2292 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2294 # check if storage is available on target node
2295 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2296 # clone only works if target storage is shared
2297 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2298 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2302 PVE
::Cluster
::check_cfs_quorum
();
2304 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2306 # exclusive lock if VM is running - else shared lock is enough;
2307 my $shared_lock = $running ?
0 : 1;
2311 # do all tests after lock
2312 # we also try to do all tests before we fork the worker
2314 my $conf = PVE
::QemuServer
::load_config
($vmid);
2316 PVE
::QemuServer
::check_lock
($conf);
2318 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2320 die "unexpected state change\n" if $verify_running != $running;
2322 die "snapshot '$snapname' does not exist\n"
2323 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2325 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2327 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2329 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2331 my $conffile = PVE
::QemuServer
::config_file
($newid);
2333 die "unable to create VM $newid: config file already exists\n"
2336 my $newconf = { lock => 'clone' };
2340 foreach my $opt (keys %$oldconf) {
2341 my $value = $oldconf->{$opt};
2343 # do not copy snapshot related info
2344 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2345 $opt eq 'vmstate' || $opt eq 'snapstate';
2347 # always change MAC! address
2348 if ($opt =~ m/^net(\d+)$/) {
2349 my $net = PVE
::QemuServer
::parse_net
($value);
2350 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2351 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2352 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2353 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2354 die "unable to parse drive options for '$opt'\n" if !$drive;
2355 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2356 $newconf->{$opt} = $value; # simply copy configuration
2358 if ($param->{full
}) {
2359 die "Full clone feature is not available"
2360 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2363 # not full means clone instead of copy
2364 die "Linked clone feature is not available"
2365 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2367 $drives->{$opt} = $drive;
2368 push @$vollist, $drive->{file
};
2371 # copy everything else
2372 $newconf->{$opt} = $value;
2376 # auto generate a new uuid
2377 my ($uuid, $uuid_str);
2378 UUID
::generate
($uuid);
2379 UUID
::unparse
($uuid, $uuid_str);
2380 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2381 $smbios1->{uuid
} = $uuid_str;
2382 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2384 delete $newconf->{template
};
2386 if ($param->{name
}) {
2387 $newconf->{name
} = $param->{name
};
2389 if ($oldconf->{name
}) {
2390 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2392 $newconf->{name
} = "Copy-of-VM-$vmid";
2396 if ($param->{description
}) {
2397 $newconf->{description
} = $param->{description
};
2400 # create empty/temp config - this fails if VM already exists on other node
2401 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2406 my $newvollist = [];
2409 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2411 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2413 foreach my $opt (keys %$drives) {
2414 my $drive = $drives->{$opt};
2416 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2417 $newid, $storage, $format, $drive->{full
}, $newvollist);
2419 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2421 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2424 delete $newconf->{lock};
2425 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2428 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2429 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2431 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2432 die "Failed to move config to node '$target' - rename failed: $!\n"
2433 if !rename($conffile, $newconffile);
2436 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2441 sleep 1; # some storage like rbd need to wait before release volume - really?
2443 foreach my $volid (@$newvollist) {
2444 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2447 die "clone failed: $err";
2453 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2456 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2457 # Aquire exclusive lock lock for $newid
2458 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2463 __PACKAGE__-
>register_method({
2464 name
=> 'move_vm_disk',
2465 path
=> '{vmid}/move_disk',
2469 description
=> "Move volume to different storage.",
2471 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2472 "and 'Datastore.AllocateSpace' permissions on the storage.",
2475 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2476 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2480 additionalProperties
=> 0,
2482 node
=> get_standard_option
('pve-node'),
2483 vmid
=> get_standard_option
('pve-vmid'),
2486 description
=> "The disk you want to move.",
2487 enum
=> [ PVE
::QemuServer
::disknames
() ],
2489 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2492 description
=> "Target Format.",
2493 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2498 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2504 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2512 description
=> "the task ID.",
2517 my $rpcenv = PVE
::RPCEnvironment
::get
();
2519 my $authuser = $rpcenv->get_user();
2521 my $node = extract_param
($param, 'node');
2523 my $vmid = extract_param
($param, 'vmid');
2525 my $digest = extract_param
($param, 'digest');
2527 my $disk = extract_param
($param, 'disk');
2529 my $storeid = extract_param
($param, 'storage');
2531 my $format = extract_param
($param, 'format');
2533 my $storecfg = PVE
::Storage
::config
();
2535 my $updatefn = sub {
2537 my $conf = PVE
::QemuServer
::load_config
($vmid);
2539 die "checksum missmatch (file change by other user?)\n"
2540 if $digest && $digest ne $conf->{digest
};
2542 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2544 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2546 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2548 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2551 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2552 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2556 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2557 (!$format || !$oldfmt || $oldfmt eq $format);
2559 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2561 my $running = PVE
::QemuServer
::check_running
($vmid);
2563 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2567 my $newvollist = [];
2570 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2572 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2573 $vmid, $storeid, $format, 1, $newvollist);
2575 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2577 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2579 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2582 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2583 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2590 foreach my $volid (@$newvollist) {
2591 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2594 die "storage migration failed: $err";
2597 if ($param->{delete}) {
2598 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2599 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2600 if ($used_paths->{$path}){
2601 warn "volume $old_volid have snapshots. Can't delete it\n";
2602 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2603 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2605 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2611 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2614 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2617 __PACKAGE__-
>register_method({
2618 name
=> 'migrate_vm',
2619 path
=> '{vmid}/migrate',
2623 description
=> "Migrate virtual machine. Creates a new migration task.",
2625 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2628 additionalProperties
=> 0,
2630 node
=> get_standard_option
('pve-node'),
2631 vmid
=> get_standard_option
('pve-vmid'),
2632 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2635 description
=> "Use online/live migration.",
2640 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2647 description
=> "the task ID.",
2652 my $rpcenv = PVE
::RPCEnvironment
::get
();
2654 my $authuser = $rpcenv->get_user();
2656 my $target = extract_param
($param, 'target');
2658 my $localnode = PVE
::INotify
::nodename
();
2659 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2661 PVE
::Cluster
::check_cfs_quorum
();
2663 PVE
::Cluster
::check_node_exists
($target);
2665 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2667 my $vmid = extract_param
($param, 'vmid');
2669 raise_param_exc
({ force
=> "Only root may use this option." })
2670 if $param->{force
} && $authuser ne 'root@pam';
2673 my $conf = PVE
::QemuServer
::load_config
($vmid);
2675 # try to detect errors early
2677 PVE
::QemuServer
::check_lock
($conf);
2679 if (PVE
::QemuServer
::check_running
($vmid)) {
2680 die "cant migrate running VM without --online\n"
2681 if !$param->{online
};
2684 my $storecfg = PVE
::Storage
::config
();
2685 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2687 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2692 my $service = "pvevm:$vmid";
2694 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2696 print "Executing HA migrate for VM $vmid to node $target\n";
2698 PVE
::Tools
::run_command
($cmd);
2703 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2710 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2713 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2718 __PACKAGE__-
>register_method({
2720 path
=> '{vmid}/monitor',
2724 description
=> "Execute Qemu monitor commands.",
2726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2729 additionalProperties
=> 0,
2731 node
=> get_standard_option
('pve-node'),
2732 vmid
=> get_standard_option
('pve-vmid'),
2735 description
=> "The monitor command.",
2739 returns
=> { type
=> 'string'},
2743 my $vmid = $param->{vmid
};
2745 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2749 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2751 $res = "ERROR: $@" if $@;
2756 __PACKAGE__-
>register_method({
2757 name
=> 'resize_vm',
2758 path
=> '{vmid}/resize',
2762 description
=> "Extend volume size.",
2764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2767 additionalProperties
=> 0,
2769 node
=> get_standard_option
('pve-node'),
2770 vmid
=> get_standard_option
('pve-vmid'),
2771 skiplock
=> get_standard_option
('skiplock'),
2774 description
=> "The disk you want to resize.",
2775 enum
=> [PVE
::QemuServer
::disknames
()],
2779 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2780 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.",
2784 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2790 returns
=> { type
=> 'null'},
2794 my $rpcenv = PVE
::RPCEnvironment
::get
();
2796 my $authuser = $rpcenv->get_user();
2798 my $node = extract_param
($param, 'node');
2800 my $vmid = extract_param
($param, 'vmid');
2802 my $digest = extract_param
($param, 'digest');
2804 my $disk = extract_param
($param, 'disk');
2806 my $sizestr = extract_param
($param, 'size');
2808 my $skiplock = extract_param
($param, 'skiplock');
2809 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2810 if $skiplock && $authuser ne 'root@pam';
2812 my $storecfg = PVE
::Storage
::config
();
2814 my $updatefn = sub {
2816 my $conf = PVE
::QemuServer
::load_config
($vmid);
2818 die "checksum missmatch (file change by other user?)\n"
2819 if $digest && $digest ne $conf->{digest
};
2820 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2822 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2824 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2826 my $volid = $drive->{file
};
2828 die "disk '$disk' has no associated volume\n" if !$volid;
2830 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2832 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2834 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2836 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2838 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2839 my ($ext, $newsize, $unit) = ($1, $2, $4);
2842 $newsize = $newsize * 1024;
2843 } elsif ($unit eq 'M') {
2844 $newsize = $newsize * 1024 * 1024;
2845 } elsif ($unit eq 'G') {
2846 $newsize = $newsize * 1024 * 1024 * 1024;
2847 } elsif ($unit eq 'T') {
2848 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2851 $newsize += $size if $ext;
2852 $newsize = int($newsize);
2854 die "unable to skrink disk size\n" if $newsize < $size;
2856 return if $size == $newsize;
2858 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2860 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2862 $drive->{size
} = $newsize;
2863 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2865 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2868 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2872 __PACKAGE__-
>register_method({
2873 name
=> 'snapshot_list',
2874 path
=> '{vmid}/snapshot',
2876 description
=> "List all snapshots.",
2878 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2881 protected
=> 1, # qemu pid files are only readable by root
2883 additionalProperties
=> 0,
2885 vmid
=> get_standard_option
('pve-vmid'),
2886 node
=> get_standard_option
('pve-node'),
2895 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2900 my $vmid = $param->{vmid
};
2902 my $conf = PVE
::QemuServer
::load_config
($vmid);
2903 my $snaphash = $conf->{snapshots
} || {};
2907 foreach my $name (keys %$snaphash) {
2908 my $d = $snaphash->{$name};
2911 snaptime
=> $d->{snaptime
} || 0,
2912 vmstate
=> $d->{vmstate
} ?
1 : 0,
2913 description
=> $d->{description
} || '',
2915 $item->{parent
} = $d->{parent
} if $d->{parent
};
2916 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2920 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2921 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2922 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2924 push @$res, $current;
2929 __PACKAGE__-
>register_method({
2931 path
=> '{vmid}/snapshot',
2935 description
=> "Snapshot a VM.",
2937 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2940 additionalProperties
=> 0,
2942 node
=> get_standard_option
('pve-node'),
2943 vmid
=> get_standard_option
('pve-vmid'),
2944 snapname
=> get_standard_option
('pve-snapshot-name'),
2948 description
=> "Save the vmstate",
2953 description
=> "A textual description or comment.",
2959 description
=> "the task ID.",
2964 my $rpcenv = PVE
::RPCEnvironment
::get
();
2966 my $authuser = $rpcenv->get_user();
2968 my $node = extract_param
($param, 'node');
2970 my $vmid = extract_param
($param, 'vmid');
2972 my $snapname = extract_param
($param, 'snapname');
2974 die "unable to use snapshot name 'current' (reserved name)\n"
2975 if $snapname eq 'current';
2978 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2979 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2980 $param->{description
});
2983 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2986 __PACKAGE__-
>register_method({
2987 name
=> 'snapshot_cmd_idx',
2988 path
=> '{vmid}/snapshot/{snapname}',
2995 additionalProperties
=> 0,
2997 vmid
=> get_standard_option
('pve-vmid'),
2998 node
=> get_standard_option
('pve-node'),
2999 snapname
=> get_standard_option
('pve-snapshot-name'),
3008 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3015 push @$res, { cmd
=> 'rollback' };
3016 push @$res, { cmd
=> 'config' };
3021 __PACKAGE__-
>register_method({
3022 name
=> 'update_snapshot_config',
3023 path
=> '{vmid}/snapshot/{snapname}/config',
3027 description
=> "Update snapshot metadata.",
3029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3032 additionalProperties
=> 0,
3034 node
=> get_standard_option
('pve-node'),
3035 vmid
=> get_standard_option
('pve-vmid'),
3036 snapname
=> get_standard_option
('pve-snapshot-name'),
3040 description
=> "A textual description or comment.",
3044 returns
=> { type
=> 'null' },
3048 my $rpcenv = PVE
::RPCEnvironment
::get
();
3050 my $authuser = $rpcenv->get_user();
3052 my $vmid = extract_param
($param, 'vmid');
3054 my $snapname = extract_param
($param, 'snapname');
3056 return undef if !defined($param->{description
});
3058 my $updatefn = sub {
3060 my $conf = PVE
::QemuServer
::load_config
($vmid);
3062 PVE
::QemuServer
::check_lock
($conf);
3064 my $snap = $conf->{snapshots
}->{$snapname};
3066 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3068 $snap->{description
} = $param->{description
} if defined($param->{description
});
3070 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3073 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
3078 __PACKAGE__-
>register_method({
3079 name
=> 'get_snapshot_config',
3080 path
=> '{vmid}/snapshot/{snapname}/config',
3083 description
=> "Get snapshot configuration",
3085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3088 additionalProperties
=> 0,
3090 node
=> get_standard_option
('pve-node'),
3091 vmid
=> get_standard_option
('pve-vmid'),
3092 snapname
=> get_standard_option
('pve-snapshot-name'),
3095 returns
=> { type
=> "object" },
3099 my $rpcenv = PVE
::RPCEnvironment
::get
();
3101 my $authuser = $rpcenv->get_user();
3103 my $vmid = extract_param
($param, 'vmid');
3105 my $snapname = extract_param
($param, 'snapname');
3107 my $conf = PVE
::QemuServer
::load_config
($vmid);
3109 my $snap = $conf->{snapshots
}->{$snapname};
3111 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3116 __PACKAGE__-
>register_method({
3118 path
=> '{vmid}/snapshot/{snapname}/rollback',
3122 description
=> "Rollback VM state to specified snapshot.",
3124 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3127 additionalProperties
=> 0,
3129 node
=> get_standard_option
('pve-node'),
3130 vmid
=> get_standard_option
('pve-vmid'),
3131 snapname
=> get_standard_option
('pve-snapshot-name'),
3136 description
=> "the task ID.",
3141 my $rpcenv = PVE
::RPCEnvironment
::get
();
3143 my $authuser = $rpcenv->get_user();
3145 my $node = extract_param
($param, 'node');
3147 my $vmid = extract_param
($param, 'vmid');
3149 my $snapname = extract_param
($param, 'snapname');
3152 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3153 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3156 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3159 __PACKAGE__-
>register_method({
3160 name
=> 'delsnapshot',
3161 path
=> '{vmid}/snapshot/{snapname}',
3165 description
=> "Delete a VM snapshot.",
3167 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3170 additionalProperties
=> 0,
3172 node
=> get_standard_option
('pve-node'),
3173 vmid
=> get_standard_option
('pve-vmid'),
3174 snapname
=> get_standard_option
('pve-snapshot-name'),
3178 description
=> "For removal from config file, even if removing disk snapshots fails.",
3184 description
=> "the task ID.",
3189 my $rpcenv = PVE
::RPCEnvironment
::get
();
3191 my $authuser = $rpcenv->get_user();
3193 my $node = extract_param
($param, 'node');
3195 my $vmid = extract_param
($param, 'vmid');
3197 my $snapname = extract_param
($param, 'snapname');
3200 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3201 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3204 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3207 __PACKAGE__-
>register_method({
3209 path
=> '{vmid}/template',
3213 description
=> "Create a Template.",
3215 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3216 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3219 additionalProperties
=> 0,
3221 node
=> get_standard_option
('pve-node'),
3222 vmid
=> get_standard_option
('pve-vmid'),
3226 description
=> "If you want to convert only 1 disk to base image.",
3227 enum
=> [PVE
::QemuServer
::disknames
()],
3232 returns
=> { type
=> 'null'},
3236 my $rpcenv = PVE
::RPCEnvironment
::get
();
3238 my $authuser = $rpcenv->get_user();
3240 my $node = extract_param
($param, 'node');
3242 my $vmid = extract_param
($param, 'vmid');
3244 my $disk = extract_param
($param, 'disk');
3246 my $updatefn = sub {
3248 my $conf = PVE
::QemuServer
::load_config
($vmid);
3250 PVE
::QemuServer
::check_lock
($conf);
3252 die "unable to create template, because VM contains snapshots\n"
3253 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3255 die "you can't convert a template to a template\n"
3256 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3258 die "you can't convert a VM to template if VM is running\n"
3259 if PVE
::QemuServer
::check_running
($vmid);
3262 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3265 $conf->{template
} = 1;
3266 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3268 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3271 PVE
::QemuServer
::lock_config
($vmid, $updatefn);