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 ($opt =~ m/^unused/) {
1011 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1012 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1013 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1014 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1015 delete $conf->{$opt};
1016 PVE
::QemuConfig-
>write_config($vmid, $conf);
1018 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1019 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1020 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1021 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1022 if defined($conf->{pending
}->{$opt});
1023 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1024 PVE
::QemuConfig-
>write_config($vmid, $conf);
1026 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1027 PVE
::QemuConfig-
>write_config($vmid, $conf);
1031 foreach my $opt (keys %$param) { # add/change
1032 $modified->{$opt} = 1;
1033 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1034 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1036 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1037 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1038 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1039 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1041 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1043 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1044 if defined($conf->{pending
}->{$opt});
1046 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1048 $conf->{pending
}->{$opt} = $param->{$opt};
1050 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1051 PVE
::QemuConfig-
>write_config($vmid, $conf);
1054 # remove pending changes when nothing changed
1055 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1056 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1057 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1059 return if !scalar(keys %{$conf->{pending
}});
1061 my $running = PVE
::QemuServer
::check_running
($vmid);
1063 # apply pending changes
1065 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1069 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1070 raise_param_exc
($errors) if scalar(keys %$errors);
1072 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1082 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1084 if ($background_delay) {
1086 # Note: It would be better to do that in the Event based HTTPServer
1087 # to avoid blocking call to sleep.
1089 my $end_time = time() + $background_delay;
1091 my $task = PVE
::Tools
::upid_decode
($upid);
1094 while (time() < $end_time) {
1095 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1097 sleep(1); # this gets interrupted when child process ends
1101 my $status = PVE
::Tools
::upid_read_status
($upid);
1102 return undef if $status eq 'OK';
1111 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1114 my $vm_config_perm_list = [
1119 'VM.Config.Network',
1121 'VM.Config.Options',
1124 __PACKAGE__-
>register_method({
1125 name
=> 'update_vm_async',
1126 path
=> '{vmid}/config',
1130 description
=> "Set virtual machine options (asynchrounous API).",
1132 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1135 additionalProperties
=> 0,
1136 properties
=> PVE
::QemuServer
::json_config_properties
(
1138 node
=> get_standard_option
('pve-node'),
1139 vmid
=> get_standard_option
('pve-vmid'),
1140 skiplock
=> get_standard_option
('skiplock'),
1142 type
=> 'string', format
=> 'pve-configid-list',
1143 description
=> "A list of settings you want to delete.",
1147 type
=> 'string', format
=> 'pve-configid-list',
1148 description
=> "Revert a pending change.",
1153 description
=> $opt_force_description,
1155 requires
=> 'delete',
1159 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1163 background_delay
=> {
1165 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1176 code
=> $update_vm_api,
1179 __PACKAGE__-
>register_method({
1180 name
=> 'update_vm',
1181 path
=> '{vmid}/config',
1185 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1187 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1190 additionalProperties
=> 0,
1191 properties
=> PVE
::QemuServer
::json_config_properties
(
1193 node
=> get_standard_option
('pve-node'),
1194 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1195 skiplock
=> get_standard_option
('skiplock'),
1197 type
=> 'string', format
=> 'pve-configid-list',
1198 description
=> "A list of settings you want to delete.",
1202 type
=> 'string', format
=> 'pve-configid-list',
1203 description
=> "Revert a pending change.",
1208 description
=> $opt_force_description,
1210 requires
=> 'delete',
1214 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1220 returns
=> { type
=> 'null' },
1223 &$update_vm_api($param, 1);
1229 __PACKAGE__-
>register_method({
1230 name
=> 'destroy_vm',
1235 description
=> "Destroy the vm (also delete all used/owned volumes).",
1237 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1244 skiplock
=> get_standard_option
('skiplock'),
1253 my $rpcenv = PVE
::RPCEnvironment
::get
();
1255 my $authuser = $rpcenv->get_user();
1257 my $vmid = $param->{vmid
};
1259 my $skiplock = $param->{skiplock
};
1260 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1261 if $skiplock && $authuser ne 'root@pam';
1264 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1266 my $storecfg = PVE
::Storage
::config
();
1268 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1270 die "unable to remove VM $vmid - used in HA resources\n"
1271 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1273 # early tests (repeat after locking)
1274 die "VM $vmid is running - destroy failed\n"
1275 if PVE
::QemuServer
::check_running
($vmid);
1280 syslog
('info', "destroy VM $vmid: $upid\n");
1282 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1284 PVE
::AccessControl
::remove_vm_access
($vmid);
1286 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1289 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1292 __PACKAGE__-
>register_method({
1294 path
=> '{vmid}/unlink',
1298 description
=> "Unlink/delete disk images.",
1300 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1303 additionalProperties
=> 0,
1305 node
=> get_standard_option
('pve-node'),
1306 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1308 type
=> 'string', format
=> 'pve-configid-list',
1309 description
=> "A list of disk IDs you want to delete.",
1313 description
=> $opt_force_description,
1318 returns
=> { type
=> 'null'},
1322 $param->{delete} = extract_param
($param, 'idlist');
1324 __PACKAGE__-
>update_vm($param);
1331 __PACKAGE__-
>register_method({
1333 path
=> '{vmid}/vncproxy',
1337 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1339 description
=> "Creates a TCP VNC proxy connections.",
1341 additionalProperties
=> 0,
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1348 description
=> "starts websockify instead of vncproxy",
1353 additionalProperties
=> 0,
1355 user
=> { type
=> 'string' },
1356 ticket
=> { type
=> 'string' },
1357 cert
=> { type
=> 'string' },
1358 port
=> { type
=> 'integer' },
1359 upid
=> { type
=> 'string' },
1365 my $rpcenv = PVE
::RPCEnvironment
::get
();
1367 my $authuser = $rpcenv->get_user();
1369 my $vmid = $param->{vmid
};
1370 my $node = $param->{node
};
1371 my $websocket = $param->{websocket
};
1373 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1375 my $authpath = "/vms/$vmid";
1377 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1379 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1382 my ($remip, $family);
1385 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1386 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1387 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1388 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1390 $family = PVE
::Tools
::get_host_address_family
($node);
1393 my $port = PVE
::Tools
::next_vnc_port
($family);
1400 syslog
('info', "starting vnc proxy $upid\n");
1404 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1406 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1408 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1409 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1410 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1411 '-timeout', $timeout, '-authpath', $authpath,
1412 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1413 PVE
::Tools
::run_command
($cmd);
1416 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1418 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1420 my $sock = IO
::Socket
::IP-
>new(
1424 GetAddrInfoFlags
=> 0,
1425 ) or die "failed to create socket: $!\n";
1426 # Inside the worker we shouldn't have any previous alarms
1427 # running anyway...:
1429 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1431 accept(my $cli, $sock) or die "connection failed: $!\n";
1433 if (PVE
::Tools
::run_command
($cmd,
1434 output
=> '>&'.fileno($cli),
1435 input
=> '<&'.fileno($cli),
1438 die "Failed to run vncproxy.\n";
1445 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1447 PVE
::Tools
::wait_for_vnc_port
($port);
1458 __PACKAGE__-
>register_method({
1459 name
=> 'vncwebsocket',
1460 path
=> '{vmid}/vncwebsocket',
1463 description
=> "You also need to pass a valid ticket (vncticket).",
1464 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1466 description
=> "Opens a weksocket for VNC traffic.",
1468 additionalProperties
=> 0,
1470 node
=> get_standard_option
('pve-node'),
1471 vmid
=> get_standard_option
('pve-vmid'),
1473 description
=> "Ticket from previous call to vncproxy.",
1478 description
=> "Port number returned by previous vncproxy call.",
1488 port
=> { type
=> 'string' },
1494 my $rpcenv = PVE
::RPCEnvironment
::get
();
1496 my $authuser = $rpcenv->get_user();
1498 my $vmid = $param->{vmid
};
1499 my $node = $param->{node
};
1501 my $authpath = "/vms/$vmid";
1503 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1505 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1507 # Note: VNC ports are acessible from outside, so we do not gain any
1508 # security if we verify that $param->{port} belongs to VM $vmid. This
1509 # check is done by verifying the VNC ticket (inside VNC protocol).
1511 my $port = $param->{port
};
1513 return { port
=> $port };
1516 __PACKAGE__-
>register_method({
1517 name
=> 'spiceproxy',
1518 path
=> '{vmid}/spiceproxy',
1523 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1525 description
=> "Returns a SPICE configuration to connect to the VM.",
1527 additionalProperties
=> 0,
1529 node
=> get_standard_option
('pve-node'),
1530 vmid
=> get_standard_option
('pve-vmid'),
1531 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1534 returns
=> get_standard_option
('remote-viewer-config'),
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $vmid = $param->{vmid
};
1543 my $node = $param->{node
};
1544 my $proxy = $param->{proxy
};
1546 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1547 my $title = "VM $vmid";
1548 $title .= " - ". $conf->{name
} if $conf->{name
};
1550 my $port = PVE
::QemuServer
::spice_port
($vmid);
1552 my ($ticket, undef, $remote_viewer_config) =
1553 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1555 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1556 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1558 return $remote_viewer_config;
1561 __PACKAGE__-
>register_method({
1563 path
=> '{vmid}/status',
1566 description
=> "Directory index",
1571 additionalProperties
=> 0,
1573 node
=> get_standard_option
('pve-node'),
1574 vmid
=> get_standard_option
('pve-vmid'),
1582 subdir
=> { type
=> 'string' },
1585 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1591 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1594 { subdir
=> 'current' },
1595 { subdir
=> 'start' },
1596 { subdir
=> 'stop' },
1602 __PACKAGE__-
>register_method({
1603 name
=> 'vm_status',
1604 path
=> '{vmid}/status/current',
1607 protected
=> 1, # qemu pid files are only readable by root
1608 description
=> "Get virtual machine status.",
1610 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1613 additionalProperties
=> 0,
1615 node
=> get_standard_option
('pve-node'),
1616 vmid
=> get_standard_option
('pve-vmid'),
1619 returns
=> { type
=> 'object' },
1624 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1626 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1627 my $status = $vmstatus->{$param->{vmid
}};
1629 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1631 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1636 __PACKAGE__-
>register_method({
1638 path
=> '{vmid}/status/start',
1642 description
=> "Start virtual machine.",
1644 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1647 additionalProperties
=> 0,
1649 node
=> get_standard_option
('pve-node'),
1650 vmid
=> get_standard_option
('pve-vmid',
1651 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1652 skiplock
=> get_standard_option
('skiplock'),
1653 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1654 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1657 enum
=> ['secure', 'insecure'],
1658 description
=> "Migration traffic is encrypted using an SSH " .
1659 "tunnel by default. On secure, completely private networks " .
1660 "this can be disabled to increase performance.",
1663 migration_network
=> {
1664 type
=> 'string', format
=> 'CIDR',
1665 description
=> "CIDR of the (sub) network that is used for migration.",
1668 machine
=> get_standard_option
('pve-qm-machine'),
1670 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1682 my $rpcenv = PVE
::RPCEnvironment
::get
();
1684 my $authuser = $rpcenv->get_user();
1686 my $node = extract_param
($param, 'node');
1688 my $vmid = extract_param
($param, 'vmid');
1690 my $machine = extract_param
($param, 'machine');
1692 my $stateuri = extract_param
($param, 'stateuri');
1693 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1694 if $stateuri && $authuser ne 'root@pam';
1696 my $skiplock = extract_param
($param, 'skiplock');
1697 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1698 if $skiplock && $authuser ne 'root@pam';
1700 my $migratedfrom = extract_param
($param, 'migratedfrom');
1701 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1702 if $migratedfrom && $authuser ne 'root@pam';
1704 my $migration_type = extract_param
($param, 'migration_type');
1705 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1706 if $migration_type && $authuser ne 'root@pam';
1708 my $migration_network = extract_param
($param, 'migration_network');
1709 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1710 if $migration_network && $authuser ne 'root@pam';
1712 my $targetstorage = extract_param
($param, 'targetstorage');
1713 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1714 if $targetstorage && $authuser ne 'root@pam';
1716 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1717 if $targetstorage && !$migratedfrom;
1719 # read spice ticket from STDIN
1721 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1722 if (defined(my $line = <>)) {
1724 $spice_ticket = $line;
1728 PVE
::Cluster
::check_cfs_quorum
();
1730 my $storecfg = PVE
::Storage
::config
();
1732 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1733 $rpcenv->{type
} ne 'ha') {
1738 my $service = "vm:$vmid";
1740 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1742 print "Executing HA start for VM $vmid\n";
1744 PVE
::Tools
::run_command
($cmd);
1749 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1756 syslog
('info', "start VM $vmid: $upid\n");
1758 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1759 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1764 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1768 __PACKAGE__-
>register_method({
1770 path
=> '{vmid}/status/stop',
1774 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1775 "is akin to pulling the power plug of a running computer and may damage the VM data",
1777 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1780 additionalProperties
=> 0,
1782 node
=> get_standard_option
('pve-node'),
1783 vmid
=> get_standard_option
('pve-vmid',
1784 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1785 skiplock
=> get_standard_option
('skiplock'),
1786 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1788 description
=> "Wait maximal timeout seconds.",
1794 description
=> "Do not deactivate storage volumes.",
1807 my $rpcenv = PVE
::RPCEnvironment
::get
();
1809 my $authuser = $rpcenv->get_user();
1811 my $node = extract_param
($param, 'node');
1813 my $vmid = extract_param
($param, 'vmid');
1815 my $skiplock = extract_param
($param, 'skiplock');
1816 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1817 if $skiplock && $authuser ne 'root@pam';
1819 my $keepActive = extract_param
($param, 'keepActive');
1820 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1821 if $keepActive && $authuser ne 'root@pam';
1823 my $migratedfrom = extract_param
($param, 'migratedfrom');
1824 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1825 if $migratedfrom && $authuser ne 'root@pam';
1828 my $storecfg = PVE
::Storage
::config
();
1830 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1835 my $service = "vm:$vmid";
1837 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1839 print "Executing HA stop for VM $vmid\n";
1841 PVE
::Tools
::run_command
($cmd);
1846 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1852 syslog
('info', "stop VM $vmid: $upid\n");
1854 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1855 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1860 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1864 __PACKAGE__-
>register_method({
1866 path
=> '{vmid}/status/reset',
1870 description
=> "Reset virtual machine.",
1872 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1875 additionalProperties
=> 0,
1877 node
=> get_standard_option
('pve-node'),
1878 vmid
=> get_standard_option
('pve-vmid',
1879 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1880 skiplock
=> get_standard_option
('skiplock'),
1889 my $rpcenv = PVE
::RPCEnvironment
::get
();
1891 my $authuser = $rpcenv->get_user();
1893 my $node = extract_param
($param, 'node');
1895 my $vmid = extract_param
($param, 'vmid');
1897 my $skiplock = extract_param
($param, 'skiplock');
1898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1899 if $skiplock && $authuser ne 'root@pam';
1901 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1906 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1911 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1914 __PACKAGE__-
>register_method({
1915 name
=> 'vm_shutdown',
1916 path
=> '{vmid}/status/shutdown',
1920 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1921 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1923 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1926 additionalProperties
=> 0,
1928 node
=> get_standard_option
('pve-node'),
1929 vmid
=> get_standard_option
('pve-vmid',
1930 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1931 skiplock
=> get_standard_option
('skiplock'),
1933 description
=> "Wait maximal timeout seconds.",
1939 description
=> "Make sure the VM stops.",
1945 description
=> "Do not deactivate storage volumes.",
1958 my $rpcenv = PVE
::RPCEnvironment
::get
();
1960 my $authuser = $rpcenv->get_user();
1962 my $node = extract_param
($param, 'node');
1964 my $vmid = extract_param
($param, 'vmid');
1966 my $skiplock = extract_param
($param, 'skiplock');
1967 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1968 if $skiplock && $authuser ne 'root@pam';
1970 my $keepActive = extract_param
($param, 'keepActive');
1971 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1972 if $keepActive && $authuser ne 'root@pam';
1974 my $storecfg = PVE
::Storage
::config
();
1978 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1979 # otherwise, we will infer a shutdown command, but run into the timeout,
1980 # then when the vm is resumed, it will instantly shutdown
1982 # checking the qmp status here to get feedback to the gui/cli/api
1983 # and the status query should not take too long
1986 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1990 if (!$err && $qmpstatus->{status
} eq "paused") {
1991 if ($param->{forceStop
}) {
1992 warn "VM is paused - stop instead of shutdown\n";
1995 die "VM is paused - cannot shutdown\n";
1999 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2000 ($rpcenv->{type
} ne 'ha')) {
2005 my $service = "vm:$vmid";
2007 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2009 print "Executing HA stop for VM $vmid\n";
2011 PVE
::Tools
::run_command
($cmd);
2016 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2023 syslog
('info', "shutdown VM $vmid: $upid\n");
2025 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2026 $shutdown, $param->{forceStop
}, $keepActive);
2031 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2035 __PACKAGE__-
>register_method({
2036 name
=> 'vm_suspend',
2037 path
=> '{vmid}/status/suspend',
2041 description
=> "Suspend virtual machine.",
2043 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid',
2050 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2051 skiplock
=> get_standard_option
('skiplock'),
2060 my $rpcenv = PVE
::RPCEnvironment
::get
();
2062 my $authuser = $rpcenv->get_user();
2064 my $node = extract_param
($param, 'node');
2066 my $vmid = extract_param
($param, 'vmid');
2068 my $skiplock = extract_param
($param, 'skiplock');
2069 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2070 if $skiplock && $authuser ne 'root@pam';
2072 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2077 syslog
('info', "suspend VM $vmid: $upid\n");
2079 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2084 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2087 __PACKAGE__-
>register_method({
2088 name
=> 'vm_resume',
2089 path
=> '{vmid}/status/resume',
2093 description
=> "Resume virtual machine.",
2095 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2098 additionalProperties
=> 0,
2100 node
=> get_standard_option
('pve-node'),
2101 vmid
=> get_standard_option
('pve-vmid',
2102 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2103 skiplock
=> get_standard_option
('skiplock'),
2104 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2114 my $rpcenv = PVE
::RPCEnvironment
::get
();
2116 my $authuser = $rpcenv->get_user();
2118 my $node = extract_param
($param, 'node');
2120 my $vmid = extract_param
($param, 'vmid');
2122 my $skiplock = extract_param
($param, 'skiplock');
2123 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2124 if $skiplock && $authuser ne 'root@pam';
2126 my $nocheck = extract_param
($param, 'nocheck');
2128 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2133 syslog
('info', "resume VM $vmid: $upid\n");
2135 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2140 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2143 __PACKAGE__-
>register_method({
2144 name
=> 'vm_sendkey',
2145 path
=> '{vmid}/sendkey',
2149 description
=> "Send key event to virtual machine.",
2151 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2154 additionalProperties
=> 0,
2156 node
=> get_standard_option
('pve-node'),
2157 vmid
=> get_standard_option
('pve-vmid',
2158 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2159 skiplock
=> get_standard_option
('skiplock'),
2161 description
=> "The key (qemu monitor encoding).",
2166 returns
=> { type
=> 'null'},
2170 my $rpcenv = PVE
::RPCEnvironment
::get
();
2172 my $authuser = $rpcenv->get_user();
2174 my $node = extract_param
($param, 'node');
2176 my $vmid = extract_param
($param, 'vmid');
2178 my $skiplock = extract_param
($param, 'skiplock');
2179 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2180 if $skiplock && $authuser ne 'root@pam';
2182 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2187 __PACKAGE__-
>register_method({
2188 name
=> 'vm_feature',
2189 path
=> '{vmid}/feature',
2193 description
=> "Check if feature for virtual machine is available.",
2195 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2198 additionalProperties
=> 0,
2200 node
=> get_standard_option
('pve-node'),
2201 vmid
=> get_standard_option
('pve-vmid'),
2203 description
=> "Feature to check.",
2205 enum
=> [ 'snapshot', 'clone', 'copy' ],
2207 snapname
=> get_standard_option
('pve-snapshot-name', {
2215 hasFeature
=> { type
=> 'boolean' },
2218 items
=> { type
=> 'string' },
2225 my $node = extract_param
($param, 'node');
2227 my $vmid = extract_param
($param, 'vmid');
2229 my $snapname = extract_param
($param, 'snapname');
2231 my $feature = extract_param
($param, 'feature');
2233 my $running = PVE
::QemuServer
::check_running
($vmid);
2235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2238 my $snap = $conf->{snapshots
}->{$snapname};
2239 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2242 my $storecfg = PVE
::Storage
::config
();
2244 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2245 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2248 hasFeature
=> $hasFeature,
2249 nodes
=> [ keys %$nodelist ],
2253 __PACKAGE__-
>register_method({
2255 path
=> '{vmid}/clone',
2259 description
=> "Create a copy of virtual machine/template.",
2261 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2262 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2263 "'Datastore.AllocateSpace' on any used storage.",
2266 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2268 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2269 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2274 additionalProperties
=> 0,
2276 node
=> get_standard_option
('pve-node'),
2277 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2278 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2281 type
=> 'string', format
=> 'dns-name',
2282 description
=> "Set a name for the new VM.",
2287 description
=> "Description for the new VM.",
2291 type
=> 'string', format
=> 'pve-poolid',
2292 description
=> "Add the new VM to the specified pool.",
2294 snapname
=> get_standard_option
('pve-snapshot-name', {
2297 storage
=> get_standard_option
('pve-storage-id', {
2298 description
=> "Target storage for full clone.",
2303 description
=> "Target format for file storage.",
2307 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2312 description
=> "Create a full copy of all disk. This is always done when " .
2313 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2316 target
=> get_standard_option
('pve-node', {
2317 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2328 my $rpcenv = PVE
::RPCEnvironment
::get
();
2330 my $authuser = $rpcenv->get_user();
2332 my $node = extract_param
($param, 'node');
2334 my $vmid = extract_param
($param, 'vmid');
2336 my $newid = extract_param
($param, 'newid');
2338 my $pool = extract_param
($param, 'pool');
2340 if (defined($pool)) {
2341 $rpcenv->check_pool_exist($pool);
2344 my $snapname = extract_param
($param, 'snapname');
2346 my $storage = extract_param
($param, 'storage');
2348 my $format = extract_param
($param, 'format');
2350 my $target = extract_param
($param, 'target');
2352 my $localnode = PVE
::INotify
::nodename
();
2354 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2356 PVE
::Cluster
::check_node_exists
($target) if $target;
2358 my $storecfg = PVE
::Storage
::config
();
2361 # check if storage is enabled on local node
2362 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2364 # check if storage is available on target node
2365 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2366 # clone only works if target storage is shared
2367 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2368 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2372 PVE
::Cluster
::check_cfs_quorum
();
2374 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2376 # exclusive lock if VM is running - else shared lock is enough;
2377 my $shared_lock = $running ?
0 : 1;
2381 # do all tests after lock
2382 # we also try to do all tests before we fork the worker
2384 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2386 PVE
::QemuConfig-
>check_lock($conf);
2388 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2390 die "unexpected state change\n" if $verify_running != $running;
2392 die "snapshot '$snapname' does not exist\n"
2393 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2395 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2397 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2399 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2401 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2403 die "unable to create VM $newid: config file already exists\n"
2406 my $newconf = { lock => 'clone' };
2411 foreach my $opt (keys %$oldconf) {
2412 my $value = $oldconf->{$opt};
2414 # do not copy snapshot related info
2415 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2416 $opt eq 'vmstate' || $opt eq 'snapstate';
2418 # no need to copy unused images, because VMID(owner) changes anyways
2419 next if $opt =~ m/^unused\d+$/;
2421 # always change MAC! address
2422 if ($opt =~ m/^net(\d+)$/) {
2423 my $net = PVE
::QemuServer
::parse_net
($value);
2424 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2425 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2426 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2427 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2428 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2429 die "unable to parse drive options for '$opt'\n" if !$drive;
2430 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2431 $newconf->{$opt} = $value; # simply copy configuration
2433 if ($param->{full
}) {
2434 die "Full clone feature is not available"
2435 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2436 $fullclone->{$opt} = 1;
2438 # not full means clone instead of copy
2439 die "Linked clone feature is not available"
2440 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2442 $drives->{$opt} = $drive;
2443 push @$vollist, $drive->{file
};
2446 # copy everything else
2447 $newconf->{$opt} = $value;
2451 # auto generate a new uuid
2452 my ($uuid, $uuid_str);
2453 UUID
::generate
($uuid);
2454 UUID
::unparse
($uuid, $uuid_str);
2455 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2456 $smbios1->{uuid
} = $uuid_str;
2457 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2459 delete $newconf->{template
};
2461 if ($param->{name
}) {
2462 $newconf->{name
} = $param->{name
};
2464 if ($oldconf->{name
}) {
2465 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2467 $newconf->{name
} = "Copy-of-VM-$vmid";
2471 if ($param->{description
}) {
2472 $newconf->{description
} = $param->{description
};
2475 # create empty/temp config - this fails if VM already exists on other node
2476 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2481 my $newvollist = [];
2485 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2487 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2489 my $total_jobs = scalar(keys %{$drives});
2492 foreach my $opt (keys %$drives) {
2493 my $drive = $drives->{$opt};
2494 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2496 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2497 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2498 $jobs, $skipcomplete, $oldconf->{agent
});
2500 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2502 PVE
::QemuConfig-
>write_config($newid, $newconf);
2506 delete $newconf->{lock};
2507 PVE
::QemuConfig-
>write_config($newid, $newconf);
2510 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2511 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2512 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2514 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2515 die "Failed to move config to node '$target' - rename failed: $!\n"
2516 if !rename($conffile, $newconffile);
2519 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2524 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2526 sleep 1; # some storage like rbd need to wait before release volume - really?
2528 foreach my $volid (@$newvollist) {
2529 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2532 die "clone failed: $err";
2538 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2540 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2543 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2544 # Aquire exclusive lock lock for $newid
2545 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2550 __PACKAGE__-
>register_method({
2551 name
=> 'move_vm_disk',
2552 path
=> '{vmid}/move_disk',
2556 description
=> "Move volume to different storage.",
2558 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2560 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2561 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2565 additionalProperties
=> 0,
2567 node
=> get_standard_option
('pve-node'),
2568 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2571 description
=> "The disk you want to move.",
2572 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2574 storage
=> get_standard_option
('pve-storage-id', {
2575 description
=> "Target storage.",
2576 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2580 description
=> "Target Format.",
2581 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2586 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2592 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2600 description
=> "the task ID.",
2605 my $rpcenv = PVE
::RPCEnvironment
::get
();
2607 my $authuser = $rpcenv->get_user();
2609 my $node = extract_param
($param, 'node');
2611 my $vmid = extract_param
($param, 'vmid');
2613 my $digest = extract_param
($param, 'digest');
2615 my $disk = extract_param
($param, 'disk');
2617 my $storeid = extract_param
($param, 'storage');
2619 my $format = extract_param
($param, 'format');
2621 my $storecfg = PVE
::Storage
::config
();
2623 my $updatefn = sub {
2625 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2627 PVE
::QemuConfig-
>check_lock($conf);
2629 die "checksum missmatch (file change by other user?)\n"
2630 if $digest && $digest ne $conf->{digest
};
2632 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2634 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2636 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2638 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2641 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2642 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2646 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2647 (!$format || !$oldfmt || $oldfmt eq $format);
2649 # this only checks snapshots because $disk is passed!
2650 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2651 die "you can't move a disk with snapshots and delete the source\n"
2652 if $snapshotted && $param->{delete};
2654 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2656 my $running = PVE
::QemuServer
::check_running
($vmid);
2658 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2662 my $newvollist = [];
2665 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2667 warn "moving disk with snapshots, snapshots will not be moved!\n"
2670 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2671 $vmid, $storeid, $format, 1, $newvollist);
2673 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2675 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2677 # convert moved disk to base if part of template
2678 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2679 if PVE
::QemuConfig-
>is_template($conf);
2681 PVE
::QemuConfig-
>write_config($vmid, $conf);
2684 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2685 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2692 foreach my $volid (@$newvollist) {
2693 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2696 die "storage migration failed: $err";
2699 if ($param->{delete}) {
2701 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2702 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2708 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2711 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2714 __PACKAGE__-
>register_method({
2715 name
=> 'migrate_vm',
2716 path
=> '{vmid}/migrate',
2720 description
=> "Migrate virtual machine. Creates a new migration task.",
2722 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2725 additionalProperties
=> 0,
2727 node
=> get_standard_option
('pve-node'),
2728 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2729 target
=> get_standard_option
('pve-node', {
2730 description
=> "Target node.",
2731 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2735 description
=> "Use online/live migration.",
2740 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2745 enum
=> ['secure', 'insecure'],
2746 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2749 migration_network
=> {
2750 type
=> 'string', format
=> 'CIDR',
2751 description
=> "CIDR of the (sub) network that is used for migration.",
2754 "with-local-disks" => {
2756 description
=> "Enable live storage migration for local disk",
2759 targetstorage
=> get_standard_option
('pve-storage-id', {
2760 description
=> "Default target storage.",
2762 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2768 description
=> "the task ID.",
2773 my $rpcenv = PVE
::RPCEnvironment
::get
();
2775 my $authuser = $rpcenv->get_user();
2777 my $target = extract_param
($param, 'target');
2779 my $localnode = PVE
::INotify
::nodename
();
2780 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2782 PVE
::Cluster
::check_cfs_quorum
();
2784 PVE
::Cluster
::check_node_exists
($target);
2786 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2788 my $vmid = extract_param
($param, 'vmid');
2790 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2791 if !$param->{online
} && $param->{targetstorage
};
2793 raise_param_exc
({ force
=> "Only root may use this option." })
2794 if $param->{force
} && $authuser ne 'root@pam';
2796 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2797 if $param->{migration_type
} && $authuser ne 'root@pam';
2799 # allow root only until better network permissions are available
2800 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2801 if $param->{migration_network
} && $authuser ne 'root@pam';
2804 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2806 # try to detect errors early
2808 PVE
::QemuConfig-
>check_lock($conf);
2810 if (PVE
::QemuServer
::check_running
($vmid)) {
2811 die "cant migrate running VM without --online\n"
2812 if !$param->{online
};
2815 my $storecfg = PVE
::Storage
::config
();
2816 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2818 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2823 my $service = "vm:$vmid";
2825 my $cmd = ['ha-manager', 'migrate', $service, $target];
2827 print "Executing HA migrate for VM $vmid to node $target\n";
2829 PVE
::Tools
::run_command
($cmd);
2834 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2841 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2844 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2849 __PACKAGE__-
>register_method({
2851 path
=> '{vmid}/monitor',
2855 description
=> "Execute Qemu monitor commands.",
2857 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2861 additionalProperties
=> 0,
2863 node
=> get_standard_option
('pve-node'),
2864 vmid
=> get_standard_option
('pve-vmid'),
2867 description
=> "The monitor command.",
2871 returns
=> { type
=> 'string'},
2875 my $rpcenv = PVE
::RPCEnvironment
::get
();
2876 my $authuser = $rpcenv->get_user();
2879 my $command = shift;
2880 return $command =~ m/^\s*info(\s+|$)/
2881 || $command =~ m/^\s*help\s*$/;
2884 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2885 if !&$is_ro($param->{command
});
2887 my $vmid = $param->{vmid
};
2889 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2893 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2895 $res = "ERROR: $@" if $@;
2900 my $guest_agent_commands = [
2908 'network-get-interfaces',
2911 'get-memory-blocks',
2912 'get-memory-block-info',
2919 __PACKAGE__-
>register_method({
2921 path
=> '{vmid}/agent',
2925 description
=> "Execute Qemu Guest Agent commands.",
2927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2930 additionalProperties
=> 0,
2932 node
=> get_standard_option
('pve-node'),
2933 vmid
=> get_standard_option
('pve-vmid', {
2934 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2937 description
=> "The QGA command.",
2938 enum
=> $guest_agent_commands,
2944 description
=> "Returns an object with a single `result` property. The type of that
2945 property depends on the executed command.",
2950 my $vmid = $param->{vmid
};
2952 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2954 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2955 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2957 my $cmd = $param->{command
};
2959 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2961 return { result
=> $res };
2964 __PACKAGE__-
>register_method({
2965 name
=> 'resize_vm',
2966 path
=> '{vmid}/resize',
2970 description
=> "Extend volume size.",
2972 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2975 additionalProperties
=> 0,
2977 node
=> get_standard_option
('pve-node'),
2978 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2979 skiplock
=> get_standard_option
('skiplock'),
2982 description
=> "The disk you want to resize.",
2983 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2987 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2988 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.",
2992 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2998 returns
=> { type
=> 'null'},
3002 my $rpcenv = PVE
::RPCEnvironment
::get
();
3004 my $authuser = $rpcenv->get_user();
3006 my $node = extract_param
($param, 'node');
3008 my $vmid = extract_param
($param, 'vmid');
3010 my $digest = extract_param
($param, 'digest');
3012 my $disk = extract_param
($param, 'disk');
3014 my $sizestr = extract_param
($param, 'size');
3016 my $skiplock = extract_param
($param, 'skiplock');
3017 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3018 if $skiplock && $authuser ne 'root@pam';
3020 my $storecfg = PVE
::Storage
::config
();
3022 my $updatefn = sub {
3024 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3026 die "checksum missmatch (file change by other user?)\n"
3027 if $digest && $digest ne $conf->{digest
};
3028 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3030 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3032 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3034 my (undef, undef, undef, undef, undef, undef, $format) =
3035 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3037 die "can't resize volume: $disk if snapshot exists\n"
3038 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3040 my $volid = $drive->{file
};
3042 die "disk '$disk' has no associated volume\n" if !$volid;
3044 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3046 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3048 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3050 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3051 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3053 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3054 my ($ext, $newsize, $unit) = ($1, $2, $4);
3057 $newsize = $newsize * 1024;
3058 } elsif ($unit eq 'M') {
3059 $newsize = $newsize * 1024 * 1024;
3060 } elsif ($unit eq 'G') {
3061 $newsize = $newsize * 1024 * 1024 * 1024;
3062 } elsif ($unit eq 'T') {
3063 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3066 $newsize += $size if $ext;
3067 $newsize = int($newsize);
3069 die "shrinking disks is not supported\n" if $newsize < $size;
3071 return if $size == $newsize;
3073 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3075 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3077 $drive->{size
} = $newsize;
3078 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3080 PVE
::QemuConfig-
>write_config($vmid, $conf);
3083 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3087 __PACKAGE__-
>register_method({
3088 name
=> 'snapshot_list',
3089 path
=> '{vmid}/snapshot',
3091 description
=> "List all snapshots.",
3093 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3096 protected
=> 1, # qemu pid files are only readable by root
3098 additionalProperties
=> 0,
3100 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3101 node
=> get_standard_option
('pve-node'),
3110 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3115 my $vmid = $param->{vmid
};
3117 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3118 my $snaphash = $conf->{snapshots
} || {};
3122 foreach my $name (keys %$snaphash) {
3123 my $d = $snaphash->{$name};
3126 snaptime
=> $d->{snaptime
} || 0,
3127 vmstate
=> $d->{vmstate
} ?
1 : 0,
3128 description
=> $d->{description
} || '',
3130 $item->{parent
} = $d->{parent
} if $d->{parent
};
3131 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3135 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3136 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3137 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3139 push @$res, $current;
3144 __PACKAGE__-
>register_method({
3146 path
=> '{vmid}/snapshot',
3150 description
=> "Snapshot a VM.",
3152 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3155 additionalProperties
=> 0,
3157 node
=> get_standard_option
('pve-node'),
3158 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3159 snapname
=> get_standard_option
('pve-snapshot-name'),
3163 description
=> "Save the vmstate",
3168 description
=> "A textual description or comment.",
3174 description
=> "the task ID.",
3179 my $rpcenv = PVE
::RPCEnvironment
::get
();
3181 my $authuser = $rpcenv->get_user();
3183 my $node = extract_param
($param, 'node');
3185 my $vmid = extract_param
($param, 'vmid');
3187 my $snapname = extract_param
($param, 'snapname');
3189 die "unable to use snapshot name 'current' (reserved name)\n"
3190 if $snapname eq 'current';
3193 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3194 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3195 $param->{description
});
3198 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3201 __PACKAGE__-
>register_method({
3202 name
=> 'snapshot_cmd_idx',
3203 path
=> '{vmid}/snapshot/{snapname}',
3210 additionalProperties
=> 0,
3212 vmid
=> get_standard_option
('pve-vmid'),
3213 node
=> get_standard_option
('pve-node'),
3214 snapname
=> get_standard_option
('pve-snapshot-name'),
3223 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3230 push @$res, { cmd
=> 'rollback' };
3231 push @$res, { cmd
=> 'config' };
3236 __PACKAGE__-
>register_method({
3237 name
=> 'update_snapshot_config',
3238 path
=> '{vmid}/snapshot/{snapname}/config',
3242 description
=> "Update snapshot metadata.",
3244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3247 additionalProperties
=> 0,
3249 node
=> get_standard_option
('pve-node'),
3250 vmid
=> get_standard_option
('pve-vmid'),
3251 snapname
=> get_standard_option
('pve-snapshot-name'),
3255 description
=> "A textual description or comment.",
3259 returns
=> { type
=> 'null' },
3263 my $rpcenv = PVE
::RPCEnvironment
::get
();
3265 my $authuser = $rpcenv->get_user();
3267 my $vmid = extract_param
($param, 'vmid');
3269 my $snapname = extract_param
($param, 'snapname');
3271 return undef if !defined($param->{description
});
3273 my $updatefn = sub {
3275 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3277 PVE
::QemuConfig-
>check_lock($conf);
3279 my $snap = $conf->{snapshots
}->{$snapname};
3281 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3283 $snap->{description
} = $param->{description
} if defined($param->{description
});
3285 PVE
::QemuConfig-
>write_config($vmid, $conf);
3288 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3293 __PACKAGE__-
>register_method({
3294 name
=> 'get_snapshot_config',
3295 path
=> '{vmid}/snapshot/{snapname}/config',
3298 description
=> "Get snapshot configuration",
3300 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3303 additionalProperties
=> 0,
3305 node
=> get_standard_option
('pve-node'),
3306 vmid
=> get_standard_option
('pve-vmid'),
3307 snapname
=> get_standard_option
('pve-snapshot-name'),
3310 returns
=> { type
=> "object" },
3314 my $rpcenv = PVE
::RPCEnvironment
::get
();
3316 my $authuser = $rpcenv->get_user();
3318 my $vmid = extract_param
($param, 'vmid');
3320 my $snapname = extract_param
($param, 'snapname');
3322 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3324 my $snap = $conf->{snapshots
}->{$snapname};
3326 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3331 __PACKAGE__-
>register_method({
3333 path
=> '{vmid}/snapshot/{snapname}/rollback',
3337 description
=> "Rollback VM state to specified snapshot.",
3339 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3342 additionalProperties
=> 0,
3344 node
=> get_standard_option
('pve-node'),
3345 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3346 snapname
=> get_standard_option
('pve-snapshot-name'),
3351 description
=> "the task ID.",
3356 my $rpcenv = PVE
::RPCEnvironment
::get
();
3358 my $authuser = $rpcenv->get_user();
3360 my $node = extract_param
($param, 'node');
3362 my $vmid = extract_param
($param, 'vmid');
3364 my $snapname = extract_param
($param, 'snapname');
3367 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3368 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3371 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3374 __PACKAGE__-
>register_method({
3375 name
=> 'delsnapshot',
3376 path
=> '{vmid}/snapshot/{snapname}',
3380 description
=> "Delete a VM snapshot.",
3382 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3385 additionalProperties
=> 0,
3387 node
=> get_standard_option
('pve-node'),
3388 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3389 snapname
=> get_standard_option
('pve-snapshot-name'),
3393 description
=> "For removal from config file, even if removing disk snapshots fails.",
3399 description
=> "the task ID.",
3404 my $rpcenv = PVE
::RPCEnvironment
::get
();
3406 my $authuser = $rpcenv->get_user();
3408 my $node = extract_param
($param, 'node');
3410 my $vmid = extract_param
($param, 'vmid');
3412 my $snapname = extract_param
($param, 'snapname');
3415 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3416 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3419 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3422 __PACKAGE__-
>register_method({
3424 path
=> '{vmid}/template',
3428 description
=> "Create a Template.",
3430 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3431 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3434 additionalProperties
=> 0,
3436 node
=> get_standard_option
('pve-node'),
3437 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3441 description
=> "If you want to convert only 1 disk to base image.",
3442 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3447 returns
=> { type
=> 'null'},
3451 my $rpcenv = PVE
::RPCEnvironment
::get
();
3453 my $authuser = $rpcenv->get_user();
3455 my $node = extract_param
($param, 'node');
3457 my $vmid = extract_param
($param, 'vmid');
3459 my $disk = extract_param
($param, 'disk');
3461 my $updatefn = sub {
3463 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3465 PVE
::QemuConfig-
>check_lock($conf);
3467 die "unable to create template, because VM contains snapshots\n"
3468 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3470 die "you can't convert a template to a template\n"
3471 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3473 die "you can't convert a VM to template if VM is running\n"
3474 if PVE
::QemuServer
::check_running
($vmid);
3477 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3480 $conf->{template
} = 1;
3481 PVE
::QemuConfig-
>write_config($vmid, $conf);
3483 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3486 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);