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. The qemu process will exit immediatly. This" .
1627 "is akin to pulling the power plug of a running computer and may damage the VM data",
1629 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1632 additionalProperties
=> 0,
1634 node
=> get_standard_option
('pve-node'),
1635 vmid
=> get_standard_option
('pve-vmid',
1636 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1637 skiplock
=> get_standard_option
('skiplock'),
1638 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1640 description
=> "Wait maximal timeout seconds.",
1646 description
=> "Do not decativate storage volumes.",
1659 my $rpcenv = PVE
::RPCEnvironment
::get
();
1661 my $authuser = $rpcenv->get_user();
1663 my $node = extract_param
($param, 'node');
1665 my $vmid = extract_param
($param, 'vmid');
1667 my $skiplock = extract_param
($param, 'skiplock');
1668 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1669 if $skiplock && $authuser ne 'root@pam';
1671 my $keepActive = extract_param
($param, 'keepActive');
1672 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1673 if $keepActive && $authuser ne 'root@pam';
1675 my $migratedfrom = extract_param
($param, 'migratedfrom');
1676 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1677 if $migratedfrom && $authuser ne 'root@pam';
1680 my $storecfg = PVE
::Storage
::config
();
1682 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1687 my $service = "vm:$vmid";
1689 my $cmd = ['ha-manager', 'disable', $service];
1691 print "Executing HA stop for VM $vmid\n";
1693 PVE
::Tools
::run_command
($cmd);
1698 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1704 syslog
('info', "stop VM $vmid: $upid\n");
1706 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1707 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1712 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1716 __PACKAGE__-
>register_method({
1718 path
=> '{vmid}/status/reset',
1722 description
=> "Reset virtual machine.",
1724 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1727 additionalProperties
=> 0,
1729 node
=> get_standard_option
('pve-node'),
1730 vmid
=> get_standard_option
('pve-vmid',
1731 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1732 skiplock
=> get_standard_option
('skiplock'),
1741 my $rpcenv = PVE
::RPCEnvironment
::get
();
1743 my $authuser = $rpcenv->get_user();
1745 my $node = extract_param
($param, 'node');
1747 my $vmid = extract_param
($param, 'vmid');
1749 my $skiplock = extract_param
($param, 'skiplock');
1750 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1751 if $skiplock && $authuser ne 'root@pam';
1753 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1758 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1763 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1766 __PACKAGE__-
>register_method({
1767 name
=> 'vm_shutdown',
1768 path
=> '{vmid}/status/shutdown',
1772 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1773 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1775 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1778 additionalProperties
=> 0,
1780 node
=> get_standard_option
('pve-node'),
1781 vmid
=> get_standard_option
('pve-vmid',
1782 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1783 skiplock
=> get_standard_option
('skiplock'),
1785 description
=> "Wait maximal timeout seconds.",
1791 description
=> "Make sure the VM stops.",
1797 description
=> "Do not decativate storage volumes.",
1810 my $rpcenv = PVE
::RPCEnvironment
::get
();
1812 my $authuser = $rpcenv->get_user();
1814 my $node = extract_param
($param, 'node');
1816 my $vmid = extract_param
($param, 'vmid');
1818 my $skiplock = extract_param
($param, 'skiplock');
1819 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1820 if $skiplock && $authuser ne 'root@pam';
1822 my $keepActive = extract_param
($param, 'keepActive');
1823 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1824 if $keepActive && $authuser ne 'root@pam';
1826 my $storecfg = PVE
::Storage
::config
();
1831 syslog
('info', "shutdown VM $vmid: $upid\n");
1833 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1834 1, $param->{forceStop
}, $keepActive);
1839 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1842 __PACKAGE__-
>register_method({
1843 name
=> 'vm_suspend',
1844 path
=> '{vmid}/status/suspend',
1848 description
=> "Suspend virtual machine.",
1850 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1853 additionalProperties
=> 0,
1855 node
=> get_standard_option
('pve-node'),
1856 vmid
=> get_standard_option
('pve-vmid',
1857 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1858 skiplock
=> get_standard_option
('skiplock'),
1867 my $rpcenv = PVE
::RPCEnvironment
::get
();
1869 my $authuser = $rpcenv->get_user();
1871 my $node = extract_param
($param, 'node');
1873 my $vmid = extract_param
($param, 'vmid');
1875 my $skiplock = extract_param
($param, 'skiplock');
1876 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1877 if $skiplock && $authuser ne 'root@pam';
1879 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1884 syslog
('info', "suspend VM $vmid: $upid\n");
1886 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1891 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1894 __PACKAGE__-
>register_method({
1895 name
=> 'vm_resume',
1896 path
=> '{vmid}/status/resume',
1900 description
=> "Resume virtual machine.",
1902 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid',
1909 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1910 skiplock
=> get_standard_option
('skiplock'),
1911 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 my $nocheck = extract_param
($param, 'nocheck');
1935 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1940 syslog
('info', "resume VM $vmid: $upid\n");
1942 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1947 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1950 __PACKAGE__-
>register_method({
1951 name
=> 'vm_sendkey',
1952 path
=> '{vmid}/sendkey',
1956 description
=> "Send key event to virtual machine.",
1958 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1961 additionalProperties
=> 0,
1963 node
=> get_standard_option
('pve-node'),
1964 vmid
=> get_standard_option
('pve-vmid',
1965 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1966 skiplock
=> get_standard_option
('skiplock'),
1968 description
=> "The key (qemu monitor encoding).",
1973 returns
=> { type
=> 'null'},
1977 my $rpcenv = PVE
::RPCEnvironment
::get
();
1979 my $authuser = $rpcenv->get_user();
1981 my $node = extract_param
($param, 'node');
1983 my $vmid = extract_param
($param, 'vmid');
1985 my $skiplock = extract_param
($param, 'skiplock');
1986 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1987 if $skiplock && $authuser ne 'root@pam';
1989 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1994 __PACKAGE__-
>register_method({
1995 name
=> 'vm_feature',
1996 path
=> '{vmid}/feature',
2000 description
=> "Check if feature for virtual machine is available.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid'),
2010 description
=> "Feature to check.",
2012 enum
=> [ 'snapshot', 'clone', 'copy' ],
2014 snapname
=> get_standard_option
('pve-snapshot-name', {
2022 hasFeature
=> { type
=> 'boolean' },
2025 items
=> { type
=> 'string' },
2032 my $node = extract_param
($param, 'node');
2034 my $vmid = extract_param
($param, 'vmid');
2036 my $snapname = extract_param
($param, 'snapname');
2038 my $feature = extract_param
($param, 'feature');
2040 my $running = PVE
::QemuServer
::check_running
($vmid);
2042 my $conf = PVE
::QemuServer
::load_config
($vmid);
2045 my $snap = $conf->{snapshots
}->{$snapname};
2046 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2049 my $storecfg = PVE
::Storage
::config
();
2051 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2052 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2055 hasFeature
=> $hasFeature,
2056 nodes
=> [ keys %$nodelist ],
2060 __PACKAGE__-
>register_method({
2062 path
=> '{vmid}/clone',
2066 description
=> "Create a copy of virtual machine/template.",
2068 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2069 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2070 "'Datastore.AllocateSpace' on any used storage.",
2073 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2075 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2076 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2085 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2088 type
=> 'string', format
=> 'dns-name',
2089 description
=> "Set a name for the new VM.",
2094 description
=> "Description for the new VM.",
2098 type
=> 'string', format
=> 'pve-poolid',
2099 description
=> "Add the new VM to the specified pool.",
2101 snapname
=> get_standard_option
('pve-snapshot-name', {
2104 storage
=> get_standard_option
('pve-storage-id', {
2105 description
=> "Target storage for full clone.",
2110 description
=> "Target format for file storage.",
2114 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2119 description
=> "Create a full copy of all disk. This is always done when " .
2120 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2123 target
=> get_standard_option
('pve-node', {
2124 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2135 my $rpcenv = PVE
::RPCEnvironment
::get
();
2137 my $authuser = $rpcenv->get_user();
2139 my $node = extract_param
($param, 'node');
2141 my $vmid = extract_param
($param, 'vmid');
2143 my $newid = extract_param
($param, 'newid');
2145 my $pool = extract_param
($param, 'pool');
2147 if (defined($pool)) {
2148 $rpcenv->check_pool_exist($pool);
2151 my $snapname = extract_param
($param, 'snapname');
2153 my $storage = extract_param
($param, 'storage');
2155 my $format = extract_param
($param, 'format');
2157 my $target = extract_param
($param, 'target');
2159 my $localnode = PVE
::INotify
::nodename
();
2161 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2163 PVE
::Cluster
::check_node_exists
($target) if $target;
2165 my $storecfg = PVE
::Storage
::config
();
2168 # check if storage is enabled on local node
2169 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2171 # check if storage is available on target node
2172 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2173 # clone only works if target storage is shared
2174 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2175 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2179 PVE
::Cluster
::check_cfs_quorum
();
2181 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2183 # exclusive lock if VM is running - else shared lock is enough;
2184 my $shared_lock = $running ?
0 : 1;
2188 # do all tests after lock
2189 # we also try to do all tests before we fork the worker
2191 my $conf = PVE
::QemuServer
::load_config
($vmid);
2193 PVE
::QemuServer
::check_lock
($conf);
2195 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2197 die "unexpected state change\n" if $verify_running != $running;
2199 die "snapshot '$snapname' does not exist\n"
2200 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2202 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2204 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2206 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2208 my $conffile = PVE
::QemuServer
::config_file
($newid);
2210 die "unable to create VM $newid: config file already exists\n"
2213 my $newconf = { lock => 'clone' };
2218 foreach my $opt (keys %$oldconf) {
2219 my $value = $oldconf->{$opt};
2221 # do not copy snapshot related info
2222 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2223 $opt eq 'vmstate' || $opt eq 'snapstate';
2225 # no need to copy unused images, because VMID(owner) changes anyways
2226 next if $opt =~ m/^unused\d+$/;
2228 # always change MAC! address
2229 if ($opt =~ m/^net(\d+)$/) {
2230 my $net = PVE
::QemuServer
::parse_net
($value);
2231 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2232 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2233 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2234 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2235 die "unable to parse drive options for '$opt'\n" if !$drive;
2236 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2237 $newconf->{$opt} = $value; # simply copy configuration
2239 if ($param->{full
}) {
2240 die "Full clone feature is not available"
2241 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2242 $fullclone->{$opt} = 1;
2244 # not full means clone instead of copy
2245 die "Linked clone feature is not available"
2246 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2248 $drives->{$opt} = $drive;
2249 push @$vollist, $drive->{file
};
2252 # copy everything else
2253 $newconf->{$opt} = $value;
2257 # auto generate a new uuid
2258 my ($uuid, $uuid_str);
2259 UUID
::generate
($uuid);
2260 UUID
::unparse
($uuid, $uuid_str);
2261 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2262 $smbios1->{uuid
} = $uuid_str;
2263 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2265 delete $newconf->{template
};
2267 if ($param->{name
}) {
2268 $newconf->{name
} = $param->{name
};
2270 if ($oldconf->{name
}) {
2271 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2273 $newconf->{name
} = "Copy-of-VM-$vmid";
2277 if ($param->{description
}) {
2278 $newconf->{description
} = $param->{description
};
2281 # create empty/temp config - this fails if VM already exists on other node
2282 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2287 my $newvollist = [];
2290 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2292 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2294 foreach my $opt (keys %$drives) {
2295 my $drive = $drives->{$opt};
2297 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2298 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2300 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2302 PVE
::QemuServer
::write_config
($newid, $newconf);
2305 delete $newconf->{lock};
2306 PVE
::QemuServer
::write_config
($newid, $newconf);
2309 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2310 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2312 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2313 die "Failed to move config to node '$target' - rename failed: $!\n"
2314 if !rename($conffile, $newconffile);
2317 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2322 sleep 1; # some storage like rbd need to wait before release volume - really?
2324 foreach my $volid (@$newvollist) {
2325 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2328 die "clone failed: $err";
2334 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2336 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2339 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2340 # Aquire exclusive lock lock for $newid
2341 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2346 __PACKAGE__-
>register_method({
2347 name
=> 'move_vm_disk',
2348 path
=> '{vmid}/move_disk',
2352 description
=> "Move volume to different storage.",
2354 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2355 "and 'Datastore.AllocateSpace' permissions on the storage.",
2358 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2359 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2363 additionalProperties
=> 0,
2365 node
=> get_standard_option
('pve-node'),
2366 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2369 description
=> "The disk you want to move.",
2370 enum
=> [ PVE
::QemuServer
::disknames
() ],
2372 storage
=> get_standard_option
('pve-storage-id', {
2373 description
=> "Target storage.",
2374 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2378 description
=> "Target Format.",
2379 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2384 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2390 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2398 description
=> "the task ID.",
2403 my $rpcenv = PVE
::RPCEnvironment
::get
();
2405 my $authuser = $rpcenv->get_user();
2407 my $node = extract_param
($param, 'node');
2409 my $vmid = extract_param
($param, 'vmid');
2411 my $digest = extract_param
($param, 'digest');
2413 my $disk = extract_param
($param, 'disk');
2415 my $storeid = extract_param
($param, 'storage');
2417 my $format = extract_param
($param, 'format');
2419 my $storecfg = PVE
::Storage
::config
();
2421 my $updatefn = sub {
2423 my $conf = PVE
::QemuServer
::load_config
($vmid);
2425 die "checksum missmatch (file change by other user?)\n"
2426 if $digest && $digest ne $conf->{digest
};
2428 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2430 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2432 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2434 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2437 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2438 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2442 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2443 (!$format || !$oldfmt || $oldfmt eq $format);
2445 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2447 my $running = PVE
::QemuServer
::check_running
($vmid);
2449 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2453 my $newvollist = [];
2456 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2458 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2459 $vmid, $storeid, $format, 1, $newvollist);
2461 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2463 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2465 PVE
::QemuServer
::write_config
($vmid, $conf);
2468 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2469 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2476 foreach my $volid (@$newvollist) {
2477 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2480 die "storage migration failed: $err";
2483 if ($param->{delete}) {
2484 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2485 warn "volume $old_volid still has snapshots, can't delete it\n";
2486 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2487 PVE
::QemuServer
::write_config
($vmid, $conf);
2489 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2495 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2498 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2501 __PACKAGE__-
>register_method({
2502 name
=> 'migrate_vm',
2503 path
=> '{vmid}/migrate',
2507 description
=> "Migrate virtual machine. Creates a new migration task.",
2509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2512 additionalProperties
=> 0,
2514 node
=> get_standard_option
('pve-node'),
2515 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2516 target
=> get_standard_option
('pve-node', {
2517 description
=> "Target node.",
2518 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2522 description
=> "Use online/live migration.",
2527 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2534 description
=> "the task ID.",
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2541 my $authuser = $rpcenv->get_user();
2543 my $target = extract_param
($param, 'target');
2545 my $localnode = PVE
::INotify
::nodename
();
2546 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2548 PVE
::Cluster
::check_cfs_quorum
();
2550 PVE
::Cluster
::check_node_exists
($target);
2552 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2554 my $vmid = extract_param
($param, 'vmid');
2556 raise_param_exc
({ force
=> "Only root may use this option." })
2557 if $param->{force
} && $authuser ne 'root@pam';
2560 my $conf = PVE
::QemuServer
::load_config
($vmid);
2562 # try to detect errors early
2564 PVE
::QemuServer
::check_lock
($conf);
2566 if (PVE
::QemuServer
::check_running
($vmid)) {
2567 die "cant migrate running VM without --online\n"
2568 if !$param->{online
};
2571 my $storecfg = PVE
::Storage
::config
();
2572 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2574 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2579 my $service = "vm:$vmid";
2581 my $cmd = ['ha-manager', 'migrate', $service, $target];
2583 print "Executing HA migrate for VM $vmid to node $target\n";
2585 PVE
::Tools
::run_command
($cmd);
2590 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2597 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2600 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2605 __PACKAGE__-
>register_method({
2607 path
=> '{vmid}/monitor',
2611 description
=> "Execute Qemu monitor commands.",
2613 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2616 additionalProperties
=> 0,
2618 node
=> get_standard_option
('pve-node'),
2619 vmid
=> get_standard_option
('pve-vmid'),
2622 description
=> "The monitor command.",
2626 returns
=> { type
=> 'string'},
2630 my $vmid = $param->{vmid
};
2632 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2636 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2638 $res = "ERROR: $@" if $@;
2643 __PACKAGE__-
>register_method({
2644 name
=> 'resize_vm',
2645 path
=> '{vmid}/resize',
2649 description
=> "Extend volume size.",
2651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2654 additionalProperties
=> 0,
2656 node
=> get_standard_option
('pve-node'),
2657 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2658 skiplock
=> get_standard_option
('skiplock'),
2661 description
=> "The disk you want to resize.",
2662 enum
=> [PVE
::QemuServer
::disknames
()],
2666 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2667 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.",
2671 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2677 returns
=> { type
=> 'null'},
2681 my $rpcenv = PVE
::RPCEnvironment
::get
();
2683 my $authuser = $rpcenv->get_user();
2685 my $node = extract_param
($param, 'node');
2687 my $vmid = extract_param
($param, 'vmid');
2689 my $digest = extract_param
($param, 'digest');
2691 my $disk = extract_param
($param, 'disk');
2693 my $sizestr = extract_param
($param, 'size');
2695 my $skiplock = extract_param
($param, 'skiplock');
2696 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2697 if $skiplock && $authuser ne 'root@pam';
2699 my $storecfg = PVE
::Storage
::config
();
2701 my $updatefn = sub {
2703 my $conf = PVE
::QemuServer
::load_config
($vmid);
2705 die "checksum missmatch (file change by other user?)\n"
2706 if $digest && $digest ne $conf->{digest
};
2707 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2709 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2711 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2713 my (undef, undef, undef, undef, undef, undef, $format) =
2714 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2716 die "can't resize volume: $disk if snapshot exists\n"
2717 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2719 my $volid = $drive->{file
};
2721 die "disk '$disk' has no associated volume\n" if !$volid;
2723 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2725 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2727 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2729 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2731 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2732 my ($ext, $newsize, $unit) = ($1, $2, $4);
2735 $newsize = $newsize * 1024;
2736 } elsif ($unit eq 'M') {
2737 $newsize = $newsize * 1024 * 1024;
2738 } elsif ($unit eq 'G') {
2739 $newsize = $newsize * 1024 * 1024 * 1024;
2740 } elsif ($unit eq 'T') {
2741 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2744 $newsize += $size if $ext;
2745 $newsize = int($newsize);
2747 die "unable to skrink disk size\n" if $newsize < $size;
2749 return if $size == $newsize;
2751 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2753 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2755 $drive->{size
} = $newsize;
2756 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2758 PVE
::QemuServer
::write_config
($vmid, $conf);
2761 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2765 __PACKAGE__-
>register_method({
2766 name
=> 'snapshot_list',
2767 path
=> '{vmid}/snapshot',
2769 description
=> "List all snapshots.",
2771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2774 protected
=> 1, # qemu pid files are only readable by root
2776 additionalProperties
=> 0,
2778 vmid
=> get_standard_option
('pve-vmid'),
2779 node
=> get_standard_option
('pve-node'),
2788 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2793 my $vmid = $param->{vmid
};
2795 my $conf = PVE
::QemuServer
::load_config
($vmid);
2796 my $snaphash = $conf->{snapshots
} || {};
2800 foreach my $name (keys %$snaphash) {
2801 my $d = $snaphash->{$name};
2804 snaptime
=> $d->{snaptime
} || 0,
2805 vmstate
=> $d->{vmstate
} ?
1 : 0,
2806 description
=> $d->{description
} || '',
2808 $item->{parent
} = $d->{parent
} if $d->{parent
};
2809 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2813 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2814 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2815 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2817 push @$res, $current;
2822 __PACKAGE__-
>register_method({
2824 path
=> '{vmid}/snapshot',
2828 description
=> "Snapshot a VM.",
2830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2833 additionalProperties
=> 0,
2835 node
=> get_standard_option
('pve-node'),
2836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2837 snapname
=> get_standard_option
('pve-snapshot-name'),
2841 description
=> "Save the vmstate",
2846 description
=> "A textual description or comment.",
2852 description
=> "the task ID.",
2857 my $rpcenv = PVE
::RPCEnvironment
::get
();
2859 my $authuser = $rpcenv->get_user();
2861 my $node = extract_param
($param, 'node');
2863 my $vmid = extract_param
($param, 'vmid');
2865 my $snapname = extract_param
($param, 'snapname');
2867 die "unable to use snapshot name 'current' (reserved name)\n"
2868 if $snapname eq 'current';
2871 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2872 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2873 $param->{description
});
2876 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2879 __PACKAGE__-
>register_method({
2880 name
=> 'snapshot_cmd_idx',
2881 path
=> '{vmid}/snapshot/{snapname}',
2888 additionalProperties
=> 0,
2890 vmid
=> get_standard_option
('pve-vmid'),
2891 node
=> get_standard_option
('pve-node'),
2892 snapname
=> get_standard_option
('pve-snapshot-name'),
2901 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2908 push @$res, { cmd
=> 'rollback' };
2909 push @$res, { cmd
=> 'config' };
2914 __PACKAGE__-
>register_method({
2915 name
=> 'update_snapshot_config',
2916 path
=> '{vmid}/snapshot/{snapname}/config',
2920 description
=> "Update snapshot metadata.",
2922 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2925 additionalProperties
=> 0,
2927 node
=> get_standard_option
('pve-node'),
2928 vmid
=> get_standard_option
('pve-vmid'),
2929 snapname
=> get_standard_option
('pve-snapshot-name'),
2933 description
=> "A textual description or comment.",
2937 returns
=> { type
=> 'null' },
2941 my $rpcenv = PVE
::RPCEnvironment
::get
();
2943 my $authuser = $rpcenv->get_user();
2945 my $vmid = extract_param
($param, 'vmid');
2947 my $snapname = extract_param
($param, 'snapname');
2949 return undef if !defined($param->{description
});
2951 my $updatefn = sub {
2953 my $conf = PVE
::QemuServer
::load_config
($vmid);
2955 PVE
::QemuServer
::check_lock
($conf);
2957 my $snap = $conf->{snapshots
}->{$snapname};
2959 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2961 $snap->{description
} = $param->{description
} if defined($param->{description
});
2963 PVE
::QemuServer
::write_config
($vmid, $conf);
2966 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2971 __PACKAGE__-
>register_method({
2972 name
=> 'get_snapshot_config',
2973 path
=> '{vmid}/snapshot/{snapname}/config',
2976 description
=> "Get snapshot configuration",
2978 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2981 additionalProperties
=> 0,
2983 node
=> get_standard_option
('pve-node'),
2984 vmid
=> get_standard_option
('pve-vmid'),
2985 snapname
=> get_standard_option
('pve-snapshot-name'),
2988 returns
=> { type
=> "object" },
2992 my $rpcenv = PVE
::RPCEnvironment
::get
();
2994 my $authuser = $rpcenv->get_user();
2996 my $vmid = extract_param
($param, 'vmid');
2998 my $snapname = extract_param
($param, 'snapname');
3000 my $conf = PVE
::QemuServer
::load_config
($vmid);
3002 my $snap = $conf->{snapshots
}->{$snapname};
3004 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3009 __PACKAGE__-
>register_method({
3011 path
=> '{vmid}/snapshot/{snapname}/rollback',
3015 description
=> "Rollback VM state to specified snapshot.",
3017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3020 additionalProperties
=> 0,
3022 node
=> get_standard_option
('pve-node'),
3023 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3024 snapname
=> get_standard_option
('pve-snapshot-name'),
3029 description
=> "the task ID.",
3034 my $rpcenv = PVE
::RPCEnvironment
::get
();
3036 my $authuser = $rpcenv->get_user();
3038 my $node = extract_param
($param, 'node');
3040 my $vmid = extract_param
($param, 'vmid');
3042 my $snapname = extract_param
($param, 'snapname');
3045 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3046 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3049 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3052 __PACKAGE__-
>register_method({
3053 name
=> 'delsnapshot',
3054 path
=> '{vmid}/snapshot/{snapname}',
3058 description
=> "Delete a VM snapshot.",
3060 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3063 additionalProperties
=> 0,
3065 node
=> get_standard_option
('pve-node'),
3066 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3067 snapname
=> get_standard_option
('pve-snapshot-name'),
3071 description
=> "For removal from config file, even if removing disk snapshots fails.",
3077 description
=> "the task ID.",
3082 my $rpcenv = PVE
::RPCEnvironment
::get
();
3084 my $authuser = $rpcenv->get_user();
3086 my $node = extract_param
($param, 'node');
3088 my $vmid = extract_param
($param, 'vmid');
3090 my $snapname = extract_param
($param, 'snapname');
3093 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3094 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3097 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3100 __PACKAGE__-
>register_method({
3102 path
=> '{vmid}/template',
3106 description
=> "Create a Template.",
3108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3109 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3112 additionalProperties
=> 0,
3114 node
=> get_standard_option
('pve-node'),
3115 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3119 description
=> "If you want to convert only 1 disk to base image.",
3120 enum
=> [PVE
::QemuServer
::disknames
()],
3125 returns
=> { type
=> 'null'},
3129 my $rpcenv = PVE
::RPCEnvironment
::get
();
3131 my $authuser = $rpcenv->get_user();
3133 my $node = extract_param
($param, 'node');
3135 my $vmid = extract_param
($param, 'vmid');
3137 my $disk = extract_param
($param, 'disk');
3139 my $updatefn = sub {
3141 my $conf = PVE
::QemuServer
::load_config
($vmid);
3143 PVE
::QemuServer
::check_lock
($conf);
3145 die "unable to create template, because VM contains snapshots\n"
3146 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3148 die "you can't convert a template to a template\n"
3149 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3151 die "you can't convert a VM to template if VM is running\n"
3152 if PVE
::QemuServer
::check_running
($vmid);
3155 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3158 $conf->{template
} = 1;
3159 PVE
::QemuServer
::write_config
($vmid, $conf);
3161 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3164 PVE
::QemuServer
::lock_config
($vmid, $updatefn);