1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
21 use PVE
::RPCEnvironment
;
22 use PVE
::AccessControl
;
26 use PVE
::API2
::Firewall
::VM
;
29 if (!$ENV{PVE_GENERATING_DOCS
}) {
30 require PVE
::HA
::Env
::PVE2
;
31 import PVE
::HA
::Env
::PVE2
;
32 require PVE
::HA
::Config
;
33 import PVE
::HA
::Config
;
37 use Data
::Dumper
; # fixme: remove
39 use base
qw(PVE::RESTHandler);
41 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.";
43 my $resolve_cdrom_alias = sub {
46 if (my $value = $param->{cdrom
}) {
47 $value .= ",media=cdrom" if $value !~ m/media=/;
48 $param->{ide2
} = $value;
49 delete $param->{cdrom
};
53 my $check_storage_access = sub {
54 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
56 PVE
::QemuServer
::foreach_drive
($settings, sub {
57 my ($ds, $drive) = @_;
59 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
61 my $volid = $drive->{file
};
63 if (!$volid || $volid eq 'none') {
65 } elsif ($isCDROM && ($volid eq 'cdrom')) {
66 $rpcenv->check($authuser, "/", ['Sys.Console']);
67 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
68 my ($storeid, $size) = ($2 || $default_storage, $3);
69 die "no storage ID specified (and no default storage)\n" if !$storeid;
70 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
72 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
77 my $check_storage_access_clone = sub {
78 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
82 PVE
::QemuServer
::foreach_drive
($conf, sub {
83 my ($ds, $drive) = @_;
85 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
87 my $volid = $drive->{file
};
89 return if !$volid || $volid eq 'none';
92 if ($volid eq 'cdrom') {
93 $rpcenv->check($authuser, "/", ['Sys.Console']);
95 # we simply allow access
96 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
97 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
98 $sharedvm = 0 if !$scfg->{shared
};
102 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
104 $sharedvm = 0 if !$scfg->{shared
};
106 $sid = $storage if $storage;
107 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
114 # Note: $pool is only needed when creating a VM, because pool permissions
115 # are automatically inherited if VM already exists inside a pool.
116 my $create_disks = sub {
117 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
122 PVE
::QemuServer
::foreach_drive
($settings, sub {
123 my ($ds, $disk) = @_;
125 my $volid = $disk->{file
};
127 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
128 delete $disk->{size
};
129 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
130 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
131 my ($storeid, $size) = ($2 || $default_storage, $3);
132 die "no storage ID specified (and no default storage)\n" if !$storeid;
133 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
134 my $fmt = $disk->{format
} || $defformat;
137 if ($ds eq 'efidisk0') {
139 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
140 die "uefi vars image not found\n" if ! -f
$ovmfvars;
141 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
143 $disk->{file
} = $volid;
144 $disk->{size
} = 128*1024;
145 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
146 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
147 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
148 my $path = PVE
::Storage
::path
($storecfg, $volid);
149 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
150 push @$efidiskcmd, $ovmfvars;
151 push @$efidiskcmd, $path;
153 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
155 eval { PVE
::Tools
::run_command
($efidiskcmd); };
157 die "Copying of EFI Vars image failed: $err" if $err;
159 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
160 $fmt, undef, $size*1024*1024);
161 $disk->{file
} = $volid;
162 $disk->{size
} = $size*1024*1024*1024;
164 push @$vollist, $volid;
165 delete $disk->{format
}; # no longer needed
166 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
169 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
171 my $volid_is_new = 1;
174 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
175 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
180 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
182 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
184 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
186 die "volume $volid does not exists\n" if !$size;
188 $disk->{size
} = $size;
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
195 # free allocated images on error
197 syslog
('err', "VM $vmid creating disks failed");
198 foreach my $volid (@$vollist) {
199 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
205 # modify vm config if everything went well
206 foreach my $ds (keys %$res) {
207 $conf->{$ds} = $res->{$ds};
224 my $memoryoptions = {
230 my $hwtypeoptions = {
242 my $generaloptions = {
249 'migrate_downtime' => 1,
250 'migrate_speed' => 1,
262 my $vmpoweroptions = {
271 my $check_vm_modify_config_perm = sub {
272 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
274 return 1 if $authuser eq 'root@pam';
276 foreach my $opt (@$key_list) {
277 # disk checks need to be done somewhere else
278 next if PVE
::QemuServer
::is_valid_drivename
($opt);
279 next if $opt eq 'cdrom';
280 next if $opt =~ m/^unused\d+$/;
282 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
284 } elsif ($memoryoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
286 } elsif ($hwtypeoptions->{$opt}) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
288 } elsif ($generaloptions->{$opt}) {
289 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
290 # special case for startup since it changes host behaviour
291 if ($opt eq 'startup') {
292 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
294 } elsif ($vmpoweroptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
296 } elsif ($diskoptions->{$opt}) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
298 } elsif ($opt =~ m/^net\d+$/) {
299 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
301 # catches usb\d+, hostpci\d+, args, lock, etc.
302 # new options will be checked here
303 die "only root can set '$opt' config\n";
310 __PACKAGE__-
>register_method({
314 description
=> "Virtual machine index (per node).",
316 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
320 protected
=> 1, # qemu pid files are only readable by root
322 additionalProperties
=> 0,
324 node
=> get_standard_option
('pve-node'),
328 description
=> "Determine the full status of active VMs.",
338 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
343 my $rpcenv = PVE
::RPCEnvironment
::get
();
344 my $authuser = $rpcenv->get_user();
346 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
349 foreach my $vmid (keys %$vmstatus) {
350 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
352 my $data = $vmstatus->{$vmid};
353 $data->{vmid
} = int($vmid);
362 __PACKAGE__-
>register_method({
366 description
=> "Create or restore a virtual machine.",
368 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
369 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
370 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
371 user
=> 'all', # check inside
376 additionalProperties
=> 0,
377 properties
=> PVE
::QemuServer
::json_config_properties
(
379 node
=> get_standard_option
('pve-node'),
380 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
382 description
=> "The backup file.",
386 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
388 storage
=> get_standard_option
('pve-storage-id', {
389 description
=> "Default storage.",
391 completion
=> \
&PVE
::QemuServer
::complete_storage
,
396 description
=> "Allow to overwrite existing VM.",
397 requires
=> 'archive',
402 description
=> "Assign a unique random ethernet address.",
403 requires
=> 'archive',
407 type
=> 'string', format
=> 'pve-poolid',
408 description
=> "Add the VM to the specified pool.",
418 my $rpcenv = PVE
::RPCEnvironment
::get
();
420 my $authuser = $rpcenv->get_user();
422 my $node = extract_param
($param, 'node');
424 my $vmid = extract_param
($param, 'vmid');
426 my $archive = extract_param
($param, 'archive');
428 my $storage = extract_param
($param, 'storage');
430 my $force = extract_param
($param, 'force');
432 my $unique = extract_param
($param, 'unique');
434 my $pool = extract_param
($param, 'pool');
436 my $filename = PVE
::QemuConfig-
>config_file($vmid);
438 my $storecfg = PVE
::Storage
::config
();
440 PVE
::Cluster
::check_cfs_quorum
();
442 if (defined($pool)) {
443 $rpcenv->check_pool_exist($pool);
446 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
447 if defined($storage);
449 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
451 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
453 } elsif ($archive && $force && (-f
$filename) &&
454 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
455 # OK: user has VM.Backup permissions, and want to restore an existing VM
461 &$resolve_cdrom_alias($param);
463 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
465 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
467 foreach my $opt (keys %$param) {
468 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
469 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
470 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
472 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
473 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
477 PVE
::QemuServer
::add_random_macs
($param);
479 my $keystr = join(' ', keys %$param);
480 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
482 if ($archive eq '-') {
483 die "pipe requires cli environment\n"
484 if $rpcenv->{type
} ne 'cli';
486 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
487 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
491 my $restorefn = sub {
492 my $vmlist = PVE
::Cluster
::get_vmlist
();
493 if ($vmlist->{ids
}->{$vmid}) {
494 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
495 if ($current_node eq $node) {
496 my $conf = PVE
::QemuConfig-
>load_config($vmid);
498 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
500 die "unable to restore vm $vmid - config file already exists\n"
503 die "unable to restore vm $vmid - vm is running\n"
504 if PVE
::QemuServer
::check_running
($vmid);
506 die "unable to restore vm $vmid - vm is a template\n"
507 if PVE
::QemuConfig-
>is_template($conf);
510 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
515 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
518 unique
=> $unique });
520 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
523 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
529 PVE
::Cluster
::check_vmid_unused
($vmid);
539 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
541 # try to be smart about bootdisk
542 my @disks = PVE
::QemuServer
::valid_drive_names
();
544 foreach my $ds (reverse @disks) {
545 next if !$conf->{$ds};
546 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
547 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
551 if (!$conf->{bootdisk
} && $firstdisk) {
552 $conf->{bootdisk
} = $firstdisk;
555 # auto generate uuid if user did not specify smbios1 option
556 if (!$conf->{smbios1
}) {
557 my ($uuid, $uuid_str);
558 UUID
::generate
($uuid);
559 UUID
::unparse
($uuid, $uuid_str);
560 $conf->{smbios1
} = "uuid=$uuid_str";
563 PVE
::QemuConfig-
>write_config($vmid, $conf);
569 foreach my $volid (@$vollist) {
570 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
573 die "create failed - $err";
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
582 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
585 __PACKAGE__-
>register_method({
590 description
=> "Directory index",
595 additionalProperties
=> 0,
597 node
=> get_standard_option
('pve-node'),
598 vmid
=> get_standard_option
('pve-vmid'),
606 subdir
=> { type
=> 'string' },
609 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
615 { subdir
=> 'config' },
616 { subdir
=> 'pending' },
617 { subdir
=> 'status' },
618 { subdir
=> 'unlink' },
619 { subdir
=> 'vncproxy' },
620 { subdir
=> 'migrate' },
621 { subdir
=> 'resize' },
622 { subdir
=> 'move' },
624 { subdir
=> 'rrddata' },
625 { subdir
=> 'monitor' },
626 { subdir
=> 'agent' },
627 { subdir
=> 'snapshot' },
628 { subdir
=> 'spiceproxy' },
629 { subdir
=> 'sendkey' },
630 { subdir
=> 'firewall' },
636 __PACKAGE__-
>register_method ({
637 subclass
=> "PVE::API2::Firewall::VM",
638 path
=> '{vmid}/firewall',
641 __PACKAGE__-
>register_method({
643 path
=> '{vmid}/rrd',
645 protected
=> 1, # fixme: can we avoid that?
647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
649 description
=> "Read VM RRD statistics (returns PNG)",
651 additionalProperties
=> 0,
653 node
=> get_standard_option
('pve-node'),
654 vmid
=> get_standard_option
('pve-vmid'),
656 description
=> "Specify the time frame you are interested in.",
658 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
661 description
=> "The list of datasources you want to display.",
662 type
=> 'string', format
=> 'pve-configid-list',
665 description
=> "The RRD consolidation function",
667 enum
=> [ 'AVERAGE', 'MAX' ],
675 filename
=> { type
=> 'string' },
681 return PVE
::Cluster
::create_rrd_graph
(
682 "pve2-vm/$param->{vmid}", $param->{timeframe
},
683 $param->{ds
}, $param->{cf
});
687 __PACKAGE__-
>register_method({
689 path
=> '{vmid}/rrddata',
691 protected
=> 1, # fixme: can we avoid that?
693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
695 description
=> "Read VM RRD statistics",
697 additionalProperties
=> 0,
699 node
=> get_standard_option
('pve-node'),
700 vmid
=> get_standard_option
('pve-vmid'),
702 description
=> "Specify the time frame you are interested in.",
704 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
707 description
=> "The RRD consolidation function",
709 enum
=> [ 'AVERAGE', 'MAX' ],
724 return PVE
::Cluster
::create_rrd_data
(
725 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
729 __PACKAGE__-
>register_method({
731 path
=> '{vmid}/config',
734 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
739 additionalProperties
=> 0,
741 node
=> get_standard_option
('pve-node'),
742 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
744 description
=> "Get current values (instead of pending values).",
756 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
763 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
765 delete $conf->{snapshots
};
767 if (!$param->{current
}) {
768 foreach my $opt (keys %{$conf->{pending
}}) {
769 next if $opt eq 'delete';
770 my $value = $conf->{pending
}->{$opt};
771 next if ref($value); # just to be sure
772 $conf->{$opt} = $value;
774 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
775 foreach my $opt (keys %$pending_delete_hash) {
776 delete $conf->{$opt} if $conf->{$opt};
780 delete $conf->{pending
};
785 __PACKAGE__-
>register_method({
786 name
=> 'vm_pending',
787 path
=> '{vmid}/pending',
790 description
=> "Get virtual machine configuration, including pending changes.",
792 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
795 additionalProperties
=> 0,
797 node
=> get_standard_option
('pve-node'),
798 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
807 description
=> "Configuration option name.",
811 description
=> "Current value.",
816 description
=> "Pending value.",
821 description
=> "Indicates a pending delete request if present and not 0. " .
822 "The value 2 indicates a force-delete request.",
834 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
836 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
840 foreach my $opt (keys %$conf) {
841 next if ref($conf->{$opt});
842 my $item = { key
=> $opt };
843 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
844 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
845 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
849 foreach my $opt (keys %{$conf->{pending
}}) {
850 next if $opt eq 'delete';
851 next if ref($conf->{pending
}->{$opt}); # just to be sure
852 next if defined($conf->{$opt});
853 my $item = { key
=> $opt };
854 $item->{pending
} = $conf->{pending
}->{$opt};
858 while (my ($opt, $force) = each %$pending_delete_hash) {
859 next if $conf->{pending
}->{$opt}; # just to be sure
860 next if $conf->{$opt};
861 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
868 # POST/PUT {vmid}/config implementation
870 # The original API used PUT (idempotent) an we assumed that all operations
871 # are fast. But it turned out that almost any configuration change can
872 # involve hot-plug actions, or disk alloc/free. Such actions can take long
873 # time to complete and have side effects (not idempotent).
875 # The new implementation uses POST and forks a worker process. We added
876 # a new option 'background_delay'. If specified we wait up to
877 # 'background_delay' second for the worker task to complete. It returns null
878 # if the task is finished within that time, else we return the UPID.
880 my $update_vm_api = sub {
881 my ($param, $sync) = @_;
883 my $rpcenv = PVE
::RPCEnvironment
::get
();
885 my $authuser = $rpcenv->get_user();
887 my $node = extract_param
($param, 'node');
889 my $vmid = extract_param
($param, 'vmid');
891 my $digest = extract_param
($param, 'digest');
893 my $background_delay = extract_param
($param, 'background_delay');
895 my @paramarr = (); # used for log message
896 foreach my $key (keys %$param) {
897 push @paramarr, "-$key", $param->{$key};
900 my $skiplock = extract_param
($param, 'skiplock');
901 raise_param_exc
({ skiplock
=> "Only root may use this option." })
902 if $skiplock && $authuser ne 'root@pam';
904 my $delete_str = extract_param
($param, 'delete');
906 my $revert_str = extract_param
($param, 'revert');
908 my $force = extract_param
($param, 'force');
910 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
912 my $storecfg = PVE
::Storage
::config
();
914 my $defaults = PVE
::QemuServer
::load_defaults
();
916 &$resolve_cdrom_alias($param);
918 # now try to verify all parameters
921 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
922 if (!PVE
::QemuServer
::option_exists
($opt)) {
923 raise_param_exc
({ revert
=> "unknown option '$opt'" });
926 raise_param_exc
({ delete => "you can't use '-$opt' and " .
927 "-revert $opt' at the same time" })
928 if defined($param->{$opt});
934 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
935 $opt = 'ide2' if $opt eq 'cdrom';
937 raise_param_exc
({ delete => "you can't use '-$opt' and " .
938 "-delete $opt' at the same time" })
939 if defined($param->{$opt});
941 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
942 "-revert $opt' at the same time" })
945 if (!PVE
::QemuServer
::option_exists
($opt)) {
946 raise_param_exc
({ delete => "unknown option '$opt'" });
952 foreach my $opt (keys %$param) {
953 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
955 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
956 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
957 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
958 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
959 } elsif ($opt =~ m/^net(\d+)$/) {
961 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
962 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
966 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
968 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
970 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
974 my $conf = PVE
::QemuConfig-
>load_config($vmid);
976 die "checksum missmatch (file change by other user?)\n"
977 if $digest && $digest ne $conf->{digest
};
979 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
981 foreach my $opt (keys %$revert) {
982 if (defined($conf->{$opt})) {
983 $param->{$opt} = $conf->{$opt};
984 } elsif (defined($conf->{pending
}->{$opt})) {
989 if ($param->{memory
} || defined($param->{balloon
})) {
990 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
991 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
993 die "balloon value too large (must be smaller than assigned memory)\n"
994 if $balloon && $balloon > $maxmem;
997 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1001 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1003 # write updates to pending section
1005 my $modified = {}; # record what $option we modify
1007 foreach my $opt (@delete) {
1008 $modified->{$opt} = 1;
1009 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1010 if (!defined($conf->{$opt})) {
1011 warn "cannot delete '$opt' - not set in current configuration!\n";
1012 $modified->{$opt} = 0;
1016 if ($opt =~ m/^unused/) {
1017 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1018 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1019 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1020 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1021 delete $conf->{$opt};
1022 PVE
::QemuConfig-
>write_config($vmid, $conf);
1024 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1025 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1026 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1027 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1028 if defined($conf->{pending
}->{$opt});
1029 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1030 PVE
::QemuConfig-
>write_config($vmid, $conf);
1032 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1033 PVE
::QemuConfig-
>write_config($vmid, $conf);
1037 foreach my $opt (keys %$param) { # add/change
1038 $modified->{$opt} = 1;
1039 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1040 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1042 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1043 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1044 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1045 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1047 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1049 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1050 if defined($conf->{pending
}->{$opt});
1052 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1054 $conf->{pending
}->{$opt} = $param->{$opt};
1056 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1057 PVE
::QemuConfig-
>write_config($vmid, $conf);
1060 # remove pending changes when nothing changed
1061 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1062 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1063 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1065 return if !scalar(keys %{$conf->{pending
}});
1067 my $running = PVE
::QemuServer
::check_running
($vmid);
1069 # apply pending changes
1071 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1075 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1076 raise_param_exc
($errors) if scalar(keys %$errors);
1078 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1088 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1090 if ($background_delay) {
1092 # Note: It would be better to do that in the Event based HTTPServer
1093 # to avoid blocking call to sleep.
1095 my $end_time = time() + $background_delay;
1097 my $task = PVE
::Tools
::upid_decode
($upid);
1100 while (time() < $end_time) {
1101 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1103 sleep(1); # this gets interrupted when child process ends
1107 my $status = PVE
::Tools
::upid_read_status
($upid);
1108 return undef if $status eq 'OK';
1117 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1120 my $vm_config_perm_list = [
1125 'VM.Config.Network',
1127 'VM.Config.Options',
1130 __PACKAGE__-
>register_method({
1131 name
=> 'update_vm_async',
1132 path
=> '{vmid}/config',
1136 description
=> "Set virtual machine options (asynchrounous API).",
1138 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1141 additionalProperties
=> 0,
1142 properties
=> PVE
::QemuServer
::json_config_properties
(
1144 node
=> get_standard_option
('pve-node'),
1145 vmid
=> get_standard_option
('pve-vmid'),
1146 skiplock
=> get_standard_option
('skiplock'),
1148 type
=> 'string', format
=> 'pve-configid-list',
1149 description
=> "A list of settings you want to delete.",
1153 type
=> 'string', format
=> 'pve-configid-list',
1154 description
=> "Revert a pending change.",
1159 description
=> $opt_force_description,
1161 requires
=> 'delete',
1165 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1169 background_delay
=> {
1171 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1182 code
=> $update_vm_api,
1185 __PACKAGE__-
>register_method({
1186 name
=> 'update_vm',
1187 path
=> '{vmid}/config',
1191 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1193 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1196 additionalProperties
=> 0,
1197 properties
=> PVE
::QemuServer
::json_config_properties
(
1199 node
=> get_standard_option
('pve-node'),
1200 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1201 skiplock
=> get_standard_option
('skiplock'),
1203 type
=> 'string', format
=> 'pve-configid-list',
1204 description
=> "A list of settings you want to delete.",
1208 type
=> 'string', format
=> 'pve-configid-list',
1209 description
=> "Revert a pending change.",
1214 description
=> $opt_force_description,
1216 requires
=> 'delete',
1220 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1226 returns
=> { type
=> 'null' },
1229 &$update_vm_api($param, 1);
1235 __PACKAGE__-
>register_method({
1236 name
=> 'destroy_vm',
1241 description
=> "Destroy the vm (also delete all used/owned volumes).",
1243 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1246 additionalProperties
=> 0,
1248 node
=> get_standard_option
('pve-node'),
1249 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1250 skiplock
=> get_standard_option
('skiplock'),
1259 my $rpcenv = PVE
::RPCEnvironment
::get
();
1261 my $authuser = $rpcenv->get_user();
1263 my $vmid = $param->{vmid
};
1265 my $skiplock = $param->{skiplock
};
1266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1267 if $skiplock && $authuser ne 'root@pam';
1270 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1272 my $storecfg = PVE
::Storage
::config
();
1274 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1276 die "unable to remove VM $vmid - used in HA resources\n"
1277 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1279 # early tests (repeat after locking)
1280 die "VM $vmid is running - destroy failed\n"
1281 if PVE
::QemuServer
::check_running
($vmid);
1286 syslog
('info', "destroy VM $vmid: $upid\n");
1288 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1290 PVE
::AccessControl
::remove_vm_access
($vmid);
1292 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1295 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1298 __PACKAGE__-
>register_method({
1300 path
=> '{vmid}/unlink',
1304 description
=> "Unlink/delete disk images.",
1306 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1309 additionalProperties
=> 0,
1311 node
=> get_standard_option
('pve-node'),
1312 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1314 type
=> 'string', format
=> 'pve-configid-list',
1315 description
=> "A list of disk IDs you want to delete.",
1319 description
=> $opt_force_description,
1324 returns
=> { type
=> 'null'},
1328 $param->{delete} = extract_param
($param, 'idlist');
1330 __PACKAGE__-
>update_vm($param);
1337 __PACKAGE__-
>register_method({
1339 path
=> '{vmid}/vncproxy',
1343 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1345 description
=> "Creates a TCP VNC proxy connections.",
1347 additionalProperties
=> 0,
1349 node
=> get_standard_option
('pve-node'),
1350 vmid
=> get_standard_option
('pve-vmid'),
1354 description
=> "starts websockify instead of vncproxy",
1359 additionalProperties
=> 0,
1361 user
=> { type
=> 'string' },
1362 ticket
=> { type
=> 'string' },
1363 cert
=> { type
=> 'string' },
1364 port
=> { type
=> 'integer' },
1365 upid
=> { type
=> 'string' },
1371 my $rpcenv = PVE
::RPCEnvironment
::get
();
1373 my $authuser = $rpcenv->get_user();
1375 my $vmid = $param->{vmid
};
1376 my $node = $param->{node
};
1377 my $websocket = $param->{websocket
};
1379 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1381 my $authpath = "/vms/$vmid";
1383 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1385 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1388 my ($remip, $family);
1391 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1392 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1393 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1394 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1396 $family = PVE
::Tools
::get_host_address_family
($node);
1399 my $port = PVE
::Tools
::next_vnc_port
($family);
1406 syslog
('info', "starting vnc proxy $upid\n");
1410 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1412 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1414 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1415 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1416 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1417 '-timeout', $timeout, '-authpath', $authpath,
1418 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1419 PVE
::Tools
::run_command
($cmd);
1422 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1424 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1426 my $sock = IO
::Socket
::IP-
>new(
1430 GetAddrInfoFlags
=> 0,
1431 ) or die "failed to create socket: $!\n";
1432 # Inside the worker we shouldn't have any previous alarms
1433 # running anyway...:
1435 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1437 accept(my $cli, $sock) or die "connection failed: $!\n";
1439 if (PVE
::Tools
::run_command
($cmd,
1440 output
=> '>&'.fileno($cli),
1441 input
=> '<&'.fileno($cli),
1444 die "Failed to run vncproxy.\n";
1451 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1453 PVE
::Tools
::wait_for_vnc_port
($port);
1464 __PACKAGE__-
>register_method({
1465 name
=> 'vncwebsocket',
1466 path
=> '{vmid}/vncwebsocket',
1469 description
=> "You also need to pass a valid ticket (vncticket).",
1470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1472 description
=> "Opens a weksocket for VNC traffic.",
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1479 description
=> "Ticket from previous call to vncproxy.",
1484 description
=> "Port number returned by previous vncproxy call.",
1494 port
=> { type
=> 'string' },
1500 my $rpcenv = PVE
::RPCEnvironment
::get
();
1502 my $authuser = $rpcenv->get_user();
1504 my $vmid = $param->{vmid
};
1505 my $node = $param->{node
};
1507 my $authpath = "/vms/$vmid";
1509 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1511 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1513 # Note: VNC ports are acessible from outside, so we do not gain any
1514 # security if we verify that $param->{port} belongs to VM $vmid. This
1515 # check is done by verifying the VNC ticket (inside VNC protocol).
1517 my $port = $param->{port
};
1519 return { port
=> $port };
1522 __PACKAGE__-
>register_method({
1523 name
=> 'spiceproxy',
1524 path
=> '{vmid}/spiceproxy',
1529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1531 description
=> "Returns a SPICE configuration to connect to the VM.",
1533 additionalProperties
=> 0,
1535 node
=> get_standard_option
('pve-node'),
1536 vmid
=> get_standard_option
('pve-vmid'),
1537 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1540 returns
=> get_standard_option
('remote-viewer-config'),
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $vmid = $param->{vmid
};
1549 my $node = $param->{node
};
1550 my $proxy = $param->{proxy
};
1552 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1553 my $title = "VM $vmid";
1554 $title .= " - ". $conf->{name
} if $conf->{name
};
1556 my $port = PVE
::QemuServer
::spice_port
($vmid);
1558 my ($ticket, undef, $remote_viewer_config) =
1559 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1561 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1562 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1564 return $remote_viewer_config;
1567 __PACKAGE__-
>register_method({
1569 path
=> '{vmid}/status',
1572 description
=> "Directory index",
1577 additionalProperties
=> 0,
1579 node
=> get_standard_option
('pve-node'),
1580 vmid
=> get_standard_option
('pve-vmid'),
1588 subdir
=> { type
=> 'string' },
1591 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1597 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1600 { subdir
=> 'current' },
1601 { subdir
=> 'start' },
1602 { subdir
=> 'stop' },
1608 __PACKAGE__-
>register_method({
1609 name
=> 'vm_status',
1610 path
=> '{vmid}/status/current',
1613 protected
=> 1, # qemu pid files are only readable by root
1614 description
=> "Get virtual machine status.",
1616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1619 additionalProperties
=> 0,
1621 node
=> get_standard_option
('pve-node'),
1622 vmid
=> get_standard_option
('pve-vmid'),
1625 returns
=> { type
=> 'object' },
1630 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1632 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1633 my $status = $vmstatus->{$param->{vmid
}};
1635 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1637 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1642 __PACKAGE__-
>register_method({
1644 path
=> '{vmid}/status/start',
1648 description
=> "Start virtual machine.",
1650 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1653 additionalProperties
=> 0,
1655 node
=> get_standard_option
('pve-node'),
1656 vmid
=> get_standard_option
('pve-vmid',
1657 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1658 skiplock
=> get_standard_option
('skiplock'),
1659 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1660 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1663 enum
=> ['secure', 'insecure'],
1664 description
=> "Migration traffic is encrypted using an SSH " .
1665 "tunnel by default. On secure, completely private networks " .
1666 "this can be disabled to increase performance.",
1669 migration_network
=> {
1670 type
=> 'string', format
=> 'CIDR',
1671 description
=> "CIDR of the (sub) network that is used for migration.",
1674 machine
=> get_standard_option
('pve-qm-machine'),
1676 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1688 my $rpcenv = PVE
::RPCEnvironment
::get
();
1690 my $authuser = $rpcenv->get_user();
1692 my $node = extract_param
($param, 'node');
1694 my $vmid = extract_param
($param, 'vmid');
1696 my $machine = extract_param
($param, 'machine');
1698 my $stateuri = extract_param
($param, 'stateuri');
1699 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1700 if $stateuri && $authuser ne 'root@pam';
1702 my $skiplock = extract_param
($param, 'skiplock');
1703 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1704 if $skiplock && $authuser ne 'root@pam';
1706 my $migratedfrom = extract_param
($param, 'migratedfrom');
1707 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1708 if $migratedfrom && $authuser ne 'root@pam';
1710 my $migration_type = extract_param
($param, 'migration_type');
1711 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1712 if $migration_type && $authuser ne 'root@pam';
1714 my $migration_network = extract_param
($param, 'migration_network');
1715 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1716 if $migration_network && $authuser ne 'root@pam';
1718 my $targetstorage = extract_param
($param, 'targetstorage');
1719 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1720 if $targetstorage && $authuser ne 'root@pam';
1722 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1723 if $targetstorage && !$migratedfrom;
1725 # read spice ticket from STDIN
1727 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1728 if (defined(my $line = <>)) {
1730 $spice_ticket = $line;
1734 PVE
::Cluster
::check_cfs_quorum
();
1736 my $storecfg = PVE
::Storage
::config
();
1738 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1739 $rpcenv->{type
} ne 'ha') {
1744 my $service = "vm:$vmid";
1746 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1748 print "Executing HA start for VM $vmid\n";
1750 PVE
::Tools
::run_command
($cmd);
1755 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1762 syslog
('info', "start VM $vmid: $upid\n");
1764 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1765 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1770 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1774 __PACKAGE__-
>register_method({
1776 path
=> '{vmid}/status/stop',
1780 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1781 "is akin to pulling the power plug of a running computer and may damage the VM data",
1783 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1786 additionalProperties
=> 0,
1788 node
=> get_standard_option
('pve-node'),
1789 vmid
=> get_standard_option
('pve-vmid',
1790 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1791 skiplock
=> get_standard_option
('skiplock'),
1792 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1794 description
=> "Wait maximal timeout seconds.",
1800 description
=> "Do not deactivate storage volumes.",
1813 my $rpcenv = PVE
::RPCEnvironment
::get
();
1815 my $authuser = $rpcenv->get_user();
1817 my $node = extract_param
($param, 'node');
1819 my $vmid = extract_param
($param, 'vmid');
1821 my $skiplock = extract_param
($param, 'skiplock');
1822 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1823 if $skiplock && $authuser ne 'root@pam';
1825 my $keepActive = extract_param
($param, 'keepActive');
1826 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1827 if $keepActive && $authuser ne 'root@pam';
1829 my $migratedfrom = extract_param
($param, 'migratedfrom');
1830 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1831 if $migratedfrom && $authuser ne 'root@pam';
1834 my $storecfg = PVE
::Storage
::config
();
1836 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1841 my $service = "vm:$vmid";
1843 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1845 print "Executing HA stop for VM $vmid\n";
1847 PVE
::Tools
::run_command
($cmd);
1852 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1858 syslog
('info', "stop VM $vmid: $upid\n");
1860 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1861 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1866 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1870 __PACKAGE__-
>register_method({
1872 path
=> '{vmid}/status/reset',
1876 description
=> "Reset virtual machine.",
1878 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1881 additionalProperties
=> 0,
1883 node
=> get_standard_option
('pve-node'),
1884 vmid
=> get_standard_option
('pve-vmid',
1885 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1886 skiplock
=> get_standard_option
('skiplock'),
1895 my $rpcenv = PVE
::RPCEnvironment
::get
();
1897 my $authuser = $rpcenv->get_user();
1899 my $node = extract_param
($param, 'node');
1901 my $vmid = extract_param
($param, 'vmid');
1903 my $skiplock = extract_param
($param, 'skiplock');
1904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1905 if $skiplock && $authuser ne 'root@pam';
1907 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1912 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1917 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1920 __PACKAGE__-
>register_method({
1921 name
=> 'vm_shutdown',
1922 path
=> '{vmid}/status/shutdown',
1926 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1927 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1929 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1932 additionalProperties
=> 0,
1934 node
=> get_standard_option
('pve-node'),
1935 vmid
=> get_standard_option
('pve-vmid',
1936 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1937 skiplock
=> get_standard_option
('skiplock'),
1939 description
=> "Wait maximal timeout seconds.",
1945 description
=> "Make sure the VM stops.",
1951 description
=> "Do not deactivate storage volumes.",
1964 my $rpcenv = PVE
::RPCEnvironment
::get
();
1966 my $authuser = $rpcenv->get_user();
1968 my $node = extract_param
($param, 'node');
1970 my $vmid = extract_param
($param, 'vmid');
1972 my $skiplock = extract_param
($param, 'skiplock');
1973 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1974 if $skiplock && $authuser ne 'root@pam';
1976 my $keepActive = extract_param
($param, 'keepActive');
1977 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1978 if $keepActive && $authuser ne 'root@pam';
1980 my $storecfg = PVE
::Storage
::config
();
1984 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1985 # otherwise, we will infer a shutdown command, but run into the timeout,
1986 # then when the vm is resumed, it will instantly shutdown
1988 # checking the qmp status here to get feedback to the gui/cli/api
1989 # and the status query should not take too long
1992 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1996 if (!$err && $qmpstatus->{status
} eq "paused") {
1997 if ($param->{forceStop
}) {
1998 warn "VM is paused - stop instead of shutdown\n";
2001 die "VM is paused - cannot shutdown\n";
2005 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2006 ($rpcenv->{type
} ne 'ha')) {
2011 my $service = "vm:$vmid";
2013 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2015 print "Executing HA stop for VM $vmid\n";
2017 PVE
::Tools
::run_command
($cmd);
2022 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2029 syslog
('info', "shutdown VM $vmid: $upid\n");
2031 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2032 $shutdown, $param->{forceStop
}, $keepActive);
2037 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2041 __PACKAGE__-
>register_method({
2042 name
=> 'vm_suspend',
2043 path
=> '{vmid}/status/suspend',
2047 description
=> "Suspend virtual machine.",
2049 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2052 additionalProperties
=> 0,
2054 node
=> get_standard_option
('pve-node'),
2055 vmid
=> get_standard_option
('pve-vmid',
2056 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2057 skiplock
=> get_standard_option
('skiplock'),
2066 my $rpcenv = PVE
::RPCEnvironment
::get
();
2068 my $authuser = $rpcenv->get_user();
2070 my $node = extract_param
($param, 'node');
2072 my $vmid = extract_param
($param, 'vmid');
2074 my $skiplock = extract_param
($param, 'skiplock');
2075 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2076 if $skiplock && $authuser ne 'root@pam';
2078 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2083 syslog
('info', "suspend VM $vmid: $upid\n");
2085 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2090 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2093 __PACKAGE__-
>register_method({
2094 name
=> 'vm_resume',
2095 path
=> '{vmid}/status/resume',
2099 description
=> "Resume virtual machine.",
2101 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2104 additionalProperties
=> 0,
2106 node
=> get_standard_option
('pve-node'),
2107 vmid
=> get_standard_option
('pve-vmid',
2108 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2109 skiplock
=> get_standard_option
('skiplock'),
2110 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2120 my $rpcenv = PVE
::RPCEnvironment
::get
();
2122 my $authuser = $rpcenv->get_user();
2124 my $node = extract_param
($param, 'node');
2126 my $vmid = extract_param
($param, 'vmid');
2128 my $skiplock = extract_param
($param, 'skiplock');
2129 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2130 if $skiplock && $authuser ne 'root@pam';
2132 my $nocheck = extract_param
($param, 'nocheck');
2134 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2139 syslog
('info', "resume VM $vmid: $upid\n");
2141 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2146 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2149 __PACKAGE__-
>register_method({
2150 name
=> 'vm_sendkey',
2151 path
=> '{vmid}/sendkey',
2155 description
=> "Send key event to virtual machine.",
2157 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2160 additionalProperties
=> 0,
2162 node
=> get_standard_option
('pve-node'),
2163 vmid
=> get_standard_option
('pve-vmid',
2164 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2165 skiplock
=> get_standard_option
('skiplock'),
2167 description
=> "The key (qemu monitor encoding).",
2172 returns
=> { type
=> 'null'},
2176 my $rpcenv = PVE
::RPCEnvironment
::get
();
2178 my $authuser = $rpcenv->get_user();
2180 my $node = extract_param
($param, 'node');
2182 my $vmid = extract_param
($param, 'vmid');
2184 my $skiplock = extract_param
($param, 'skiplock');
2185 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2186 if $skiplock && $authuser ne 'root@pam';
2188 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2193 __PACKAGE__-
>register_method({
2194 name
=> 'vm_feature',
2195 path
=> '{vmid}/feature',
2199 description
=> "Check if feature for virtual machine is available.",
2201 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid'),
2209 description
=> "Feature to check.",
2211 enum
=> [ 'snapshot', 'clone', 'copy' ],
2213 snapname
=> get_standard_option
('pve-snapshot-name', {
2221 hasFeature
=> { type
=> 'boolean' },
2224 items
=> { type
=> 'string' },
2231 my $node = extract_param
($param, 'node');
2233 my $vmid = extract_param
($param, 'vmid');
2235 my $snapname = extract_param
($param, 'snapname');
2237 my $feature = extract_param
($param, 'feature');
2239 my $running = PVE
::QemuServer
::check_running
($vmid);
2241 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2244 my $snap = $conf->{snapshots
}->{$snapname};
2245 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2248 my $storecfg = PVE
::Storage
::config
();
2250 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2251 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2254 hasFeature
=> $hasFeature,
2255 nodes
=> [ keys %$nodelist ],
2259 __PACKAGE__-
>register_method({
2261 path
=> '{vmid}/clone',
2265 description
=> "Create a copy of virtual machine/template.",
2267 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2268 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2269 "'Datastore.AllocateSpace' on any used storage.",
2272 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2274 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2275 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2280 additionalProperties
=> 0,
2282 node
=> get_standard_option
('pve-node'),
2283 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2284 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2287 type
=> 'string', format
=> 'dns-name',
2288 description
=> "Set a name for the new VM.",
2293 description
=> "Description for the new VM.",
2297 type
=> 'string', format
=> 'pve-poolid',
2298 description
=> "Add the new VM to the specified pool.",
2300 snapname
=> get_standard_option
('pve-snapshot-name', {
2303 storage
=> get_standard_option
('pve-storage-id', {
2304 description
=> "Target storage for full clone.",
2309 description
=> "Target format for file storage.",
2313 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2318 description
=> "Create a full copy of all disk. This is always done when " .
2319 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2322 target
=> get_standard_option
('pve-node', {
2323 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2334 my $rpcenv = PVE
::RPCEnvironment
::get
();
2336 my $authuser = $rpcenv->get_user();
2338 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $newid = extract_param
($param, 'newid');
2344 my $pool = extract_param
($param, 'pool');
2346 if (defined($pool)) {
2347 $rpcenv->check_pool_exist($pool);
2350 my $snapname = extract_param
($param, 'snapname');
2352 my $storage = extract_param
($param, 'storage');
2354 my $format = extract_param
($param, 'format');
2356 my $target = extract_param
($param, 'target');
2358 my $localnode = PVE
::INotify
::nodename
();
2360 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2362 PVE
::Cluster
::check_node_exists
($target) if $target;
2364 my $storecfg = PVE
::Storage
::config
();
2367 # check if storage is enabled on local node
2368 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2370 # check if storage is available on target node
2371 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2372 # clone only works if target storage is shared
2373 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2374 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2378 PVE
::Cluster
::check_cfs_quorum
();
2380 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2382 # exclusive lock if VM is running - else shared lock is enough;
2383 my $shared_lock = $running ?
0 : 1;
2387 # do all tests after lock
2388 # we also try to do all tests before we fork the worker
2390 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2392 PVE
::QemuConfig-
>check_lock($conf);
2394 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2396 die "unexpected state change\n" if $verify_running != $running;
2398 die "snapshot '$snapname' does not exist\n"
2399 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2401 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2403 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2405 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2407 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2409 die "unable to create VM $newid: config file already exists\n"
2412 my $newconf = { lock => 'clone' };
2417 foreach my $opt (keys %$oldconf) {
2418 my $value = $oldconf->{$opt};
2420 # do not copy snapshot related info
2421 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2422 $opt eq 'vmstate' || $opt eq 'snapstate';
2424 # no need to copy unused images, because VMID(owner) changes anyways
2425 next if $opt =~ m/^unused\d+$/;
2427 # always change MAC! address
2428 if ($opt =~ m/^net(\d+)$/) {
2429 my $net = PVE
::QemuServer
::parse_net
($value);
2430 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2431 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2432 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2433 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2434 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2435 die "unable to parse drive options for '$opt'\n" if !$drive;
2436 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2437 $newconf->{$opt} = $value; # simply copy configuration
2439 if ($param->{full
}) {
2440 die "Full clone feature is not available"
2441 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2442 $fullclone->{$opt} = 1;
2444 # not full means clone instead of copy
2445 die "Linked clone feature is not available"
2446 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2448 $drives->{$opt} = $drive;
2449 push @$vollist, $drive->{file
};
2452 # copy everything else
2453 $newconf->{$opt} = $value;
2457 # auto generate a new uuid
2458 my ($uuid, $uuid_str);
2459 UUID
::generate
($uuid);
2460 UUID
::unparse
($uuid, $uuid_str);
2461 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2462 $smbios1->{uuid
} = $uuid_str;
2463 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2465 delete $newconf->{template
};
2467 if ($param->{name
}) {
2468 $newconf->{name
} = $param->{name
};
2470 if ($oldconf->{name
}) {
2471 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2473 $newconf->{name
} = "Copy-of-VM-$vmid";
2477 if ($param->{description
}) {
2478 $newconf->{description
} = $param->{description
};
2481 # create empty/temp config - this fails if VM already exists on other node
2482 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2487 my $newvollist = [];
2491 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2493 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2495 my $total_jobs = scalar(keys %{$drives});
2498 foreach my $opt (keys %$drives) {
2499 my $drive = $drives->{$opt};
2500 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2502 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2503 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2504 $jobs, $skipcomplete, $oldconf->{agent
});
2506 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2508 PVE
::QemuConfig-
>write_config($newid, $newconf);
2512 delete $newconf->{lock};
2513 PVE
::QemuConfig-
>write_config($newid, $newconf);
2516 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2517 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2518 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2520 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2521 die "Failed to move config to node '$target' - rename failed: $!\n"
2522 if !rename($conffile, $newconffile);
2525 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2530 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2532 sleep 1; # some storage like rbd need to wait before release volume - really?
2534 foreach my $volid (@$newvollist) {
2535 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2538 die "clone failed: $err";
2544 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2546 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2549 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2550 # Aquire exclusive lock lock for $newid
2551 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2556 __PACKAGE__-
>register_method({
2557 name
=> 'move_vm_disk',
2558 path
=> '{vmid}/move_disk',
2562 description
=> "Move volume to different storage.",
2564 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2566 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2567 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2571 additionalProperties
=> 0,
2573 node
=> get_standard_option
('pve-node'),
2574 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2577 description
=> "The disk you want to move.",
2578 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2580 storage
=> get_standard_option
('pve-storage-id', {
2581 description
=> "Target storage.",
2582 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2586 description
=> "Target Format.",
2587 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2592 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2598 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2606 description
=> "the task ID.",
2611 my $rpcenv = PVE
::RPCEnvironment
::get
();
2613 my $authuser = $rpcenv->get_user();
2615 my $node = extract_param
($param, 'node');
2617 my $vmid = extract_param
($param, 'vmid');
2619 my $digest = extract_param
($param, 'digest');
2621 my $disk = extract_param
($param, 'disk');
2623 my $storeid = extract_param
($param, 'storage');
2625 my $format = extract_param
($param, 'format');
2627 my $storecfg = PVE
::Storage
::config
();
2629 my $updatefn = sub {
2631 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2633 PVE
::QemuConfig-
>check_lock($conf);
2635 die "checksum missmatch (file change by other user?)\n"
2636 if $digest && $digest ne $conf->{digest
};
2638 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2640 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2642 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2644 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2647 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2648 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2652 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2653 (!$format || !$oldfmt || $oldfmt eq $format);
2655 # this only checks snapshots because $disk is passed!
2656 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2657 die "you can't move a disk with snapshots and delete the source\n"
2658 if $snapshotted && $param->{delete};
2660 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2662 my $running = PVE
::QemuServer
::check_running
($vmid);
2664 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2668 my $newvollist = [];
2671 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2673 warn "moving disk with snapshots, snapshots will not be moved!\n"
2676 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2677 $vmid, $storeid, $format, 1, $newvollist);
2679 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2681 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2683 # convert moved disk to base if part of template
2684 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2685 if PVE
::QemuConfig-
>is_template($conf);
2687 PVE
::QemuConfig-
>write_config($vmid, $conf);
2690 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2691 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2698 foreach my $volid (@$newvollist) {
2699 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2702 die "storage migration failed: $err";
2705 if ($param->{delete}) {
2707 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2708 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2714 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2717 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2720 __PACKAGE__-
>register_method({
2721 name
=> 'migrate_vm',
2722 path
=> '{vmid}/migrate',
2726 description
=> "Migrate virtual machine. Creates a new migration task.",
2728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2731 additionalProperties
=> 0,
2733 node
=> get_standard_option
('pve-node'),
2734 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2735 target
=> get_standard_option
('pve-node', {
2736 description
=> "Target node.",
2737 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2741 description
=> "Use online/live migration.",
2746 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2751 enum
=> ['secure', 'insecure'],
2752 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2755 migration_network
=> {
2756 type
=> 'string', format
=> 'CIDR',
2757 description
=> "CIDR of the (sub) network that is used for migration.",
2760 "with-local-disks" => {
2762 description
=> "Enable live storage migration for local disk",
2765 targetstorage
=> get_standard_option
('pve-storage-id', {
2766 description
=> "Default target storage.",
2768 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2774 description
=> "the task ID.",
2779 my $rpcenv = PVE
::RPCEnvironment
::get
();
2781 my $authuser = $rpcenv->get_user();
2783 my $target = extract_param
($param, 'target');
2785 my $localnode = PVE
::INotify
::nodename
();
2786 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2788 PVE
::Cluster
::check_cfs_quorum
();
2790 PVE
::Cluster
::check_node_exists
($target);
2792 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2794 my $vmid = extract_param
($param, 'vmid');
2796 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2797 if !$param->{online
} && $param->{targetstorage
};
2799 raise_param_exc
({ force
=> "Only root may use this option." })
2800 if $param->{force
} && $authuser ne 'root@pam';
2802 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2803 if $param->{migration_type
} && $authuser ne 'root@pam';
2805 # allow root only until better network permissions are available
2806 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2807 if $param->{migration_network
} && $authuser ne 'root@pam';
2810 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2812 # try to detect errors early
2814 PVE
::QemuConfig-
>check_lock($conf);
2816 if (PVE
::QemuServer
::check_running
($vmid)) {
2817 die "cant migrate running VM without --online\n"
2818 if !$param->{online
};
2821 my $storecfg = PVE
::Storage
::config
();
2822 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2824 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2829 my $service = "vm:$vmid";
2831 my $cmd = ['ha-manager', 'migrate', $service, $target];
2833 print "Executing HA migrate for VM $vmid to node $target\n";
2835 PVE
::Tools
::run_command
($cmd);
2840 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2847 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2850 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2855 __PACKAGE__-
>register_method({
2857 path
=> '{vmid}/monitor',
2861 description
=> "Execute Qemu monitor commands.",
2863 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2867 additionalProperties
=> 0,
2869 node
=> get_standard_option
('pve-node'),
2870 vmid
=> get_standard_option
('pve-vmid'),
2873 description
=> "The monitor command.",
2877 returns
=> { type
=> 'string'},
2881 my $rpcenv = PVE
::RPCEnvironment
::get
();
2882 my $authuser = $rpcenv->get_user();
2885 my $command = shift;
2886 return $command =~ m/^\s*info(\s+|$)/
2887 || $command =~ m/^\s*help\s*$/;
2890 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2891 if !&$is_ro($param->{command
});
2893 my $vmid = $param->{vmid
};
2895 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2899 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2901 $res = "ERROR: $@" if $@;
2906 my $guest_agent_commands = [
2914 'network-get-interfaces',
2917 'get-memory-blocks',
2918 'get-memory-block-info',
2925 __PACKAGE__-
>register_method({
2927 path
=> '{vmid}/agent',
2931 description
=> "Execute Qemu Guest Agent commands.",
2933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2936 additionalProperties
=> 0,
2938 node
=> get_standard_option
('pve-node'),
2939 vmid
=> get_standard_option
('pve-vmid', {
2940 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2943 description
=> "The QGA command.",
2944 enum
=> $guest_agent_commands,
2950 description
=> "Returns an object with a single `result` property. The type of that
2951 property depends on the executed command.",
2956 my $vmid = $param->{vmid
};
2958 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2960 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2961 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2963 my $cmd = $param->{command
};
2965 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2967 return { result
=> $res };
2970 __PACKAGE__-
>register_method({
2971 name
=> 'resize_vm',
2972 path
=> '{vmid}/resize',
2976 description
=> "Extend volume size.",
2978 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2981 additionalProperties
=> 0,
2983 node
=> get_standard_option
('pve-node'),
2984 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2985 skiplock
=> get_standard_option
('skiplock'),
2988 description
=> "The disk you want to resize.",
2989 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2993 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2994 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.",
2998 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3004 returns
=> { type
=> 'null'},
3008 my $rpcenv = PVE
::RPCEnvironment
::get
();
3010 my $authuser = $rpcenv->get_user();
3012 my $node = extract_param
($param, 'node');
3014 my $vmid = extract_param
($param, 'vmid');
3016 my $digest = extract_param
($param, 'digest');
3018 my $disk = extract_param
($param, 'disk');
3020 my $sizestr = extract_param
($param, 'size');
3022 my $skiplock = extract_param
($param, 'skiplock');
3023 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3024 if $skiplock && $authuser ne 'root@pam';
3026 my $storecfg = PVE
::Storage
::config
();
3028 my $updatefn = sub {
3030 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3032 die "checksum missmatch (file change by other user?)\n"
3033 if $digest && $digest ne $conf->{digest
};
3034 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3036 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3038 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3040 my (undef, undef, undef, undef, undef, undef, $format) =
3041 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3043 die "can't resize volume: $disk if snapshot exists\n"
3044 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3046 my $volid = $drive->{file
};
3048 die "disk '$disk' has no associated volume\n" if !$volid;
3050 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3052 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3054 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3056 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3057 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3059 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3060 my ($ext, $newsize, $unit) = ($1, $2, $4);
3063 $newsize = $newsize * 1024;
3064 } elsif ($unit eq 'M') {
3065 $newsize = $newsize * 1024 * 1024;
3066 } elsif ($unit eq 'G') {
3067 $newsize = $newsize * 1024 * 1024 * 1024;
3068 } elsif ($unit eq 'T') {
3069 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3072 $newsize += $size if $ext;
3073 $newsize = int($newsize);
3075 die "shrinking disks is not supported\n" if $newsize < $size;
3077 return if $size == $newsize;
3079 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3081 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3083 $drive->{size
} = $newsize;
3084 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3086 PVE
::QemuConfig-
>write_config($vmid, $conf);
3089 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3093 __PACKAGE__-
>register_method({
3094 name
=> 'snapshot_list',
3095 path
=> '{vmid}/snapshot',
3097 description
=> "List all snapshots.",
3099 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3102 protected
=> 1, # qemu pid files are only readable by root
3104 additionalProperties
=> 0,
3106 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3107 node
=> get_standard_option
('pve-node'),
3116 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3121 my $vmid = $param->{vmid
};
3123 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3124 my $snaphash = $conf->{snapshots
} || {};
3128 foreach my $name (keys %$snaphash) {
3129 my $d = $snaphash->{$name};
3132 snaptime
=> $d->{snaptime
} || 0,
3133 vmstate
=> $d->{vmstate
} ?
1 : 0,
3134 description
=> $d->{description
} || '',
3136 $item->{parent
} = $d->{parent
} if $d->{parent
};
3137 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3141 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3142 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3143 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3145 push @$res, $current;
3150 __PACKAGE__-
>register_method({
3152 path
=> '{vmid}/snapshot',
3156 description
=> "Snapshot a VM.",
3158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3161 additionalProperties
=> 0,
3163 node
=> get_standard_option
('pve-node'),
3164 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3165 snapname
=> get_standard_option
('pve-snapshot-name'),
3169 description
=> "Save the vmstate",
3174 description
=> "A textual description or comment.",
3180 description
=> "the task ID.",
3185 my $rpcenv = PVE
::RPCEnvironment
::get
();
3187 my $authuser = $rpcenv->get_user();
3189 my $node = extract_param
($param, 'node');
3191 my $vmid = extract_param
($param, 'vmid');
3193 my $snapname = extract_param
($param, 'snapname');
3195 die "unable to use snapshot name 'current' (reserved name)\n"
3196 if $snapname eq 'current';
3199 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3200 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3201 $param->{description
});
3204 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3207 __PACKAGE__-
>register_method({
3208 name
=> 'snapshot_cmd_idx',
3209 path
=> '{vmid}/snapshot/{snapname}',
3216 additionalProperties
=> 0,
3218 vmid
=> get_standard_option
('pve-vmid'),
3219 node
=> get_standard_option
('pve-node'),
3220 snapname
=> get_standard_option
('pve-snapshot-name'),
3229 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3236 push @$res, { cmd
=> 'rollback' };
3237 push @$res, { cmd
=> 'config' };
3242 __PACKAGE__-
>register_method({
3243 name
=> 'update_snapshot_config',
3244 path
=> '{vmid}/snapshot/{snapname}/config',
3248 description
=> "Update snapshot metadata.",
3250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3253 additionalProperties
=> 0,
3255 node
=> get_standard_option
('pve-node'),
3256 vmid
=> get_standard_option
('pve-vmid'),
3257 snapname
=> get_standard_option
('pve-snapshot-name'),
3261 description
=> "A textual description or comment.",
3265 returns
=> { type
=> 'null' },
3269 my $rpcenv = PVE
::RPCEnvironment
::get
();
3271 my $authuser = $rpcenv->get_user();
3273 my $vmid = extract_param
($param, 'vmid');
3275 my $snapname = extract_param
($param, 'snapname');
3277 return undef if !defined($param->{description
});
3279 my $updatefn = sub {
3281 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3283 PVE
::QemuConfig-
>check_lock($conf);
3285 my $snap = $conf->{snapshots
}->{$snapname};
3287 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3289 $snap->{description
} = $param->{description
} if defined($param->{description
});
3291 PVE
::QemuConfig-
>write_config($vmid, $conf);
3294 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3299 __PACKAGE__-
>register_method({
3300 name
=> 'get_snapshot_config',
3301 path
=> '{vmid}/snapshot/{snapname}/config',
3304 description
=> "Get snapshot configuration",
3306 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3309 additionalProperties
=> 0,
3311 node
=> get_standard_option
('pve-node'),
3312 vmid
=> get_standard_option
('pve-vmid'),
3313 snapname
=> get_standard_option
('pve-snapshot-name'),
3316 returns
=> { type
=> "object" },
3320 my $rpcenv = PVE
::RPCEnvironment
::get
();
3322 my $authuser = $rpcenv->get_user();
3324 my $vmid = extract_param
($param, 'vmid');
3326 my $snapname = extract_param
($param, 'snapname');
3328 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3330 my $snap = $conf->{snapshots
}->{$snapname};
3332 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3337 __PACKAGE__-
>register_method({
3339 path
=> '{vmid}/snapshot/{snapname}/rollback',
3343 description
=> "Rollback VM state to specified snapshot.",
3345 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3348 additionalProperties
=> 0,
3350 node
=> get_standard_option
('pve-node'),
3351 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3352 snapname
=> get_standard_option
('pve-snapshot-name'),
3357 description
=> "the task ID.",
3362 my $rpcenv = PVE
::RPCEnvironment
::get
();
3364 my $authuser = $rpcenv->get_user();
3366 my $node = extract_param
($param, 'node');
3368 my $vmid = extract_param
($param, 'vmid');
3370 my $snapname = extract_param
($param, 'snapname');
3373 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3374 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3377 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3380 __PACKAGE__-
>register_method({
3381 name
=> 'delsnapshot',
3382 path
=> '{vmid}/snapshot/{snapname}',
3386 description
=> "Delete a VM snapshot.",
3388 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3391 additionalProperties
=> 0,
3393 node
=> get_standard_option
('pve-node'),
3394 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3395 snapname
=> get_standard_option
('pve-snapshot-name'),
3399 description
=> "For removal from config file, even if removing disk snapshots fails.",
3405 description
=> "the task ID.",
3410 my $rpcenv = PVE
::RPCEnvironment
::get
();
3412 my $authuser = $rpcenv->get_user();
3414 my $node = extract_param
($param, 'node');
3416 my $vmid = extract_param
($param, 'vmid');
3418 my $snapname = extract_param
($param, 'snapname');
3421 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3422 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3425 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3428 __PACKAGE__-
>register_method({
3430 path
=> '{vmid}/template',
3434 description
=> "Create a Template.",
3436 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3437 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3440 additionalProperties
=> 0,
3442 node
=> get_standard_option
('pve-node'),
3443 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3447 description
=> "If you want to convert only 1 disk to base image.",
3448 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3453 returns
=> { type
=> 'null'},
3457 my $rpcenv = PVE
::RPCEnvironment
::get
();
3459 my $authuser = $rpcenv->get_user();
3461 my $node = extract_param
($param, 'node');
3463 my $vmid = extract_param
($param, 'vmid');
3465 my $disk = extract_param
($param, 'disk');
3467 my $updatefn = sub {
3469 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3471 PVE
::QemuConfig-
>check_lock($conf);
3473 die "unable to create template, because VM contains snapshots\n"
3474 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3476 die "you can't convert a template to a template\n"
3477 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3479 die "you can't convert a VM to template if VM is running\n"
3480 if PVE
::QemuServer
::check_running
($vmid);
3483 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3486 $conf->{template
} = 1;
3487 PVE
::QemuConfig-
>write_config($vmid, $conf);
3489 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3492 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);