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
;
23 use PVE
::API2
::Firewall
::VM
;
24 use PVE
::HA
::Env
::PVE2
;
27 use Data
::Dumper
; # fixme: remove
29 use base
qw(PVE::RESTHandler);
31 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.";
33 my $resolve_cdrom_alias = sub {
36 if (my $value = $param->{cdrom
}) {
37 $value .= ",media=cdrom" if $value !~ m/media=/;
38 $param->{ide2
} = $value;
39 delete $param->{cdrom
};
43 my $check_storage_access = sub {
44 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
46 PVE
::QemuServer
::foreach_drive
($settings, sub {
47 my ($ds, $drive) = @_;
49 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
51 my $volid = $drive->{file
};
53 if (!$volid || $volid eq 'none') {
55 } elsif ($isCDROM && ($volid eq 'cdrom')) {
56 $rpcenv->check($authuser, "/", ['Sys.Console']);
57 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
58 my ($storeid, $size) = ($2 || $default_storage, $3);
59 die "no storage ID specified (and no default storage)\n" if !$storeid;
60 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
62 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
67 my $check_storage_access_clone = sub {
68 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
72 PVE
::QemuServer
::foreach_drive
($conf, sub {
73 my ($ds, $drive) = @_;
75 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
77 my $volid = $drive->{file
};
79 return if !$volid || $volid eq 'none';
82 if ($volid eq 'cdrom') {
83 $rpcenv->check($authuser, "/", ['Sys.Console']);
85 # we simply allow access
86 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
87 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
88 $sharedvm = 0 if !$scfg->{shared
};
92 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
93 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
94 $sharedvm = 0 if !$scfg->{shared
};
96 $sid = $storage if $storage;
97 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
104 # Note: $pool is only needed when creating a VM, because pool permissions
105 # are automatically inherited if VM already exists inside a pool.
106 my $create_disks = sub {
107 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
112 PVE
::QemuServer
::foreach_drive
($settings, sub {
113 my ($ds, $disk) = @_;
115 my $volid = $disk->{file
};
117 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
118 delete $disk->{size
};
119 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
120 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
121 my ($storeid, $size) = ($2 || $default_storage, $3);
122 die "no storage ID specified (and no default storage)\n" if !$storeid;
123 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
124 my $fmt = $disk->{format
} || $defformat;
125 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
126 $fmt, undef, $size*1024*1024);
127 $disk->{file
} = $volid;
128 $disk->{size
} = $size*1024*1024*1024;
129 push @$vollist, $volid;
130 delete $disk->{format
}; # no longer needed
131 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
134 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
136 my $volid_is_new = 1;
139 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
140 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
145 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
147 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
149 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
151 die "volume $volid does not exists\n" if !$size;
153 $disk->{size
} = $size;
156 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
160 # free allocated images on error
162 syslog
('err', "VM $vmid creating disks failed");
163 foreach my $volid (@$vollist) {
164 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
170 # modify vm config if everything went well
171 foreach my $ds (keys %$res) {
172 $conf->{$ds} = $res->{$ds};
178 my $check_vm_modify_config_perm = sub {
179 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
181 return 1 if $authuser eq 'root@pam';
183 foreach my $opt (@$key_list) {
184 # disk checks need to be done somewhere else
185 next if PVE
::QemuServer
::valid_drivename
($opt);
187 if ($opt eq 'sockets' || $opt eq 'cores' ||
188 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
189 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
191 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
192 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
193 } elsif ($opt eq 'args' || $opt eq 'lock') {
194 die "only root can set '$opt' config\n";
195 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
196 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
198 } elsif ($opt =~ m/^net\d+$/) {
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
201 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
208 my $check_protection = sub {
209 my ($vm_conf, $err_msg) = @_;
211 if ($vm_conf->{protection
}) {
212 die "$err_msg - protection mode enabled\n";
216 __PACKAGE__-
>register_method({
220 description
=> "Virtual machine index (per node).",
222 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
226 protected
=> 1, # qemu pid files are only readable by root
228 additionalProperties
=> 0,
230 node
=> get_standard_option
('pve-node'),
234 description
=> "Determine the full status of active VMs.",
244 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
249 my $rpcenv = PVE
::RPCEnvironment
::get
();
250 my $authuser = $rpcenv->get_user();
252 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
255 foreach my $vmid (keys %$vmstatus) {
256 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
258 my $data = $vmstatus->{$vmid};
259 $data->{vmid
} = int($vmid);
268 __PACKAGE__-
>register_method({
272 description
=> "Create or restore a virtual machine.",
274 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
275 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
276 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
277 user
=> 'all', # check inside
282 additionalProperties
=> 0,
283 properties
=> PVE
::QemuServer
::json_config_properties
(
285 node
=> get_standard_option
('pve-node'),
286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
288 description
=> "The backup file.",
292 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
294 storage
=> get_standard_option
('pve-storage-id', {
295 description
=> "Default storage.",
297 completion
=> \
&PVE
::QemuServer
::complete_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 {
398 my $vmlist = PVE
::Cluster
::get_vmlist
();
399 if ($vmlist->{ids
}->{$vmid}) {
400 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
401 if ($current_node eq $node) {
402 my $conf = PVE
::QemuServer
::load_config
($vmid);
404 &$check_protection($conf, "unable to restore VM $vmid");
406 die "unable to restore vm $vmid - config file already exists\n"
409 die "unable to restore vm $vmid - vm is running\n"
410 if PVE
::QemuServer
::check_running
($vmid);
412 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
417 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
420 unique
=> $unique });
422 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
425 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
431 PVE
::Cluster
::check_vmid_unused
($vmid);
441 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
443 # try to be smart about bootdisk
444 my @disks = PVE
::QemuServer
::disknames
();
446 foreach my $ds (reverse @disks) {
447 next if !$conf->{$ds};
448 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
449 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
453 if (!$conf->{bootdisk
} && $firstdisk) {
454 $conf->{bootdisk
} = $firstdisk;
457 # auto generate uuid if user did not specify smbios1 option
458 if (!$conf->{smbios1
}) {
459 my ($uuid, $uuid_str);
460 UUID
::generate
($uuid);
461 UUID
::unparse
($uuid, $uuid_str);
462 $conf->{smbios1
} = "uuid=$uuid_str";
465 PVE
::QemuServer
::write_config
($vmid, $conf);
471 foreach my $volid (@$vollist) {
472 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
475 die "create failed - $err";
478 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
481 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
484 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
487 __PACKAGE__-
>register_method({
492 description
=> "Directory index",
497 additionalProperties
=> 0,
499 node
=> get_standard_option
('pve-node'),
500 vmid
=> get_standard_option
('pve-vmid'),
508 subdir
=> { type
=> 'string' },
511 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
517 { subdir
=> 'config' },
518 { subdir
=> 'pending' },
519 { subdir
=> 'status' },
520 { subdir
=> 'unlink' },
521 { subdir
=> 'vncproxy' },
522 { subdir
=> 'migrate' },
523 { subdir
=> 'resize' },
524 { subdir
=> 'move' },
526 { subdir
=> 'rrddata' },
527 { subdir
=> 'monitor' },
528 { subdir
=> 'snapshot' },
529 { subdir
=> 'spiceproxy' },
530 { subdir
=> 'sendkey' },
531 { subdir
=> 'firewall' },
537 __PACKAGE__-
>register_method ({
538 subclass
=> "PVE::API2::Firewall::VM",
539 path
=> '{vmid}/firewall',
542 __PACKAGE__-
>register_method({
544 path
=> '{vmid}/rrd',
546 protected
=> 1, # fixme: can we avoid that?
548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
550 description
=> "Read VM RRD statistics (returns PNG)",
552 additionalProperties
=> 0,
554 node
=> get_standard_option
('pve-node'),
555 vmid
=> get_standard_option
('pve-vmid'),
557 description
=> "Specify the time frame you are interested in.",
559 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
562 description
=> "The list of datasources you want to display.",
563 type
=> 'string', format
=> 'pve-configid-list',
566 description
=> "The RRD consolidation function",
568 enum
=> [ 'AVERAGE', 'MAX' ],
576 filename
=> { type
=> 'string' },
582 return PVE
::Cluster
::create_rrd_graph
(
583 "pve2-vm/$param->{vmid}", $param->{timeframe
},
584 $param->{ds
}, $param->{cf
});
588 __PACKAGE__-
>register_method({
590 path
=> '{vmid}/rrddata',
592 protected
=> 1, # fixme: can we avoid that?
594 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
596 description
=> "Read VM RRD statistics",
598 additionalProperties
=> 0,
600 node
=> get_standard_option
('pve-node'),
601 vmid
=> get_standard_option
('pve-vmid'),
603 description
=> "Specify the time frame you are interested in.",
605 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
608 description
=> "The RRD consolidation function",
610 enum
=> [ 'AVERAGE', 'MAX' ],
625 return PVE
::Cluster
::create_rrd_data
(
626 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
630 __PACKAGE__-
>register_method({
632 path
=> '{vmid}/config',
635 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
637 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
640 additionalProperties
=> 0,
642 node
=> get_standard_option
('pve-node'),
643 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
645 description
=> "Get current values (instead of pending values).",
657 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
664 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
666 delete $conf->{snapshots
};
668 if (!$param->{current
}) {
669 foreach my $opt (keys %{$conf->{pending
}}) {
670 next if $opt eq 'delete';
671 my $value = $conf->{pending
}->{$opt};
672 next if ref($value); # just to be sure
673 $conf->{$opt} = $value;
675 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
676 foreach my $opt (keys %$pending_delete_hash) {
677 delete $conf->{$opt} if $conf->{$opt};
681 delete $conf->{pending
};
686 __PACKAGE__-
>register_method({
687 name
=> 'vm_pending',
688 path
=> '{vmid}/pending',
691 description
=> "Get virtual machine configuration, including pending changes.",
693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
696 additionalProperties
=> 0,
698 node
=> get_standard_option
('pve-node'),
699 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
708 description
=> "Configuration option name.",
712 description
=> "Current value.",
717 description
=> "Pending value.",
722 description
=> "Indicates a pending delete request if present and not 0. " .
723 "The value 2 indicates a force-delete request.",
735 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
737 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
741 foreach my $opt (keys %$conf) {
742 next if ref($conf->{$opt});
743 my $item = { key
=> $opt };
744 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
745 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
746 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
750 foreach my $opt (keys %{$conf->{pending
}}) {
751 next if $opt eq 'delete';
752 next if ref($conf->{pending
}->{$opt}); # just to be sure
753 next if defined($conf->{$opt});
754 my $item = { key
=> $opt };
755 $item->{pending
} = $conf->{pending
}->{$opt};
759 while (my ($opt, $force) = each %$pending_delete_hash) {
760 next if $conf->{pending
}->{$opt}; # just to be sure
761 next if $conf->{$opt};
762 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
769 # POST/PUT {vmid}/config implementation
771 # The original API used PUT (idempotent) an we assumed that all operations
772 # are fast. But it turned out that almost any configuration change can
773 # involve hot-plug actions, or disk alloc/free. Such actions can take long
774 # time to complete and have side effects (not idempotent).
776 # The new implementation uses POST and forks a worker process. We added
777 # a new option 'background_delay'. If specified we wait up to
778 # 'background_delay' second for the worker task to complete. It returns null
779 # if the task is finished within that time, else we return the UPID.
781 my $update_vm_api = sub {
782 my ($param, $sync) = @_;
784 my $rpcenv = PVE
::RPCEnvironment
::get
();
786 my $authuser = $rpcenv->get_user();
788 my $node = extract_param
($param, 'node');
790 my $vmid = extract_param
($param, 'vmid');
792 my $digest = extract_param
($param, 'digest');
794 my $background_delay = extract_param
($param, 'background_delay');
796 my @paramarr = (); # used for log message
797 foreach my $key (keys %$param) {
798 push @paramarr, "-$key", $param->{$key};
801 my $skiplock = extract_param
($param, 'skiplock');
802 raise_param_exc
({ skiplock
=> "Only root may use this option." })
803 if $skiplock && $authuser ne 'root@pam';
805 my $delete_str = extract_param
($param, 'delete');
807 my $revert_str = extract_param
($param, 'revert');
809 my $force = extract_param
($param, 'force');
811 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
813 my $storecfg = PVE
::Storage
::config
();
815 my $defaults = PVE
::QemuServer
::load_defaults
();
817 &$resolve_cdrom_alias($param);
819 # now try to verify all parameters
822 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
823 if (!PVE
::QemuServer
::option_exists
($opt)) {
824 raise_param_exc
({ revert
=> "unknown option '$opt'" });
827 raise_param_exc
({ delete => "you can't use '-$opt' and " .
828 "-revert $opt' at the same time" })
829 if defined($param->{$opt});
835 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
836 $opt = 'ide2' if $opt eq 'cdrom';
838 raise_param_exc
({ delete => "you can't use '-$opt' and " .
839 "-delete $opt' at the same time" })
840 if defined($param->{$opt});
842 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
843 "-revert $opt' at the same time" })
846 if (!PVE
::QemuServer
::option_exists
($opt)) {
847 raise_param_exc
({ delete => "unknown option '$opt'" });
853 foreach my $opt (keys %$param) {
854 if (PVE
::QemuServer
::valid_drivename
($opt)) {
856 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
857 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
858 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
859 } elsif ($opt =~ m/^net(\d+)$/) {
861 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
862 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
866 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
868 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
870 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
874 my $conf = PVE
::QemuServer
::load_config
($vmid);
876 die "checksum missmatch (file change by other user?)\n"
877 if $digest && $digest ne $conf->{digest
};
879 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
881 foreach my $opt (keys %$revert) {
882 if (defined($conf->{$opt})) {
883 $param->{$opt} = $conf->{$opt};
884 } elsif (defined($conf->{pending
}->{$opt})) {
889 if ($param->{memory
} || defined($param->{balloon
})) {
890 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
891 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
893 die "balloon value too large (must be smaller than assigned memory)\n"
894 if $balloon && $balloon > $maxmem;
897 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
901 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
903 # write updates to pending section
905 my $modified = {}; # record what $option we modify
907 foreach my $opt (@delete) {
908 $modified->{$opt} = 1;
909 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
910 if ($opt =~ m/^unused/) {
911 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
912 &$check_protection($conf, "can't remove unused disk '$drive->{file}'");
913 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
914 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
915 delete $conf->{$opt};
916 PVE
::QemuServer
::write_config
($vmid, $conf);
918 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
919 &$check_protection($conf, "can't remove drive '$opt'");
920 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
921 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
922 if defined($conf->{pending
}->{$opt});
923 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
924 PVE
::QemuServer
::write_config
($vmid, $conf);
926 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
927 PVE
::QemuServer
::write_config
($vmid, $conf);
931 foreach my $opt (keys %$param) { # add/change
932 $modified->{$opt} = 1;
933 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
934 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
936 if (PVE
::QemuServer
::valid_drivename
($opt)) {
937 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
938 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
939 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
941 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
943 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
944 if defined($conf->{pending
}->{$opt});
946 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
948 $conf->{pending
}->{$opt} = $param->{$opt};
950 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
951 PVE
::QemuServer
::write_config
($vmid, $conf);
954 # remove pending changes when nothing changed
955 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
956 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
957 PVE
::QemuServer
::write_config
($vmid, $conf) if $changes;
959 return if !scalar(keys %{$conf->{pending
}});
961 my $running = PVE
::QemuServer
::check_running
($vmid);
963 # apply pending changes
965 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
969 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
970 raise_param_exc
($errors) if scalar(keys %$errors);
972 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
982 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
984 if ($background_delay) {
986 # Note: It would be better to do that in the Event based HTTPServer
987 # to avoid blocking call to sleep.
989 my $end_time = time() + $background_delay;
991 my $task = PVE
::Tools
::upid_decode
($upid);
994 while (time() < $end_time) {
995 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
997 sleep(1); # this gets interrupted when child process ends
1001 my $status = PVE
::Tools
::upid_read_status
($upid);
1002 return undef if $status eq 'OK';
1011 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1014 my $vm_config_perm_list = [
1019 'VM.Config.Network',
1021 'VM.Config.Options',
1024 __PACKAGE__-
>register_method({
1025 name
=> 'update_vm_async',
1026 path
=> '{vmid}/config',
1030 description
=> "Set virtual machine options (asynchrounous API).",
1032 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1035 additionalProperties
=> 0,
1036 properties
=> PVE
::QemuServer
::json_config_properties
(
1038 node
=> get_standard_option
('pve-node'),
1039 vmid
=> get_standard_option
('pve-vmid'),
1040 skiplock
=> get_standard_option
('skiplock'),
1042 type
=> 'string', format
=> 'pve-configid-list',
1043 description
=> "A list of settings you want to delete.",
1047 type
=> 'string', format
=> 'pve-configid-list',
1048 description
=> "Revert a pending change.",
1053 description
=> $opt_force_description,
1055 requires
=> 'delete',
1059 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1063 background_delay
=> {
1065 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1076 code
=> $update_vm_api,
1079 __PACKAGE__-
>register_method({
1080 name
=> 'update_vm',
1081 path
=> '{vmid}/config',
1085 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1087 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1090 additionalProperties
=> 0,
1091 properties
=> PVE
::QemuServer
::json_config_properties
(
1093 node
=> get_standard_option
('pve-node'),
1094 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1095 skiplock
=> get_standard_option
('skiplock'),
1097 type
=> 'string', format
=> 'pve-configid-list',
1098 description
=> "A list of settings you want to delete.",
1102 type
=> 'string', format
=> 'pve-configid-list',
1103 description
=> "Revert a pending change.",
1108 description
=> $opt_force_description,
1110 requires
=> 'delete',
1114 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1120 returns
=> { type
=> 'null' },
1123 &$update_vm_api($param, 1);
1129 __PACKAGE__-
>register_method({
1130 name
=> 'destroy_vm',
1135 description
=> "Destroy the vm (also delete all used/owned volumes).",
1137 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1140 additionalProperties
=> 0,
1142 node
=> get_standard_option
('pve-node'),
1143 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1144 skiplock
=> get_standard_option
('skiplock'),
1153 my $rpcenv = PVE
::RPCEnvironment
::get
();
1155 my $authuser = $rpcenv->get_user();
1157 my $vmid = $param->{vmid
};
1159 my $skiplock = $param->{skiplock
};
1160 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1161 if $skiplock && $authuser ne 'root@pam';
1164 my $conf = PVE
::QemuServer
::load_config
($vmid);
1166 my $storecfg = PVE
::Storage
::config
();
1168 &$check_protection($conf, "can't remove VM $vmid");
1170 die "unable to remove VM $vmid - used in HA resources\n"
1171 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1173 # early tests (repeat after locking)
1174 die "VM $vmid is running - destroy failed\n"
1175 if PVE
::QemuServer
::check_running
($vmid);
1180 syslog
('info', "destroy VM $vmid: $upid\n");
1182 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1184 PVE
::AccessControl
::remove_vm_access
($vmid);
1186 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1189 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1192 __PACKAGE__-
>register_method({
1194 path
=> '{vmid}/unlink',
1198 description
=> "Unlink/delete disk images.",
1200 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1203 additionalProperties
=> 0,
1205 node
=> get_standard_option
('pve-node'),
1206 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1208 type
=> 'string', format
=> 'pve-configid-list',
1209 description
=> "A list of disk IDs you want to delete.",
1213 description
=> $opt_force_description,
1218 returns
=> { type
=> 'null'},
1222 $param->{delete} = extract_param
($param, 'idlist');
1224 __PACKAGE__-
>update_vm($param);
1231 __PACKAGE__-
>register_method({
1233 path
=> '{vmid}/vncproxy',
1237 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1239 description
=> "Creates a TCP VNC proxy connections.",
1241 additionalProperties
=> 0,
1243 node
=> get_standard_option
('pve-node'),
1244 vmid
=> get_standard_option
('pve-vmid'),
1248 description
=> "starts websockify instead of vncproxy",
1253 additionalProperties
=> 0,
1255 user
=> { type
=> 'string' },
1256 ticket
=> { type
=> 'string' },
1257 cert
=> { type
=> 'string' },
1258 port
=> { type
=> 'integer' },
1259 upid
=> { type
=> 'string' },
1265 my $rpcenv = PVE
::RPCEnvironment
::get
();
1267 my $authuser = $rpcenv->get_user();
1269 my $vmid = $param->{vmid
};
1270 my $node = $param->{node
};
1271 my $websocket = $param->{websocket
};
1273 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
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 ($remip, $family);
1285 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1286 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1287 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1288 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1290 $family = PVE
::Tools
::get_host_address_family
($node);
1293 my $port = PVE
::Tools
::next_vnc_port
($family);
1300 syslog
('info', "starting vnc proxy $upid\n");
1304 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1306 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1308 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1309 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1310 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1311 '-timeout', $timeout, '-authpath', $authpath,
1312 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1315 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1317 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1319 my $qmstr = join(' ', @$qmcmd);
1321 # also redirect stderr (else we get RFB protocol errors)
1322 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1325 PVE
::Tools
::run_command
($cmd);
1330 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1332 PVE
::Tools
::wait_for_vnc_port
($port);
1343 __PACKAGE__-
>register_method({
1344 name
=> 'vncwebsocket',
1345 path
=> '{vmid}/vncwebsocket',
1348 description
=> "You also need to pass a valid ticket (vncticket).",
1349 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1351 description
=> "Opens a weksocket for VNC traffic.",
1353 additionalProperties
=> 0,
1355 node
=> get_standard_option
('pve-node'),
1356 vmid
=> get_standard_option
('pve-vmid'),
1358 description
=> "Ticket from previous call to vncproxy.",
1363 description
=> "Port number returned by previous vncproxy call.",
1373 port
=> { type
=> 'string' },
1379 my $rpcenv = PVE
::RPCEnvironment
::get
();
1381 my $authuser = $rpcenv->get_user();
1383 my $vmid = $param->{vmid
};
1384 my $node = $param->{node
};
1386 my $authpath = "/vms/$vmid";
1388 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1390 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1392 # Note: VNC ports are acessible from outside, so we do not gain any
1393 # security if we verify that $param->{port} belongs to VM $vmid. This
1394 # check is done by verifying the VNC ticket (inside VNC protocol).
1396 my $port = $param->{port
};
1398 return { port
=> $port };
1401 __PACKAGE__-
>register_method({
1402 name
=> 'spiceproxy',
1403 path
=> '{vmid}/spiceproxy',
1408 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1410 description
=> "Returns a SPICE configuration to connect to the VM.",
1412 additionalProperties
=> 0,
1414 node
=> get_standard_option
('pve-node'),
1415 vmid
=> get_standard_option
('pve-vmid'),
1416 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1419 returns
=> get_standard_option
('remote-viewer-config'),
1423 my $rpcenv = PVE
::RPCEnvironment
::get
();
1425 my $authuser = $rpcenv->get_user();
1427 my $vmid = $param->{vmid
};
1428 my $node = $param->{node
};
1429 my $proxy = $param->{proxy
};
1431 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1432 my $title = "VM $vmid";
1433 $title .= " - ". $conf->{name
} if $conf->{name
};
1435 my $port = PVE
::QemuServer
::spice_port
($vmid);
1437 my ($ticket, undef, $remote_viewer_config) =
1438 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1440 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1441 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1443 return $remote_viewer_config;
1446 __PACKAGE__-
>register_method({
1448 path
=> '{vmid}/status',
1451 description
=> "Directory index",
1456 additionalProperties
=> 0,
1458 node
=> get_standard_option
('pve-node'),
1459 vmid
=> get_standard_option
('pve-vmid'),
1467 subdir
=> { type
=> 'string' },
1470 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1476 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1479 { subdir
=> 'current' },
1480 { subdir
=> 'start' },
1481 { subdir
=> 'stop' },
1487 __PACKAGE__-
>register_method({
1488 name
=> 'vm_status',
1489 path
=> '{vmid}/status/current',
1492 protected
=> 1, # qemu pid files are only readable by root
1493 description
=> "Get virtual machine status.",
1495 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1498 additionalProperties
=> 0,
1500 node
=> get_standard_option
('pve-node'),
1501 vmid
=> get_standard_option
('pve-vmid'),
1504 returns
=> { type
=> 'object' },
1509 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1511 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1512 my $status = $vmstatus->{$param->{vmid
}};
1514 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1516 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1521 __PACKAGE__-
>register_method({
1523 path
=> '{vmid}/status/start',
1527 description
=> "Start virtual machine.",
1529 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid',
1536 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1537 skiplock
=> get_standard_option
('skiplock'),
1538 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1539 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1540 machine
=> get_standard_option
('pve-qm-machine'),
1549 my $rpcenv = PVE
::RPCEnvironment
::get
();
1551 my $authuser = $rpcenv->get_user();
1553 my $node = extract_param
($param, 'node');
1555 my $vmid = extract_param
($param, 'vmid');
1557 my $machine = extract_param
($param, 'machine');
1559 my $stateuri = extract_param
($param, 'stateuri');
1560 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1561 if $stateuri && $authuser ne 'root@pam';
1563 my $skiplock = extract_param
($param, 'skiplock');
1564 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1565 if $skiplock && $authuser ne 'root@pam';
1567 my $migratedfrom = extract_param
($param, 'migratedfrom');
1568 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1569 if $migratedfrom && $authuser ne 'root@pam';
1571 # read spice ticket from STDIN
1573 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1574 if (defined(my $line = <>)) {
1576 $spice_ticket = $line;
1580 PVE
::Cluster
::check_cfs_quorum
();
1582 my $storecfg = PVE
::Storage
::config
();
1584 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1585 $rpcenv->{type
} ne 'ha') {
1590 my $service = "vm:$vmid";
1592 my $cmd = ['ha-manager', 'enable', $service];
1594 print "Executing HA start for VM $vmid\n";
1596 PVE
::Tools
::run_command
($cmd);
1601 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1608 syslog
('info', "start VM $vmid: $upid\n");
1610 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1611 $machine, $spice_ticket);
1616 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1620 __PACKAGE__-
>register_method({
1622 path
=> '{vmid}/status/stop',
1626 description
=> "Stop virtual machine.",
1628 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1631 additionalProperties
=> 0,
1633 node
=> get_standard_option
('pve-node'),
1634 vmid
=> get_standard_option
('pve-vmid',
1635 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1636 skiplock
=> get_standard_option
('skiplock'),
1637 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1639 description
=> "Wait maximal timeout seconds.",
1645 description
=> "Do not decativate storage volumes.",
1658 my $rpcenv = PVE
::RPCEnvironment
::get
();
1660 my $authuser = $rpcenv->get_user();
1662 my $node = extract_param
($param, 'node');
1664 my $vmid = extract_param
($param, 'vmid');
1666 my $skiplock = extract_param
($param, 'skiplock');
1667 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1668 if $skiplock && $authuser ne 'root@pam';
1670 my $keepActive = extract_param
($param, 'keepActive');
1671 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1672 if $keepActive && $authuser ne 'root@pam';
1674 my $migratedfrom = extract_param
($param, 'migratedfrom');
1675 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1676 if $migratedfrom && $authuser ne 'root@pam';
1679 my $storecfg = PVE
::Storage
::config
();
1681 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1686 my $service = "vm:$vmid";
1688 my $cmd = ['ha-manager', 'disable', $service];
1690 print "Executing HA stop for VM $vmid\n";
1692 PVE
::Tools
::run_command
($cmd);
1697 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1703 syslog
('info', "stop VM $vmid: $upid\n");
1705 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1706 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1711 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1715 __PACKAGE__-
>register_method({
1717 path
=> '{vmid}/status/reset',
1721 description
=> "Reset virtual machine.",
1723 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1726 additionalProperties
=> 0,
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid',
1730 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1731 skiplock
=> get_standard_option
('skiplock'),
1740 my $rpcenv = PVE
::RPCEnvironment
::get
();
1742 my $authuser = $rpcenv->get_user();
1744 my $node = extract_param
($param, 'node');
1746 my $vmid = extract_param
($param, 'vmid');
1748 my $skiplock = extract_param
($param, 'skiplock');
1749 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1750 if $skiplock && $authuser ne 'root@pam';
1752 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1757 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1762 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1765 __PACKAGE__-
>register_method({
1766 name
=> 'vm_shutdown',
1767 path
=> '{vmid}/status/shutdown',
1771 description
=> "Shutdown virtual machine.",
1773 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1776 additionalProperties
=> 0,
1778 node
=> get_standard_option
('pve-node'),
1779 vmid
=> get_standard_option
('pve-vmid',
1780 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1781 skiplock
=> get_standard_option
('skiplock'),
1783 description
=> "Wait maximal timeout seconds.",
1789 description
=> "Make sure the VM stops.",
1795 description
=> "Do not decativate storage volumes.",
1808 my $rpcenv = PVE
::RPCEnvironment
::get
();
1810 my $authuser = $rpcenv->get_user();
1812 my $node = extract_param
($param, 'node');
1814 my $vmid = extract_param
($param, 'vmid');
1816 my $skiplock = extract_param
($param, 'skiplock');
1817 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1818 if $skiplock && $authuser ne 'root@pam';
1820 my $keepActive = extract_param
($param, 'keepActive');
1821 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1822 if $keepActive && $authuser ne 'root@pam';
1824 my $storecfg = PVE
::Storage
::config
();
1829 syslog
('info', "shutdown VM $vmid: $upid\n");
1831 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1832 1, $param->{forceStop
}, $keepActive);
1837 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1840 __PACKAGE__-
>register_method({
1841 name
=> 'vm_suspend',
1842 path
=> '{vmid}/status/suspend',
1846 description
=> "Suspend virtual machine.",
1848 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1851 additionalProperties
=> 0,
1853 node
=> get_standard_option
('pve-node'),
1854 vmid
=> get_standard_option
('pve-vmid',
1855 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1856 skiplock
=> get_standard_option
('skiplock'),
1865 my $rpcenv = PVE
::RPCEnvironment
::get
();
1867 my $authuser = $rpcenv->get_user();
1869 my $node = extract_param
($param, 'node');
1871 my $vmid = extract_param
($param, 'vmid');
1873 my $skiplock = extract_param
($param, 'skiplock');
1874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1875 if $skiplock && $authuser ne 'root@pam';
1877 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1882 syslog
('info', "suspend VM $vmid: $upid\n");
1884 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1889 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1892 __PACKAGE__-
>register_method({
1893 name
=> 'vm_resume',
1894 path
=> '{vmid}/status/resume',
1898 description
=> "Resume virtual machine.",
1900 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1903 additionalProperties
=> 0,
1905 node
=> get_standard_option
('pve-node'),
1906 vmid
=> get_standard_option
('pve-vmid',
1907 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1908 skiplock
=> get_standard_option
('skiplock'),
1909 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1919 my $rpcenv = PVE
::RPCEnvironment
::get
();
1921 my $authuser = $rpcenv->get_user();
1923 my $node = extract_param
($param, 'node');
1925 my $vmid = extract_param
($param, 'vmid');
1927 my $skiplock = extract_param
($param, 'skiplock');
1928 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1929 if $skiplock && $authuser ne 'root@pam';
1931 my $nocheck = extract_param
($param, 'nocheck');
1933 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1938 syslog
('info', "resume VM $vmid: $upid\n");
1940 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1945 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1948 __PACKAGE__-
>register_method({
1949 name
=> 'vm_sendkey',
1950 path
=> '{vmid}/sendkey',
1954 description
=> "Send key event to virtual machine.",
1956 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1959 additionalProperties
=> 0,
1961 node
=> get_standard_option
('pve-node'),
1962 vmid
=> get_standard_option
('pve-vmid',
1963 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1964 skiplock
=> get_standard_option
('skiplock'),
1966 description
=> "The key (qemu monitor encoding).",
1971 returns
=> { type
=> 'null'},
1975 my $rpcenv = PVE
::RPCEnvironment
::get
();
1977 my $authuser = $rpcenv->get_user();
1979 my $node = extract_param
($param, 'node');
1981 my $vmid = extract_param
($param, 'vmid');
1983 my $skiplock = extract_param
($param, 'skiplock');
1984 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1985 if $skiplock && $authuser ne 'root@pam';
1987 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1992 __PACKAGE__-
>register_method({
1993 name
=> 'vm_feature',
1994 path
=> '{vmid}/feature',
1998 description
=> "Check if feature for virtual machine is available.",
2000 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2003 additionalProperties
=> 0,
2005 node
=> get_standard_option
('pve-node'),
2006 vmid
=> get_standard_option
('pve-vmid'),
2008 description
=> "Feature to check.",
2010 enum
=> [ 'snapshot', 'clone', 'copy' ],
2012 snapname
=> get_standard_option
('pve-snapshot-name', {
2020 hasFeature
=> { type
=> 'boolean' },
2023 items
=> { type
=> 'string' },
2030 my $node = extract_param
($param, 'node');
2032 my $vmid = extract_param
($param, 'vmid');
2034 my $snapname = extract_param
($param, 'snapname');
2036 my $feature = extract_param
($param, 'feature');
2038 my $running = PVE
::QemuServer
::check_running
($vmid);
2040 my $conf = PVE
::QemuServer
::load_config
($vmid);
2043 my $snap = $conf->{snapshots
}->{$snapname};
2044 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2047 my $storecfg = PVE
::Storage
::config
();
2049 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2050 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2053 hasFeature
=> $hasFeature,
2054 nodes
=> [ keys %$nodelist ],
2058 __PACKAGE__-
>register_method({
2060 path
=> '{vmid}/clone',
2064 description
=> "Create a copy of virtual machine/template.",
2066 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2067 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2068 "'Datastore.AllocateSpace' on any used storage.",
2071 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2073 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2074 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2079 additionalProperties
=> 0,
2081 node
=> get_standard_option
('pve-node'),
2082 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2083 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2086 type
=> 'string', format
=> 'dns-name',
2087 description
=> "Set a name for the new VM.",
2092 description
=> "Description for the new VM.",
2096 type
=> 'string', format
=> 'pve-poolid',
2097 description
=> "Add the new VM to the specified pool.",
2099 snapname
=> get_standard_option
('pve-snapshot-name', {
2102 storage
=> get_standard_option
('pve-storage-id', {
2103 description
=> "Target storage for full clone.",
2108 description
=> "Target format for file storage.",
2112 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2117 description
=> "Create a full copy of all disk. This is always done when " .
2118 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2121 target
=> get_standard_option
('pve-node', {
2122 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2133 my $rpcenv = PVE
::RPCEnvironment
::get
();
2135 my $authuser = $rpcenv->get_user();
2137 my $node = extract_param
($param, 'node');
2139 my $vmid = extract_param
($param, 'vmid');
2141 my $newid = extract_param
($param, 'newid');
2143 my $pool = extract_param
($param, 'pool');
2145 if (defined($pool)) {
2146 $rpcenv->check_pool_exist($pool);
2149 my $snapname = extract_param
($param, 'snapname');
2151 my $storage = extract_param
($param, 'storage');
2153 my $format = extract_param
($param, 'format');
2155 my $target = extract_param
($param, 'target');
2157 my $localnode = PVE
::INotify
::nodename
();
2159 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2161 PVE
::Cluster
::check_node_exists
($target) if $target;
2163 my $storecfg = PVE
::Storage
::config
();
2166 # check if storage is enabled on local node
2167 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2169 # check if storage is available on target node
2170 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2171 # clone only works if target storage is shared
2172 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2173 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2177 PVE
::Cluster
::check_cfs_quorum
();
2179 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2181 # exclusive lock if VM is running - else shared lock is enough;
2182 my $shared_lock = $running ?
0 : 1;
2186 # do all tests after lock
2187 # we also try to do all tests before we fork the worker
2189 my $conf = PVE
::QemuServer
::load_config
($vmid);
2191 PVE
::QemuServer
::check_lock
($conf);
2193 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2195 die "unexpected state change\n" if $verify_running != $running;
2197 die "snapshot '$snapname' does not exist\n"
2198 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2200 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2202 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2204 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2206 my $conffile = PVE
::QemuServer
::config_file
($newid);
2208 die "unable to create VM $newid: config file already exists\n"
2211 my $newconf = { lock => 'clone' };
2216 foreach my $opt (keys %$oldconf) {
2217 my $value = $oldconf->{$opt};
2219 # do not copy snapshot related info
2220 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2221 $opt eq 'vmstate' || $opt eq 'snapstate';
2223 # no need to copy unused images, because VMID(owner) changes anyways
2224 next if $opt =~ m/^unused\d+$/;
2226 # always change MAC! address
2227 if ($opt =~ m/^net(\d+)$/) {
2228 my $net = PVE
::QemuServer
::parse_net
($value);
2229 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2230 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2231 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2232 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2233 die "unable to parse drive options for '$opt'\n" if !$drive;
2234 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2235 $newconf->{$opt} = $value; # simply copy configuration
2237 if ($param->{full
}) {
2238 die "Full clone feature is not available"
2239 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2240 $fullclone->{$opt} = 1;
2242 # not full means clone instead of copy
2243 die "Linked clone feature is not available"
2244 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2246 $drives->{$opt} = $drive;
2247 push @$vollist, $drive->{file
};
2250 # copy everything else
2251 $newconf->{$opt} = $value;
2255 # auto generate a new uuid
2256 my ($uuid, $uuid_str);
2257 UUID
::generate
($uuid);
2258 UUID
::unparse
($uuid, $uuid_str);
2259 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2260 $smbios1->{uuid
} = $uuid_str;
2261 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2263 delete $newconf->{template
};
2265 if ($param->{name
}) {
2266 $newconf->{name
} = $param->{name
};
2268 if ($oldconf->{name
}) {
2269 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2271 $newconf->{name
} = "Copy-of-VM-$vmid";
2275 if ($param->{description
}) {
2276 $newconf->{description
} = $param->{description
};
2279 # create empty/temp config - this fails if VM already exists on other node
2280 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2285 my $newvollist = [];
2288 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2290 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2292 foreach my $opt (keys %$drives) {
2293 my $drive = $drives->{$opt};
2295 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2296 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2298 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2300 PVE
::QemuServer
::write_config
($newid, $newconf);
2303 delete $newconf->{lock};
2304 PVE
::QemuServer
::write_config
($newid, $newconf);
2307 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2308 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2310 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2311 die "Failed to move config to node '$target' - rename failed: $!\n"
2312 if !rename($conffile, $newconffile);
2315 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2320 sleep 1; # some storage like rbd need to wait before release volume - really?
2322 foreach my $volid (@$newvollist) {
2323 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2326 die "clone failed: $err";
2332 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2334 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2337 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2338 # Aquire exclusive lock lock for $newid
2339 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2344 __PACKAGE__-
>register_method({
2345 name
=> 'move_vm_disk',
2346 path
=> '{vmid}/move_disk',
2350 description
=> "Move volume to different storage.",
2352 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2353 "and 'Datastore.AllocateSpace' permissions on the storage.",
2356 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2357 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2361 additionalProperties
=> 0,
2363 node
=> get_standard_option
('pve-node'),
2364 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2367 description
=> "The disk you want to move.",
2368 enum
=> [ PVE
::QemuServer
::disknames
() ],
2370 storage
=> get_standard_option
('pve-storage-id', {
2371 description
=> "Target storage.",
2372 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2376 description
=> "Target Format.",
2377 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2382 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2388 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2396 description
=> "the task ID.",
2401 my $rpcenv = PVE
::RPCEnvironment
::get
();
2403 my $authuser = $rpcenv->get_user();
2405 my $node = extract_param
($param, 'node');
2407 my $vmid = extract_param
($param, 'vmid');
2409 my $digest = extract_param
($param, 'digest');
2411 my $disk = extract_param
($param, 'disk');
2413 my $storeid = extract_param
($param, 'storage');
2415 my $format = extract_param
($param, 'format');
2417 my $storecfg = PVE
::Storage
::config
();
2419 my $updatefn = sub {
2421 my $conf = PVE
::QemuServer
::load_config
($vmid);
2423 die "checksum missmatch (file change by other user?)\n"
2424 if $digest && $digest ne $conf->{digest
};
2426 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2428 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2430 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2432 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2435 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2436 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2440 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2441 (!$format || !$oldfmt || $oldfmt eq $format);
2443 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2445 my $running = PVE
::QemuServer
::check_running
($vmid);
2447 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2451 my $newvollist = [];
2454 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2456 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2457 $vmid, $storeid, $format, 1, $newvollist);
2459 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2461 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2463 PVE
::QemuServer
::write_config
($vmid, $conf);
2466 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2467 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2474 foreach my $volid (@$newvollist) {
2475 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2478 die "storage migration failed: $err";
2481 if ($param->{delete}) {
2482 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2483 warn "volume $old_volid still has snapshots, can't delete it\n";
2484 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2485 PVE
::QemuServer
::write_config
($vmid, $conf);
2487 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2493 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2496 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2499 __PACKAGE__-
>register_method({
2500 name
=> 'migrate_vm',
2501 path
=> '{vmid}/migrate',
2505 description
=> "Migrate virtual machine. Creates a new migration task.",
2507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2510 additionalProperties
=> 0,
2512 node
=> get_standard_option
('pve-node'),
2513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2514 target
=> get_standard_option
('pve-node', {
2515 description
=> "Target node.",
2516 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2520 description
=> "Use online/live migration.",
2525 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2532 description
=> "the task ID.",
2537 my $rpcenv = PVE
::RPCEnvironment
::get
();
2539 my $authuser = $rpcenv->get_user();
2541 my $target = extract_param
($param, 'target');
2543 my $localnode = PVE
::INotify
::nodename
();
2544 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2546 PVE
::Cluster
::check_cfs_quorum
();
2548 PVE
::Cluster
::check_node_exists
($target);
2550 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2552 my $vmid = extract_param
($param, 'vmid');
2554 raise_param_exc
({ force
=> "Only root may use this option." })
2555 if $param->{force
} && $authuser ne 'root@pam';
2558 my $conf = PVE
::QemuServer
::load_config
($vmid);
2560 # try to detect errors early
2562 PVE
::QemuServer
::check_lock
($conf);
2564 if (PVE
::QemuServer
::check_running
($vmid)) {
2565 die "cant migrate running VM without --online\n"
2566 if !$param->{online
};
2569 my $storecfg = PVE
::Storage
::config
();
2570 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2572 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2577 my $service = "vm:$vmid";
2579 my $cmd = ['ha-manager', 'migrate', $service, $target];
2581 print "Executing HA migrate for VM $vmid to node $target\n";
2583 PVE
::Tools
::run_command
($cmd);
2588 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2595 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2598 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2603 __PACKAGE__-
>register_method({
2605 path
=> '{vmid}/monitor',
2609 description
=> "Execute Qemu monitor commands.",
2611 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2614 additionalProperties
=> 0,
2616 node
=> get_standard_option
('pve-node'),
2617 vmid
=> get_standard_option
('pve-vmid'),
2620 description
=> "The monitor command.",
2624 returns
=> { type
=> 'string'},
2628 my $vmid = $param->{vmid
};
2630 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2634 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2636 $res = "ERROR: $@" if $@;
2641 __PACKAGE__-
>register_method({
2642 name
=> 'resize_vm',
2643 path
=> '{vmid}/resize',
2647 description
=> "Extend volume size.",
2649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2652 additionalProperties
=> 0,
2654 node
=> get_standard_option
('pve-node'),
2655 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2656 skiplock
=> get_standard_option
('skiplock'),
2659 description
=> "The disk you want to resize.",
2660 enum
=> [PVE
::QemuServer
::disknames
()],
2664 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2665 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.",
2669 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2675 returns
=> { type
=> 'null'},
2679 my $rpcenv = PVE
::RPCEnvironment
::get
();
2681 my $authuser = $rpcenv->get_user();
2683 my $node = extract_param
($param, 'node');
2685 my $vmid = extract_param
($param, 'vmid');
2687 my $digest = extract_param
($param, 'digest');
2689 my $disk = extract_param
($param, 'disk');
2691 my $sizestr = extract_param
($param, 'size');
2693 my $skiplock = extract_param
($param, 'skiplock');
2694 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2695 if $skiplock && $authuser ne 'root@pam';
2697 my $storecfg = PVE
::Storage
::config
();
2699 my $updatefn = sub {
2701 my $conf = PVE
::QemuServer
::load_config
($vmid);
2703 die "checksum missmatch (file change by other user?)\n"
2704 if $digest && $digest ne $conf->{digest
};
2705 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2707 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2709 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2711 my (undef, undef, undef, undef, undef, undef, $format) =
2712 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2714 die "can't resize volume: $disk if snapshot exists\n"
2715 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2717 my $volid = $drive->{file
};
2719 die "disk '$disk' has no associated volume\n" if !$volid;
2721 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2723 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2725 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2727 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2729 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2730 my ($ext, $newsize, $unit) = ($1, $2, $4);
2733 $newsize = $newsize * 1024;
2734 } elsif ($unit eq 'M') {
2735 $newsize = $newsize * 1024 * 1024;
2736 } elsif ($unit eq 'G') {
2737 $newsize = $newsize * 1024 * 1024 * 1024;
2738 } elsif ($unit eq 'T') {
2739 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2742 $newsize += $size if $ext;
2743 $newsize = int($newsize);
2745 die "unable to skrink disk size\n" if $newsize < $size;
2747 return if $size == $newsize;
2749 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2751 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2753 $drive->{size
} = $newsize;
2754 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2756 PVE
::QemuServer
::write_config
($vmid, $conf);
2759 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2763 __PACKAGE__-
>register_method({
2764 name
=> 'snapshot_list',
2765 path
=> '{vmid}/snapshot',
2767 description
=> "List all snapshots.",
2769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2772 protected
=> 1, # qemu pid files are only readable by root
2774 additionalProperties
=> 0,
2776 vmid
=> get_standard_option
('pve-vmid'),
2777 node
=> get_standard_option
('pve-node'),
2786 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2791 my $vmid = $param->{vmid
};
2793 my $conf = PVE
::QemuServer
::load_config
($vmid);
2794 my $snaphash = $conf->{snapshots
} || {};
2798 foreach my $name (keys %$snaphash) {
2799 my $d = $snaphash->{$name};
2802 snaptime
=> $d->{snaptime
} || 0,
2803 vmstate
=> $d->{vmstate
} ?
1 : 0,
2804 description
=> $d->{description
} || '',
2806 $item->{parent
} = $d->{parent
} if $d->{parent
};
2807 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2811 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2812 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2813 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2815 push @$res, $current;
2820 __PACKAGE__-
>register_method({
2822 path
=> '{vmid}/snapshot',
2826 description
=> "Snapshot a VM.",
2828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2831 additionalProperties
=> 0,
2833 node
=> get_standard_option
('pve-node'),
2834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2835 snapname
=> get_standard_option
('pve-snapshot-name'),
2839 description
=> "Save the vmstate",
2844 description
=> "A textual description or comment.",
2850 description
=> "the task ID.",
2855 my $rpcenv = PVE
::RPCEnvironment
::get
();
2857 my $authuser = $rpcenv->get_user();
2859 my $node = extract_param
($param, 'node');
2861 my $vmid = extract_param
($param, 'vmid');
2863 my $snapname = extract_param
($param, 'snapname');
2865 die "unable to use snapshot name 'current' (reserved name)\n"
2866 if $snapname eq 'current';
2869 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2870 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2871 $param->{description
});
2874 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2877 __PACKAGE__-
>register_method({
2878 name
=> 'snapshot_cmd_idx',
2879 path
=> '{vmid}/snapshot/{snapname}',
2886 additionalProperties
=> 0,
2888 vmid
=> get_standard_option
('pve-vmid'),
2889 node
=> get_standard_option
('pve-node'),
2890 snapname
=> get_standard_option
('pve-snapshot-name'),
2899 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2906 push @$res, { cmd
=> 'rollback' };
2907 push @$res, { cmd
=> 'config' };
2912 __PACKAGE__-
>register_method({
2913 name
=> 'update_snapshot_config',
2914 path
=> '{vmid}/snapshot/{snapname}/config',
2918 description
=> "Update snapshot metadata.",
2920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2923 additionalProperties
=> 0,
2925 node
=> get_standard_option
('pve-node'),
2926 vmid
=> get_standard_option
('pve-vmid'),
2927 snapname
=> get_standard_option
('pve-snapshot-name'),
2931 description
=> "A textual description or comment.",
2935 returns
=> { type
=> 'null' },
2939 my $rpcenv = PVE
::RPCEnvironment
::get
();
2941 my $authuser = $rpcenv->get_user();
2943 my $vmid = extract_param
($param, 'vmid');
2945 my $snapname = extract_param
($param, 'snapname');
2947 return undef if !defined($param->{description
});
2949 my $updatefn = sub {
2951 my $conf = PVE
::QemuServer
::load_config
($vmid);
2953 PVE
::QemuServer
::check_lock
($conf);
2955 my $snap = $conf->{snapshots
}->{$snapname};
2957 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2959 $snap->{description
} = $param->{description
} if defined($param->{description
});
2961 PVE
::QemuServer
::write_config
($vmid, $conf);
2964 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2969 __PACKAGE__-
>register_method({
2970 name
=> 'get_snapshot_config',
2971 path
=> '{vmid}/snapshot/{snapname}/config',
2974 description
=> "Get snapshot configuration",
2976 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2979 additionalProperties
=> 0,
2981 node
=> get_standard_option
('pve-node'),
2982 vmid
=> get_standard_option
('pve-vmid'),
2983 snapname
=> get_standard_option
('pve-snapshot-name'),
2986 returns
=> { type
=> "object" },
2990 my $rpcenv = PVE
::RPCEnvironment
::get
();
2992 my $authuser = $rpcenv->get_user();
2994 my $vmid = extract_param
($param, 'vmid');
2996 my $snapname = extract_param
($param, 'snapname');
2998 my $conf = PVE
::QemuServer
::load_config
($vmid);
3000 my $snap = $conf->{snapshots
}->{$snapname};
3002 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3007 __PACKAGE__-
>register_method({
3009 path
=> '{vmid}/snapshot/{snapname}/rollback',
3013 description
=> "Rollback VM state to specified snapshot.",
3015 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3018 additionalProperties
=> 0,
3020 node
=> get_standard_option
('pve-node'),
3021 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3022 snapname
=> get_standard_option
('pve-snapshot-name'),
3027 description
=> "the task ID.",
3032 my $rpcenv = PVE
::RPCEnvironment
::get
();
3034 my $authuser = $rpcenv->get_user();
3036 my $node = extract_param
($param, 'node');
3038 my $vmid = extract_param
($param, 'vmid');
3040 my $snapname = extract_param
($param, 'snapname');
3043 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3044 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3047 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3050 __PACKAGE__-
>register_method({
3051 name
=> 'delsnapshot',
3052 path
=> '{vmid}/snapshot/{snapname}',
3056 description
=> "Delete a VM snapshot.",
3058 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3061 additionalProperties
=> 0,
3063 node
=> get_standard_option
('pve-node'),
3064 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3065 snapname
=> get_standard_option
('pve-snapshot-name'),
3069 description
=> "For removal from config file, even if removing disk snapshots fails.",
3075 description
=> "the task ID.",
3080 my $rpcenv = PVE
::RPCEnvironment
::get
();
3082 my $authuser = $rpcenv->get_user();
3084 my $node = extract_param
($param, 'node');
3086 my $vmid = extract_param
($param, 'vmid');
3088 my $snapname = extract_param
($param, 'snapname');
3091 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3092 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3095 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3098 __PACKAGE__-
>register_method({
3100 path
=> '{vmid}/template',
3104 description
=> "Create a Template.",
3106 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3107 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3110 additionalProperties
=> 0,
3112 node
=> get_standard_option
('pve-node'),
3113 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3117 description
=> "If you want to convert only 1 disk to base image.",
3118 enum
=> [PVE
::QemuServer
::disknames
()],
3123 returns
=> { type
=> 'null'},
3127 my $rpcenv = PVE
::RPCEnvironment
::get
();
3129 my $authuser = $rpcenv->get_user();
3131 my $node = extract_param
($param, 'node');
3133 my $vmid = extract_param
($param, 'vmid');
3135 my $disk = extract_param
($param, 'disk');
3137 my $updatefn = sub {
3139 my $conf = PVE
::QemuServer
::load_config
($vmid);
3141 PVE
::QemuServer
::check_lock
($conf);
3143 die "unable to create template, because VM contains snapshots\n"
3144 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3146 die "you can't convert a template to a template\n"
3147 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3149 die "you can't convert a VM to template if VM is running\n"
3150 if PVE
::QemuServer
::check_running
($vmid);
3153 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3156 $conf->{template
} = 1;
3157 PVE
::QemuServer
::write_config
($vmid, $conf);
3159 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3162 PVE
::QemuServer
::lock_config
($vmid, $updatefn);