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
;
26 use Data
::Dumper
; # fixme: remove
28 use base
qw(PVE::RESTHandler);
30 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.";
32 my $resolve_cdrom_alias = sub {
35 if (my $value = $param->{cdrom
}) {
36 $value .= ",media=cdrom" if $value !~ m/media=/;
37 $param->{ide2
} = $value;
38 delete $param->{cdrom
};
42 my $check_storage_access = sub {
43 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
45 PVE
::QemuServer
::foreach_drive
($settings, sub {
46 my ($ds, $drive) = @_;
48 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
50 my $volid = $drive->{file
};
52 if (!$volid || $volid eq 'none') {
54 } elsif ($isCDROM && ($volid eq 'cdrom')) {
55 $rpcenv->check($authuser, "/", ['Sys.Console']);
56 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
57 my ($storeid, $size) = ($2 || $default_storage, $3);
58 die "no storage ID specified (and no default storage)\n" if !$storeid;
59 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
61 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
66 my $check_storage_access_clone = sub {
67 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
71 PVE
::QemuServer
::foreach_drive
($conf, sub {
72 my ($ds, $drive) = @_;
74 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
76 my $volid = $drive->{file
};
78 return if !$volid || $volid eq 'none';
81 if ($volid eq 'cdrom') {
82 $rpcenv->check($authuser, "/", ['Sys.Console']);
84 # we simply allow access
85 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
87 $sharedvm = 0 if !$scfg->{shared
};
91 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
92 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
93 $sharedvm = 0 if !$scfg->{shared
};
95 $sid = $storage if $storage;
96 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
103 # Note: $pool is only needed when creating a VM, because pool permissions
104 # are automatically inherited if VM already exists inside a pool.
105 my $create_disks = sub {
106 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
111 PVE
::QemuServer
::foreach_drive
($settings, sub {
112 my ($ds, $disk) = @_;
114 my $volid = $disk->{file
};
116 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
117 delete $disk->{size
};
118 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
120 my ($storeid, $size) = ($2 || $default_storage, $3);
121 die "no storage ID specified (and no default storage)\n" if !$storeid;
122 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
123 my $fmt = $disk->{format
} || $defformat;
124 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
125 $fmt, undef, $size*1024*1024);
126 $disk->{file
} = $volid;
127 $disk->{size
} = $size*1024*1024*1024;
128 push @$vollist, $volid;
129 delete $disk->{format
}; # no longer needed
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
135 my $volid_is_new = 1;
138 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
139 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
144 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
146 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
148 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
150 die "volume $volid does not exists\n" if !$size;
152 $disk->{size
} = $size;
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
159 # free allocated images on error
161 syslog
('err', "VM $vmid creating disks failed");
162 foreach my $volid (@$vollist) {
163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
169 # modify vm config if everything went well
170 foreach my $ds (keys %$res) {
171 $conf->{$ds} = $res->{$ds};
177 my $check_vm_modify_config_perm = sub {
178 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
180 return 1 if $authuser eq 'root@pam';
182 foreach my $opt (@$key_list) {
183 # disk checks need to be done somewhere else
184 next if PVE
::QemuServer
::valid_drivename
($opt);
186 if ($opt eq 'sockets' || $opt eq 'cores' ||
187 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
188 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
190 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
192 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
193 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
194 } elsif ($opt eq 'args' || $opt eq 'lock') {
195 die "only root can set '$opt' config\n";
196 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
197 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
199 } elsif ($opt =~ m/^net\d+$/) {
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
202 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
209 __PACKAGE__-
>register_method({
213 description
=> "Virtual machine index (per node).",
215 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
219 protected
=> 1, # qemu pid files are only readable by root
221 additionalProperties
=> 0,
223 node
=> get_standard_option
('pve-node'),
232 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
237 my $rpcenv = PVE
::RPCEnvironment
::get
();
238 my $authuser = $rpcenv->get_user();
240 my $vmstatus = PVE
::QemuServer
::vmstatus
();
243 foreach my $vmid (keys %$vmstatus) {
244 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
246 my $data = $vmstatus->{$vmid};
247 $data->{vmid
} = int($vmid);
256 __PACKAGE__-
>register_method({
260 description
=> "Create or restore a virtual machine.",
262 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
263 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
264 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
265 user
=> 'all', # check inside
270 additionalProperties
=> 0,
271 properties
=> PVE
::QemuServer
::json_config_properties
(
273 node
=> get_standard_option
('pve-node'),
274 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
276 description
=> "The backup file.",
280 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
282 storage
=> get_standard_option
('pve-storage-id', {
283 description
=> "Default storage.",
289 description
=> "Allow to overwrite existing VM.",
290 requires
=> 'archive',
295 description
=> "Assign a unique random ethernet address.",
296 requires
=> 'archive',
300 type
=> 'string', format
=> 'pve-poolid',
301 description
=> "Add the VM to the specified pool.",
311 my $rpcenv = PVE
::RPCEnvironment
::get
();
313 my $authuser = $rpcenv->get_user();
315 my $node = extract_param
($param, 'node');
317 my $vmid = extract_param
($param, 'vmid');
319 my $archive = extract_param
($param, 'archive');
321 my $storage = extract_param
($param, 'storage');
323 my $force = extract_param
($param, 'force');
325 my $unique = extract_param
($param, 'unique');
327 my $pool = extract_param
($param, 'pool');
329 my $filename = PVE
::QemuServer
::config_file
($vmid);
331 my $storecfg = PVE
::Storage
::config
();
333 PVE
::Cluster
::check_cfs_quorum
();
335 if (defined($pool)) {
336 $rpcenv->check_pool_exist($pool);
339 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
340 if defined($storage);
342 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
344 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
346 } elsif ($archive && $force && (-f
$filename) &&
347 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
348 # OK: user has VM.Backup permissions, and want to restore an existing VM
354 &$resolve_cdrom_alias($param);
356 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
358 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
360 foreach my $opt (keys %$param) {
361 if (PVE
::QemuServer
::valid_drivename
($opt)) {
362 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
363 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
365 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
366 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
370 PVE
::QemuServer
::add_random_macs
($param);
372 my $keystr = join(' ', keys %$param);
373 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
375 if ($archive eq '-') {
376 die "pipe requires cli environment\n"
377 if $rpcenv->{type
} ne 'cli';
379 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
380 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
384 my $restorefn = sub {
386 # fixme: this test does not work if VM exists on other node!
388 die "unable to restore vm $vmid: config file already exists\n"
391 die "unable to restore vm $vmid: vm is running\n"
392 if PVE
::QemuServer
::check_running
($vmid);
396 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
399 unique
=> $unique });
401 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
404 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
410 die "unable to create vm $vmid: config file already exists\n"
421 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
423 # try to be smart about bootdisk
424 my @disks = PVE
::QemuServer
::disknames
();
426 foreach my $ds (reverse @disks) {
427 next if !$conf->{$ds};
428 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
429 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
433 if (!$conf->{bootdisk
} && $firstdisk) {
434 $conf->{bootdisk
} = $firstdisk;
437 # auto generate uuid if user did not specify smbios1 option
438 if (!$conf->{smbios1
}) {
439 my ($uuid, $uuid_str);
440 UUID
::generate
($uuid);
441 UUID
::unparse
($uuid, $uuid_str);
442 $conf->{smbios1
} = "uuid=$uuid_str";
445 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
451 foreach my $volid (@$vollist) {
452 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
455 die "create failed - $err";
458 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
461 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
464 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
467 __PACKAGE__-
>register_method({
472 description
=> "Directory index",
477 additionalProperties
=> 0,
479 node
=> get_standard_option
('pve-node'),
480 vmid
=> get_standard_option
('pve-vmid'),
488 subdir
=> { type
=> 'string' },
491 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
497 { subdir
=> 'config' },
498 { subdir
=> 'pending' },
499 { subdir
=> 'status' },
500 { subdir
=> 'unlink' },
501 { subdir
=> 'vncproxy' },
502 { subdir
=> 'migrate' },
503 { subdir
=> 'resize' },
504 { subdir
=> 'move' },
506 { subdir
=> 'rrddata' },
507 { subdir
=> 'monitor' },
508 { subdir
=> 'snapshot' },
509 { subdir
=> 'spiceproxy' },
510 { subdir
=> 'sendkey' },
511 { subdir
=> 'firewall' },
517 __PACKAGE__-
>register_method ({
518 subclass
=> "PVE::API2::Firewall::VM",
519 path
=> '{vmid}/firewall',
522 __PACKAGE__-
>register_method({
524 path
=> '{vmid}/rrd',
526 protected
=> 1, # fixme: can we avoid that?
528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
530 description
=> "Read VM RRD statistics (returns PNG)",
532 additionalProperties
=> 0,
534 node
=> get_standard_option
('pve-node'),
535 vmid
=> get_standard_option
('pve-vmid'),
537 description
=> "Specify the time frame you are interested in.",
539 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
542 description
=> "The list of datasources you want to display.",
543 type
=> 'string', format
=> 'pve-configid-list',
546 description
=> "The RRD consolidation function",
548 enum
=> [ 'AVERAGE', 'MAX' ],
556 filename
=> { type
=> 'string' },
562 return PVE
::Cluster
::create_rrd_graph
(
563 "pve2-vm/$param->{vmid}", $param->{timeframe
},
564 $param->{ds
}, $param->{cf
});
568 __PACKAGE__-
>register_method({
570 path
=> '{vmid}/rrddata',
572 protected
=> 1, # fixme: can we avoid that?
574 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
576 description
=> "Read VM RRD statistics",
578 additionalProperties
=> 0,
580 node
=> get_standard_option
('pve-node'),
581 vmid
=> get_standard_option
('pve-vmid'),
583 description
=> "Specify the time frame you are interested in.",
585 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
588 description
=> "The RRD consolidation function",
590 enum
=> [ 'AVERAGE', 'MAX' ],
605 return PVE
::Cluster
::create_rrd_data
(
606 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
610 __PACKAGE__-
>register_method({
612 path
=> '{vmid}/config',
615 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
617 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
620 additionalProperties
=> 0,
622 node
=> get_standard_option
('pve-node'),
623 vmid
=> get_standard_option
('pve-vmid'),
625 description
=> "Get current values (instead of pending values).",
637 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
644 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
646 delete $conf->{snapshots
};
648 if (!$param->{current
}) {
649 foreach my $opt (keys %{$conf->{pending
}}) {
650 next if $opt eq 'delete';
651 my $value = $conf->{pending
}->{$opt};
652 next if ref($value); # just to be sure
653 $conf->{$opt} = $value;
655 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
656 foreach my $opt (keys %$pending_delete_hash) {
657 delete $conf->{$opt} if $conf->{$opt};
661 delete $conf->{pending
};
666 __PACKAGE__-
>register_method({
667 name
=> 'vm_pending',
668 path
=> '{vmid}/pending',
671 description
=> "Get virtual machine configuration, including pending changes.",
673 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
676 additionalProperties
=> 0,
678 node
=> get_standard_option
('pve-node'),
679 vmid
=> get_standard_option
('pve-vmid'),
688 description
=> "Configuration option name.",
692 description
=> "Current value.",
697 description
=> "Pending value.",
702 description
=> "Indicates a pending delete request if present and not 0. " .
703 "The value 2 indicates a force-delete request.",
715 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
717 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
721 foreach my $opt (keys %$conf) {
722 next if ref($conf->{$opt});
723 my $item = { key
=> $opt };
724 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
725 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
726 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
730 foreach my $opt (keys %{$conf->{pending
}}) {
731 next if $opt eq 'delete';
732 next if ref($conf->{pending
}->{$opt}); # just to be sure
733 next if defined($conf->{$opt});
734 my $item = { key
=> $opt };
735 $item->{pending
} = $conf->{pending
}->{$opt};
739 while (my ($opt, $force) = each %$pending_delete_hash) {
740 next if $conf->{pending
}->{$opt}; # just to be sure
741 next if $conf->{$opt};
742 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
749 # POST/PUT {vmid}/config implementation
751 # The original API used PUT (idempotent) an we assumed that all operations
752 # are fast. But it turned out that almost any configuration change can
753 # involve hot-plug actions, or disk alloc/free. Such actions can take long
754 # time to complete and have side effects (not idempotent).
756 # The new implementation uses POST and forks a worker process. We added
757 # a new option 'background_delay'. If specified we wait up to
758 # 'background_delay' second for the worker task to complete. It returns null
759 # if the task is finished within that time, else we return the UPID.
761 my $update_vm_api = sub {
762 my ($param, $sync) = @_;
764 my $rpcenv = PVE
::RPCEnvironment
::get
();
766 my $authuser = $rpcenv->get_user();
768 my $node = extract_param
($param, 'node');
770 my $vmid = extract_param
($param, 'vmid');
772 my $digest = extract_param
($param, 'digest');
774 my $background_delay = extract_param
($param, 'background_delay');
776 my @paramarr = (); # used for log message
777 foreach my $key (keys %$param) {
778 push @paramarr, "-$key", $param->{$key};
781 my $skiplock = extract_param
($param, 'skiplock');
782 raise_param_exc
({ skiplock
=> "Only root may use this option." })
783 if $skiplock && $authuser ne 'root@pam';
785 my $delete_str = extract_param
($param, 'delete');
787 my $revert_str = extract_param
($param, 'revert');
789 my $force = extract_param
($param, 'force');
791 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
793 my $storecfg = PVE
::Storage
::config
();
795 my $defaults = PVE
::QemuServer
::load_defaults
();
797 &$resolve_cdrom_alias($param);
799 # now try to verify all parameters
802 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
803 if (!PVE
::QemuServer
::option_exists
($opt)) {
804 raise_param_exc
({ revert
=> "unknown option '$opt'" });
807 raise_param_exc
({ delete => "you can't use '-$opt' and " .
808 "-revert $opt' at the same time" })
809 if defined($param->{$opt});
815 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
816 $opt = 'ide2' if $opt eq 'cdrom';
818 raise_param_exc
({ delete => "you can't use '-$opt' and " .
819 "-delete $opt' at the same time" })
820 if defined($param->{$opt});
822 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
823 "-revert $opt' at the same time" })
826 if (!PVE
::QemuServer
::option_exists
($opt)) {
827 raise_param_exc
({ delete => "unknown option '$opt'" });
833 foreach my $opt (keys %$param) {
834 if (PVE
::QemuServer
::valid_drivename
($opt)) {
836 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
837 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
838 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
839 } elsif ($opt =~ m/^net(\d+)$/) {
841 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
842 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
846 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
848 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
850 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
854 my $conf = PVE
::QemuServer
::load_config
($vmid);
856 die "checksum missmatch (file change by other user?)\n"
857 if $digest && $digest ne $conf->{digest
};
859 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
861 foreach my $opt (keys %$revert) {
862 if (defined($conf->{$opt})) {
863 $param->{$opt} = $conf->{$opt};
864 } elsif (defined($conf->{pending
}->{$opt})) {
869 if ($param->{memory
} || defined($param->{balloon
})) {
870 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
871 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
873 die "balloon value too large (must be smaller than assigned memory)\n"
874 if $balloon && $balloon > $maxmem;
877 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
881 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
883 # write updates to pending section
885 my $modified = {}; # record what $option we modify
887 foreach my $opt (@delete) {
888 $modified->{$opt} = 1;
889 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
890 if ($opt =~ m/^unused/) {
891 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
892 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
893 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
894 delete $conf->{$opt};
895 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
897 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
898 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
899 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
900 if defined($conf->{pending
}->{$opt});
901 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
902 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
904 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
905 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
909 foreach my $opt (keys %$param) { # add/change
910 $modified->{$opt} = 1;
911 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
912 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
914 if (PVE
::QemuServer
::valid_drivename
($opt)) {
915 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
916 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
917 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
919 $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});
924 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
926 $conf->{pending
}->{$opt} = $param->{$opt};
928 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
929 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
932 # remove pending changes when nothing changed
933 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
934 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
935 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
937 return if !scalar(keys %{$conf->{pending
}});
939 my $running = PVE
::QemuServer
::check_running
($vmid);
941 # apply pending changes
943 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
947 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
948 raise_param_exc
($errors) if scalar(keys %$errors);
950 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
960 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
962 if ($background_delay) {
964 # Note: It would be better to do that in the Event based HTTPServer
965 # to avoid blocking call to sleep.
967 my $end_time = time() + $background_delay;
969 my $task = PVE
::Tools
::upid_decode
($upid);
972 while (time() < $end_time) {
973 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
975 sleep(1); # this gets interrupted when child process ends
979 my $status = PVE
::Tools
::upid_read_status
($upid);
980 return undef if $status eq 'OK';
989 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
992 my $vm_config_perm_list = [
1002 __PACKAGE__-
>register_method({
1003 name
=> 'update_vm_async',
1004 path
=> '{vmid}/config',
1008 description
=> "Set virtual machine options (asynchrounous API).",
1010 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1013 additionalProperties
=> 0,
1014 properties
=> PVE
::QemuServer
::json_config_properties
(
1016 node
=> get_standard_option
('pve-node'),
1017 vmid
=> get_standard_option
('pve-vmid'),
1018 skiplock
=> get_standard_option
('skiplock'),
1020 type
=> 'string', format
=> 'pve-configid-list',
1021 description
=> "A list of settings you want to delete.",
1025 type
=> 'string', format
=> 'pve-configid-list',
1026 description
=> "Revert a pending change.",
1031 description
=> $opt_force_description,
1033 requires
=> 'delete',
1037 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1041 background_delay
=> {
1043 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1054 code
=> $update_vm_api,
1057 __PACKAGE__-
>register_method({
1058 name
=> 'update_vm',
1059 path
=> '{vmid}/config',
1063 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1065 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1068 additionalProperties
=> 0,
1069 properties
=> PVE
::QemuServer
::json_config_properties
(
1071 node
=> get_standard_option
('pve-node'),
1072 vmid
=> get_standard_option
('pve-vmid'),
1073 skiplock
=> get_standard_option
('skiplock'),
1075 type
=> 'string', format
=> 'pve-configid-list',
1076 description
=> "A list of settings you want to delete.",
1080 type
=> 'string', format
=> 'pve-configid-list',
1081 description
=> "Revert a pending change.",
1086 description
=> $opt_force_description,
1088 requires
=> 'delete',
1092 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1098 returns
=> { type
=> 'null' },
1101 &$update_vm_api($param, 1);
1107 __PACKAGE__-
>register_method({
1108 name
=> 'destroy_vm',
1113 description
=> "Destroy the vm (also delete all used/owned volumes).",
1115 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1118 additionalProperties
=> 0,
1120 node
=> get_standard_option
('pve-node'),
1121 vmid
=> get_standard_option
('pve-vmid'),
1122 skiplock
=> get_standard_option
('skiplock'),
1131 my $rpcenv = PVE
::RPCEnvironment
::get
();
1133 my $authuser = $rpcenv->get_user();
1135 my $vmid = $param->{vmid
};
1137 my $skiplock = $param->{skiplock
};
1138 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1139 if $skiplock && $authuser ne 'root@pam';
1142 my $conf = PVE
::QemuServer
::load_config
($vmid);
1144 my $storecfg = PVE
::Storage
::config
();
1146 die "can't remove VM $vmid - protection mode enabled\n"
1147 if ($conf->{protection
} == 1);
1149 die "unable to remove VM $vmid - used in HA resources\n"
1150 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1155 syslog
('info', "destroy VM $vmid: $upid\n");
1157 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1159 PVE
::AccessControl
::remove_vm_access
($vmid);
1161 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1164 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1167 __PACKAGE__-
>register_method({
1169 path
=> '{vmid}/unlink',
1173 description
=> "Unlink/delete disk images.",
1175 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1178 additionalProperties
=> 0,
1180 node
=> get_standard_option
('pve-node'),
1181 vmid
=> get_standard_option
('pve-vmid'),
1183 type
=> 'string', format
=> 'pve-configid-list',
1184 description
=> "A list of disk IDs you want to delete.",
1188 description
=> $opt_force_description,
1193 returns
=> { type
=> 'null'},
1197 $param->{delete} = extract_param
($param, 'idlist');
1199 __PACKAGE__-
>update_vm($param);
1206 __PACKAGE__-
>register_method({
1208 path
=> '{vmid}/vncproxy',
1212 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1214 description
=> "Creates a TCP VNC proxy connections.",
1216 additionalProperties
=> 0,
1218 node
=> get_standard_option
('pve-node'),
1219 vmid
=> get_standard_option
('pve-vmid'),
1223 description
=> "starts websockify instead of vncproxy",
1228 additionalProperties
=> 0,
1230 user
=> { type
=> 'string' },
1231 ticket
=> { type
=> 'string' },
1232 cert
=> { type
=> 'string' },
1233 port
=> { type
=> 'integer' },
1234 upid
=> { type
=> 'string' },
1240 my $rpcenv = PVE
::RPCEnvironment
::get
();
1242 my $authuser = $rpcenv->get_user();
1244 my $vmid = $param->{vmid
};
1245 my $node = $param->{node
};
1246 my $websocket = $param->{websocket
};
1248 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1250 my $authpath = "/vms/$vmid";
1252 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1254 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1257 my ($remip, $family);
1260 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1261 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1262 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1263 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1265 $family = PVE
::Tools
::get_host_address_family
($node);
1268 my $port = PVE
::Tools
::next_vnc_port
($family);
1275 syslog
('info', "starting vnc proxy $upid\n");
1279 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1281 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1283 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1284 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1285 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1286 '-timeout', $timeout, '-authpath', $authpath,
1287 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1290 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1292 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1294 my $qmstr = join(' ', @$qmcmd);
1296 # also redirect stderr (else we get RFB protocol errors)
1297 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1300 PVE
::Tools
::run_command
($cmd);
1305 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1307 PVE
::Tools
::wait_for_vnc_port
($port);
1318 __PACKAGE__-
>register_method({
1319 name
=> 'vncwebsocket',
1320 path
=> '{vmid}/vncwebsocket',
1323 description
=> "You also need to pass a valid ticket (vncticket).",
1324 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1326 description
=> "Opens a weksocket for VNC traffic.",
1328 additionalProperties
=> 0,
1330 node
=> get_standard_option
('pve-node'),
1331 vmid
=> get_standard_option
('pve-vmid'),
1333 description
=> "Ticket from previous call to vncproxy.",
1338 description
=> "Port number returned by previous vncproxy call.",
1348 port
=> { type
=> 'string' },
1354 my $rpcenv = PVE
::RPCEnvironment
::get
();
1356 my $authuser = $rpcenv->get_user();
1358 my $vmid = $param->{vmid
};
1359 my $node = $param->{node
};
1361 my $authpath = "/vms/$vmid";
1363 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1365 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1367 # Note: VNC ports are acessible from outside, so we do not gain any
1368 # security if we verify that $param->{port} belongs to VM $vmid. This
1369 # check is done by verifying the VNC ticket (inside VNC protocol).
1371 my $port = $param->{port
};
1373 return { port
=> $port };
1376 __PACKAGE__-
>register_method({
1377 name
=> 'spiceproxy',
1378 path
=> '{vmid}/spiceproxy',
1383 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1385 description
=> "Returns a SPICE configuration to connect to the VM.",
1387 additionalProperties
=> 0,
1389 node
=> get_standard_option
('pve-node'),
1390 vmid
=> get_standard_option
('pve-vmid'),
1391 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1394 returns
=> get_standard_option
('remote-viewer-config'),
1398 my $rpcenv = PVE
::RPCEnvironment
::get
();
1400 my $authuser = $rpcenv->get_user();
1402 my $vmid = $param->{vmid
};
1403 my $node = $param->{node
};
1404 my $proxy = $param->{proxy
};
1406 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1407 my $title = "VM $vmid";
1408 $title .= " - ". $conf->{name
} if $conf->{name
};
1410 my $port = PVE
::QemuServer
::spice_port
($vmid);
1412 my ($ticket, undef, $remote_viewer_config) =
1413 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1415 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1416 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1418 return $remote_viewer_config;
1421 __PACKAGE__-
>register_method({
1423 path
=> '{vmid}/status',
1426 description
=> "Directory index",
1431 additionalProperties
=> 0,
1433 node
=> get_standard_option
('pve-node'),
1434 vmid
=> get_standard_option
('pve-vmid'),
1442 subdir
=> { type
=> 'string' },
1445 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1451 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1454 { subdir
=> 'current' },
1455 { subdir
=> 'start' },
1456 { subdir
=> 'stop' },
1462 __PACKAGE__-
>register_method({
1463 name
=> 'vm_status',
1464 path
=> '{vmid}/status/current',
1467 protected
=> 1, # qemu pid files are only readable by root
1468 description
=> "Get virtual machine status.",
1470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1473 additionalProperties
=> 0,
1475 node
=> get_standard_option
('pve-node'),
1476 vmid
=> get_standard_option
('pve-vmid'),
1479 returns
=> { type
=> 'object' },
1484 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1486 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1487 my $status = $vmstatus->{$param->{vmid
}};
1489 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1491 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1496 __PACKAGE__-
>register_method({
1498 path
=> '{vmid}/status/start',
1502 description
=> "Start virtual machine.",
1504 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1507 additionalProperties
=> 0,
1509 node
=> get_standard_option
('pve-node'),
1510 vmid
=> get_standard_option
('pve-vmid'),
1511 skiplock
=> get_standard_option
('skiplock'),
1512 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1513 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1514 machine
=> get_standard_option
('pve-qm-machine'),
1523 my $rpcenv = PVE
::RPCEnvironment
::get
();
1525 my $authuser = $rpcenv->get_user();
1527 my $node = extract_param
($param, 'node');
1529 my $vmid = extract_param
($param, 'vmid');
1531 my $machine = extract_param
($param, 'machine');
1533 my $stateuri = extract_param
($param, 'stateuri');
1534 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1535 if $stateuri && $authuser ne 'root@pam';
1537 my $skiplock = extract_param
($param, 'skiplock');
1538 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1539 if $skiplock && $authuser ne 'root@pam';
1541 my $migratedfrom = extract_param
($param, 'migratedfrom');
1542 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1543 if $migratedfrom && $authuser ne 'root@pam';
1545 # read spice ticket from STDIN
1547 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1548 if (defined(my $line = <>)) {
1550 $spice_ticket = $line;
1554 my $storecfg = PVE
::Storage
::config
();
1556 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1557 $rpcenv->{type
} ne 'ha') {
1562 my $service = "vm:$vmid";
1564 my $cmd = ['ha-manager', 'enable', $service];
1566 print "Executing HA start for VM $vmid\n";
1568 PVE
::Tools
::run_command
($cmd);
1573 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1580 syslog
('info', "start VM $vmid: $upid\n");
1582 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1583 $machine, $spice_ticket);
1588 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1592 __PACKAGE__-
>register_method({
1594 path
=> '{vmid}/status/stop',
1598 description
=> "Stop virtual machine.",
1600 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1603 additionalProperties
=> 0,
1605 node
=> get_standard_option
('pve-node'),
1606 vmid
=> get_standard_option
('pve-vmid'),
1607 skiplock
=> get_standard_option
('skiplock'),
1608 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1610 description
=> "Wait maximal timeout seconds.",
1616 description
=> "Do not decativate storage volumes.",
1629 my $rpcenv = PVE
::RPCEnvironment
::get
();
1631 my $authuser = $rpcenv->get_user();
1633 my $node = extract_param
($param, 'node');
1635 my $vmid = extract_param
($param, 'vmid');
1637 my $skiplock = extract_param
($param, 'skiplock');
1638 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1639 if $skiplock && $authuser ne 'root@pam';
1641 my $keepActive = extract_param
($param, 'keepActive');
1642 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1643 if $keepActive && $authuser ne 'root@pam';
1645 my $migratedfrom = extract_param
($param, 'migratedfrom');
1646 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1647 if $migratedfrom && $authuser ne 'root@pam';
1650 my $storecfg = PVE
::Storage
::config
();
1652 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1657 my $service = "vm:$vmid";
1659 my $cmd = ['ha-manager', 'disable', $service];
1661 print "Executing HA stop for VM $vmid\n";
1663 PVE
::Tools
::run_command
($cmd);
1668 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1674 syslog
('info', "stop VM $vmid: $upid\n");
1676 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1677 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1682 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1686 __PACKAGE__-
>register_method({
1688 path
=> '{vmid}/status/reset',
1692 description
=> "Reset virtual machine.",
1694 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1697 additionalProperties
=> 0,
1699 node
=> get_standard_option
('pve-node'),
1700 vmid
=> get_standard_option
('pve-vmid'),
1701 skiplock
=> get_standard_option
('skiplock'),
1710 my $rpcenv = PVE
::RPCEnvironment
::get
();
1712 my $authuser = $rpcenv->get_user();
1714 my $node = extract_param
($param, 'node');
1716 my $vmid = extract_param
($param, 'vmid');
1718 my $skiplock = extract_param
($param, 'skiplock');
1719 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1720 if $skiplock && $authuser ne 'root@pam';
1722 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1727 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1732 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1735 __PACKAGE__-
>register_method({
1736 name
=> 'vm_shutdown',
1737 path
=> '{vmid}/status/shutdown',
1741 description
=> "Shutdown virtual machine.",
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid'),
1750 skiplock
=> get_standard_option
('skiplock'),
1752 description
=> "Wait maximal timeout seconds.",
1758 description
=> "Make sure the VM stops.",
1764 description
=> "Do not decativate storage volumes.",
1777 my $rpcenv = PVE
::RPCEnvironment
::get
();
1779 my $authuser = $rpcenv->get_user();
1781 my $node = extract_param
($param, 'node');
1783 my $vmid = extract_param
($param, 'vmid');
1785 my $skiplock = extract_param
($param, 'skiplock');
1786 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1787 if $skiplock && $authuser ne 'root@pam';
1789 my $keepActive = extract_param
($param, 'keepActive');
1790 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1791 if $keepActive && $authuser ne 'root@pam';
1793 my $storecfg = PVE
::Storage
::config
();
1798 syslog
('info', "shutdown VM $vmid: $upid\n");
1800 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1801 1, $param->{forceStop
}, $keepActive);
1806 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1809 __PACKAGE__-
>register_method({
1810 name
=> 'vm_suspend',
1811 path
=> '{vmid}/status/suspend',
1815 description
=> "Suspend virtual machine.",
1817 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1820 additionalProperties
=> 0,
1822 node
=> get_standard_option
('pve-node'),
1823 vmid
=> get_standard_option
('pve-vmid'),
1824 skiplock
=> get_standard_option
('skiplock'),
1833 my $rpcenv = PVE
::RPCEnvironment
::get
();
1835 my $authuser = $rpcenv->get_user();
1837 my $node = extract_param
($param, 'node');
1839 my $vmid = extract_param
($param, 'vmid');
1841 my $skiplock = extract_param
($param, 'skiplock');
1842 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1843 if $skiplock && $authuser ne 'root@pam';
1845 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1850 syslog
('info', "suspend VM $vmid: $upid\n");
1852 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1857 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1860 __PACKAGE__-
>register_method({
1861 name
=> 'vm_resume',
1862 path
=> '{vmid}/status/resume',
1866 description
=> "Resume virtual machine.",
1868 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1871 additionalProperties
=> 0,
1873 node
=> get_standard_option
('pve-node'),
1874 vmid
=> get_standard_option
('pve-vmid'),
1875 skiplock
=> get_standard_option
('skiplock'),
1884 my $rpcenv = PVE
::RPCEnvironment
::get
();
1886 my $authuser = $rpcenv->get_user();
1888 my $node = extract_param
($param, 'node');
1890 my $vmid = extract_param
($param, 'vmid');
1892 my $skiplock = extract_param
($param, 'skiplock');
1893 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1894 if $skiplock && $authuser ne 'root@pam';
1896 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1901 syslog
('info', "resume VM $vmid: $upid\n");
1903 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1908 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1911 __PACKAGE__-
>register_method({
1912 name
=> 'vm_sendkey',
1913 path
=> '{vmid}/sendkey',
1917 description
=> "Send key event to virtual machine.",
1919 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1922 additionalProperties
=> 0,
1924 node
=> get_standard_option
('pve-node'),
1925 vmid
=> get_standard_option
('pve-vmid'),
1926 skiplock
=> get_standard_option
('skiplock'),
1928 description
=> "The key (qemu monitor encoding).",
1933 returns
=> { type
=> 'null'},
1937 my $rpcenv = PVE
::RPCEnvironment
::get
();
1939 my $authuser = $rpcenv->get_user();
1941 my $node = extract_param
($param, 'node');
1943 my $vmid = extract_param
($param, 'vmid');
1945 my $skiplock = extract_param
($param, 'skiplock');
1946 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1947 if $skiplock && $authuser ne 'root@pam';
1949 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1954 __PACKAGE__-
>register_method({
1955 name
=> 'vm_feature',
1956 path
=> '{vmid}/feature',
1960 description
=> "Check if feature for virtual machine is available.",
1962 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1965 additionalProperties
=> 0,
1967 node
=> get_standard_option
('pve-node'),
1968 vmid
=> get_standard_option
('pve-vmid'),
1970 description
=> "Feature to check.",
1972 enum
=> [ 'snapshot', 'clone', 'copy' ],
1974 snapname
=> get_standard_option
('pve-snapshot-name', {
1982 hasFeature
=> { type
=> 'boolean' },
1985 items
=> { type
=> 'string' },
1992 my $node = extract_param
($param, 'node');
1994 my $vmid = extract_param
($param, 'vmid');
1996 my $snapname = extract_param
($param, 'snapname');
1998 my $feature = extract_param
($param, 'feature');
2000 my $running = PVE
::QemuServer
::check_running
($vmid);
2002 my $conf = PVE
::QemuServer
::load_config
($vmid);
2005 my $snap = $conf->{snapshots
}->{$snapname};
2006 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2009 my $storecfg = PVE
::Storage
::config
();
2011 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2012 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2015 hasFeature
=> $hasFeature,
2016 nodes
=> [ keys %$nodelist ],
2020 __PACKAGE__-
>register_method({
2022 path
=> '{vmid}/clone',
2026 description
=> "Create a copy of virtual machine/template.",
2028 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2029 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2030 "'Datastore.AllocateSpace' on any used storage.",
2033 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2035 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2036 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2041 additionalProperties
=> 0,
2043 node
=> get_standard_option
('pve-node'),
2044 vmid
=> get_standard_option
('pve-vmid'),
2045 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2048 type
=> 'string', format
=> 'dns-name',
2049 description
=> "Set a name for the new VM.",
2054 description
=> "Description for the new VM.",
2058 type
=> 'string', format
=> 'pve-poolid',
2059 description
=> "Add the new VM to the specified pool.",
2061 snapname
=> get_standard_option
('pve-snapshot-name', {
2064 storage
=> get_standard_option
('pve-storage-id', {
2065 description
=> "Target storage for full clone.",
2070 description
=> "Target format for file storage.",
2074 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2079 description
=> "Create a full copy of all disk. This is always done when " .
2080 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2083 target
=> get_standard_option
('pve-node', {
2084 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2095 my $rpcenv = PVE
::RPCEnvironment
::get
();
2097 my $authuser = $rpcenv->get_user();
2099 my $node = extract_param
($param, 'node');
2101 my $vmid = extract_param
($param, 'vmid');
2103 my $newid = extract_param
($param, 'newid');
2105 my $pool = extract_param
($param, 'pool');
2107 if (defined($pool)) {
2108 $rpcenv->check_pool_exist($pool);
2111 my $snapname = extract_param
($param, 'snapname');
2113 my $storage = extract_param
($param, 'storage');
2115 my $format = extract_param
($param, 'format');
2117 my $target = extract_param
($param, 'target');
2119 my $localnode = PVE
::INotify
::nodename
();
2121 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2123 PVE
::Cluster
::check_node_exists
($target) if $target;
2125 my $storecfg = PVE
::Storage
::config
();
2128 # check if storage is enabled on local node
2129 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2131 # check if storage is available on target node
2132 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2133 # clone only works if target storage is shared
2134 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2135 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2139 PVE
::Cluster
::check_cfs_quorum
();
2141 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2143 # exclusive lock if VM is running - else shared lock is enough;
2144 my $shared_lock = $running ?
0 : 1;
2148 # do all tests after lock
2149 # we also try to do all tests before we fork the worker
2151 my $conf = PVE
::QemuServer
::load_config
($vmid);
2153 PVE
::QemuServer
::check_lock
($conf);
2155 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2157 die "unexpected state change\n" if $verify_running != $running;
2159 die "snapshot '$snapname' does not exist\n"
2160 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2162 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2164 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2166 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2168 my $conffile = PVE
::QemuServer
::config_file
($newid);
2170 die "unable to create VM $newid: config file already exists\n"
2173 my $newconf = { lock => 'clone' };
2177 foreach my $opt (keys %$oldconf) {
2178 my $value = $oldconf->{$opt};
2180 # do not copy snapshot related info
2181 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2182 $opt eq 'vmstate' || $opt eq 'snapstate';
2184 # no need to copy unused images, because VMID(owner) changes anyways
2185 next if $opt =~ m/^unused\d+$/;
2187 # always change MAC! address
2188 if ($opt =~ m/^net(\d+)$/) {
2189 my $net = PVE
::QemuServer
::parse_net
($value);
2190 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2191 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2192 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2193 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2194 die "unable to parse drive options for '$opt'\n" if !$drive;
2195 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2196 $newconf->{$opt} = $value; # simply copy configuration
2198 if ($param->{full
}) {
2199 die "Full clone feature is not available"
2200 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2203 # not full means clone instead of copy
2204 die "Linked clone feature is not available"
2205 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2207 $drives->{$opt} = $drive;
2208 push @$vollist, $drive->{file
};
2211 # copy everything else
2212 $newconf->{$opt} = $value;
2216 # auto generate a new uuid
2217 my ($uuid, $uuid_str);
2218 UUID
::generate
($uuid);
2219 UUID
::unparse
($uuid, $uuid_str);
2220 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2221 $smbios1->{uuid
} = $uuid_str;
2222 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2224 delete $newconf->{template
};
2226 if ($param->{name
}) {
2227 $newconf->{name
} = $param->{name
};
2229 if ($oldconf->{name
}) {
2230 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2232 $newconf->{name
} = "Copy-of-VM-$vmid";
2236 if ($param->{description
}) {
2237 $newconf->{description
} = $param->{description
};
2240 # create empty/temp config - this fails if VM already exists on other node
2241 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2246 my $newvollist = [];
2249 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2251 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2253 foreach my $opt (keys %$drives) {
2254 my $drive = $drives->{$opt};
2256 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2257 $newid, $storage, $format, $drive->{full
}, $newvollist);
2259 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2261 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2264 delete $newconf->{lock};
2265 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2268 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2269 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2271 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2272 die "Failed to move config to node '$target' - rename failed: $!\n"
2273 if !rename($conffile, $newconffile);
2276 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2281 sleep 1; # some storage like rbd need to wait before release volume - really?
2283 foreach my $volid (@$newvollist) {
2284 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2287 die "clone failed: $err";
2293 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2295 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2298 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2299 # Aquire exclusive lock lock for $newid
2300 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2305 __PACKAGE__-
>register_method({
2306 name
=> 'move_vm_disk',
2307 path
=> '{vmid}/move_disk',
2311 description
=> "Move volume to different storage.",
2313 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2314 "and 'Datastore.AllocateSpace' permissions on the storage.",
2317 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2318 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2322 additionalProperties
=> 0,
2324 node
=> get_standard_option
('pve-node'),
2325 vmid
=> get_standard_option
('pve-vmid'),
2328 description
=> "The disk you want to move.",
2329 enum
=> [ PVE
::QemuServer
::disknames
() ],
2331 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2334 description
=> "Target Format.",
2335 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2340 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2346 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2354 description
=> "the task ID.",
2359 my $rpcenv = PVE
::RPCEnvironment
::get
();
2361 my $authuser = $rpcenv->get_user();
2363 my $node = extract_param
($param, 'node');
2365 my $vmid = extract_param
($param, 'vmid');
2367 my $digest = extract_param
($param, 'digest');
2369 my $disk = extract_param
($param, 'disk');
2371 my $storeid = extract_param
($param, 'storage');
2373 my $format = extract_param
($param, 'format');
2375 my $storecfg = PVE
::Storage
::config
();
2377 my $updatefn = sub {
2379 my $conf = PVE
::QemuServer
::load_config
($vmid);
2381 die "checksum missmatch (file change by other user?)\n"
2382 if $digest && $digest ne $conf->{digest
};
2384 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2386 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2388 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2390 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2393 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2394 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2398 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2399 (!$format || !$oldfmt || $oldfmt eq $format);
2401 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2403 my $running = PVE
::QemuServer
::check_running
($vmid);
2405 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2409 my $newvollist = [];
2412 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2414 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2415 $vmid, $storeid, $format, 1, $newvollist);
2417 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2419 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2421 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2424 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2425 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2432 foreach my $volid (@$newvollist) {
2433 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2436 die "storage migration failed: $err";
2439 if ($param->{delete}) {
2440 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2441 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2442 if ($used_paths->{$path}){
2443 warn "volume $old_volid have snapshots. Can't delete it\n";
2444 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2445 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2447 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2453 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2456 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2459 __PACKAGE__-
>register_method({
2460 name
=> 'migrate_vm',
2461 path
=> '{vmid}/migrate',
2465 description
=> "Migrate virtual machine. Creates a new migration task.",
2467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2470 additionalProperties
=> 0,
2472 node
=> get_standard_option
('pve-node'),
2473 vmid
=> get_standard_option
('pve-vmid'),
2474 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2477 description
=> "Use online/live migration.",
2482 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2489 description
=> "the task ID.",
2494 my $rpcenv = PVE
::RPCEnvironment
::get
();
2496 my $authuser = $rpcenv->get_user();
2498 my $target = extract_param
($param, 'target');
2500 my $localnode = PVE
::INotify
::nodename
();
2501 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2503 PVE
::Cluster
::check_cfs_quorum
();
2505 PVE
::Cluster
::check_node_exists
($target);
2507 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2509 my $vmid = extract_param
($param, 'vmid');
2511 raise_param_exc
({ force
=> "Only root may use this option." })
2512 if $param->{force
} && $authuser ne 'root@pam';
2515 my $conf = PVE
::QemuServer
::load_config
($vmid);
2517 # try to detect errors early
2519 PVE
::QemuServer
::check_lock
($conf);
2521 if (PVE
::QemuServer
::check_running
($vmid)) {
2522 die "cant migrate running VM without --online\n"
2523 if !$param->{online
};
2526 my $storecfg = PVE
::Storage
::config
();
2527 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2529 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2534 my $service = "vm:$vmid";
2536 my $cmd = ['ha-manager', 'migrate', $service, $target];
2538 print "Executing HA migrate for VM $vmid to node $target\n";
2540 PVE
::Tools
::run_command
($cmd);
2545 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2552 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2555 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2560 __PACKAGE__-
>register_method({
2562 path
=> '{vmid}/monitor',
2566 description
=> "Execute Qemu monitor commands.",
2568 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2571 additionalProperties
=> 0,
2573 node
=> get_standard_option
('pve-node'),
2574 vmid
=> get_standard_option
('pve-vmid'),
2577 description
=> "The monitor command.",
2581 returns
=> { type
=> 'string'},
2585 my $vmid = $param->{vmid
};
2587 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2591 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2593 $res = "ERROR: $@" if $@;
2598 __PACKAGE__-
>register_method({
2599 name
=> 'resize_vm',
2600 path
=> '{vmid}/resize',
2604 description
=> "Extend volume size.",
2606 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2609 additionalProperties
=> 0,
2611 node
=> get_standard_option
('pve-node'),
2612 vmid
=> get_standard_option
('pve-vmid'),
2613 skiplock
=> get_standard_option
('skiplock'),
2616 description
=> "The disk you want to resize.",
2617 enum
=> [PVE
::QemuServer
::disknames
()],
2621 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2622 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.",
2626 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2632 returns
=> { type
=> 'null'},
2636 my $rpcenv = PVE
::RPCEnvironment
::get
();
2638 my $authuser = $rpcenv->get_user();
2640 my $node = extract_param
($param, 'node');
2642 my $vmid = extract_param
($param, 'vmid');
2644 my $digest = extract_param
($param, 'digest');
2646 my $disk = extract_param
($param, 'disk');
2648 my $sizestr = extract_param
($param, 'size');
2650 my $skiplock = extract_param
($param, 'skiplock');
2651 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2652 if $skiplock && $authuser ne 'root@pam';
2654 my $storecfg = PVE
::Storage
::config
();
2656 my $updatefn = sub {
2658 my $conf = PVE
::QemuServer
::load_config
($vmid);
2660 die "checksum missmatch (file change by other user?)\n"
2661 if $digest && $digest ne $conf->{digest
};
2662 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2664 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2666 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2668 my (undef, undef, undef, undef, undef, undef, $format) =
2669 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2671 die "can't resize volume: $disk if snapshot exists\n"
2672 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2674 my $volid = $drive->{file
};
2676 die "disk '$disk' has no associated volume\n" if !$volid;
2678 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2680 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2682 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2684 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2686 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2687 my ($ext, $newsize, $unit) = ($1, $2, $4);
2690 $newsize = $newsize * 1024;
2691 } elsif ($unit eq 'M') {
2692 $newsize = $newsize * 1024 * 1024;
2693 } elsif ($unit eq 'G') {
2694 $newsize = $newsize * 1024 * 1024 * 1024;
2695 } elsif ($unit eq 'T') {
2696 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2699 $newsize += $size if $ext;
2700 $newsize = int($newsize);
2702 die "unable to skrink disk size\n" if $newsize < $size;
2704 return if $size == $newsize;
2706 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2708 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2710 $drive->{size
} = $newsize;
2711 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2713 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2716 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2720 __PACKAGE__-
>register_method({
2721 name
=> 'snapshot_list',
2722 path
=> '{vmid}/snapshot',
2724 description
=> "List all snapshots.",
2726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2729 protected
=> 1, # qemu pid files are only readable by root
2731 additionalProperties
=> 0,
2733 vmid
=> get_standard_option
('pve-vmid'),
2734 node
=> get_standard_option
('pve-node'),
2743 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2748 my $vmid = $param->{vmid
};
2750 my $conf = PVE
::QemuServer
::load_config
($vmid);
2751 my $snaphash = $conf->{snapshots
} || {};
2755 foreach my $name (keys %$snaphash) {
2756 my $d = $snaphash->{$name};
2759 snaptime
=> $d->{snaptime
} || 0,
2760 vmstate
=> $d->{vmstate
} ?
1 : 0,
2761 description
=> $d->{description
} || '',
2763 $item->{parent
} = $d->{parent
} if $d->{parent
};
2764 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2768 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2769 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2770 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2772 push @$res, $current;
2777 __PACKAGE__-
>register_method({
2779 path
=> '{vmid}/snapshot',
2783 description
=> "Snapshot a VM.",
2785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2788 additionalProperties
=> 0,
2790 node
=> get_standard_option
('pve-node'),
2791 vmid
=> get_standard_option
('pve-vmid'),
2792 snapname
=> get_standard_option
('pve-snapshot-name'),
2796 description
=> "Save the vmstate",
2801 description
=> "A textual description or comment.",
2807 description
=> "the task ID.",
2812 my $rpcenv = PVE
::RPCEnvironment
::get
();
2814 my $authuser = $rpcenv->get_user();
2816 my $node = extract_param
($param, 'node');
2818 my $vmid = extract_param
($param, 'vmid');
2820 my $snapname = extract_param
($param, 'snapname');
2822 die "unable to use snapshot name 'current' (reserved name)\n"
2823 if $snapname eq 'current';
2826 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2827 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2828 $param->{description
});
2831 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2834 __PACKAGE__-
>register_method({
2835 name
=> 'snapshot_cmd_idx',
2836 path
=> '{vmid}/snapshot/{snapname}',
2843 additionalProperties
=> 0,
2845 vmid
=> get_standard_option
('pve-vmid'),
2846 node
=> get_standard_option
('pve-node'),
2847 snapname
=> get_standard_option
('pve-snapshot-name'),
2856 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2863 push @$res, { cmd
=> 'rollback' };
2864 push @$res, { cmd
=> 'config' };
2869 __PACKAGE__-
>register_method({
2870 name
=> 'update_snapshot_config',
2871 path
=> '{vmid}/snapshot/{snapname}/config',
2875 description
=> "Update snapshot metadata.",
2877 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2880 additionalProperties
=> 0,
2882 node
=> get_standard_option
('pve-node'),
2883 vmid
=> get_standard_option
('pve-vmid'),
2884 snapname
=> get_standard_option
('pve-snapshot-name'),
2888 description
=> "A textual description or comment.",
2892 returns
=> { type
=> 'null' },
2896 my $rpcenv = PVE
::RPCEnvironment
::get
();
2898 my $authuser = $rpcenv->get_user();
2900 my $vmid = extract_param
($param, 'vmid');
2902 my $snapname = extract_param
($param, 'snapname');
2904 return undef if !defined($param->{description
});
2906 my $updatefn = sub {
2908 my $conf = PVE
::QemuServer
::load_config
($vmid);
2910 PVE
::QemuServer
::check_lock
($conf);
2912 my $snap = $conf->{snapshots
}->{$snapname};
2914 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2916 $snap->{description
} = $param->{description
} if defined($param->{description
});
2918 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2921 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2926 __PACKAGE__-
>register_method({
2927 name
=> 'get_snapshot_config',
2928 path
=> '{vmid}/snapshot/{snapname}/config',
2931 description
=> "Get snapshot configuration",
2933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2936 additionalProperties
=> 0,
2938 node
=> get_standard_option
('pve-node'),
2939 vmid
=> get_standard_option
('pve-vmid'),
2940 snapname
=> get_standard_option
('pve-snapshot-name'),
2943 returns
=> { type
=> "object" },
2947 my $rpcenv = PVE
::RPCEnvironment
::get
();
2949 my $authuser = $rpcenv->get_user();
2951 my $vmid = extract_param
($param, 'vmid');
2953 my $snapname = extract_param
($param, 'snapname');
2955 my $conf = PVE
::QemuServer
::load_config
($vmid);
2957 my $snap = $conf->{snapshots
}->{$snapname};
2959 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2964 __PACKAGE__-
>register_method({
2966 path
=> '{vmid}/snapshot/{snapname}/rollback',
2970 description
=> "Rollback VM state to specified snapshot.",
2972 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2975 additionalProperties
=> 0,
2977 node
=> get_standard_option
('pve-node'),
2978 vmid
=> get_standard_option
('pve-vmid'),
2979 snapname
=> get_standard_option
('pve-snapshot-name'),
2984 description
=> "the task ID.",
2989 my $rpcenv = PVE
::RPCEnvironment
::get
();
2991 my $authuser = $rpcenv->get_user();
2993 my $node = extract_param
($param, 'node');
2995 my $vmid = extract_param
($param, 'vmid');
2997 my $snapname = extract_param
($param, 'snapname');
3000 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3001 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3004 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3007 __PACKAGE__-
>register_method({
3008 name
=> 'delsnapshot',
3009 path
=> '{vmid}/snapshot/{snapname}',
3013 description
=> "Delete a VM 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'),
3022 snapname
=> get_standard_option
('pve-snapshot-name'),
3026 description
=> "For removal from config file, even if removing disk snapshots fails.",
3032 description
=> "the task ID.",
3037 my $rpcenv = PVE
::RPCEnvironment
::get
();
3039 my $authuser = $rpcenv->get_user();
3041 my $node = extract_param
($param, 'node');
3043 my $vmid = extract_param
($param, 'vmid');
3045 my $snapname = extract_param
($param, 'snapname');
3048 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3049 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3052 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3055 __PACKAGE__-
>register_method({
3057 path
=> '{vmid}/template',
3061 description
=> "Create a Template.",
3063 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3064 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3067 additionalProperties
=> 0,
3069 node
=> get_standard_option
('pve-node'),
3070 vmid
=> get_standard_option
('pve-vmid'),
3074 description
=> "If you want to convert only 1 disk to base image.",
3075 enum
=> [PVE
::QemuServer
::disknames
()],
3080 returns
=> { type
=> 'null'},
3084 my $rpcenv = PVE
::RPCEnvironment
::get
();
3086 my $authuser = $rpcenv->get_user();
3088 my $node = extract_param
($param, 'node');
3090 my $vmid = extract_param
($param, 'vmid');
3092 my $disk = extract_param
($param, 'disk');
3094 my $updatefn = sub {
3096 my $conf = PVE
::QemuServer
::load_config
($vmid);
3098 PVE
::QemuServer
::check_lock
($conf);
3100 die "unable to create template, because VM contains snapshots\n"
3101 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3103 die "you can't convert a template to a template\n"
3104 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3106 die "you can't convert a VM to template if VM is running\n"
3107 if PVE
::QemuServer
::check_running
($vmid);
3110 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3113 $conf->{template
} = 1;
3114 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3116 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3119 PVE
::QemuServer
::lock_config
($vmid, $updatefn);