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";
1440 if (PVE
::Tools
::run_command
($cmd,
1441 output
=> '>&'.fileno($cli),
1442 input
=> '<&'.fileno($cli),
1445 die "Failed to run vncproxy.\n";
1452 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1454 PVE
::Tools
::wait_for_vnc_port
($port);
1465 __PACKAGE__-
>register_method({
1466 name
=> 'vncwebsocket',
1467 path
=> '{vmid}/vncwebsocket',
1470 description
=> "You also need to pass a valid ticket (vncticket).",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1473 description
=> "Opens a weksocket for VNC traffic.",
1475 additionalProperties
=> 0,
1477 node
=> get_standard_option
('pve-node'),
1478 vmid
=> get_standard_option
('pve-vmid'),
1480 description
=> "Ticket from previous call to vncproxy.",
1485 description
=> "Port number returned by previous vncproxy call.",
1495 port
=> { type
=> 'string' },
1501 my $rpcenv = PVE
::RPCEnvironment
::get
();
1503 my $authuser = $rpcenv->get_user();
1505 my $vmid = $param->{vmid
};
1506 my $node = $param->{node
};
1508 my $authpath = "/vms/$vmid";
1510 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1512 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1514 # Note: VNC ports are acessible from outside, so we do not gain any
1515 # security if we verify that $param->{port} belongs to VM $vmid. This
1516 # check is done by verifying the VNC ticket (inside VNC protocol).
1518 my $port = $param->{port
};
1520 return { port
=> $port };
1523 __PACKAGE__-
>register_method({
1524 name
=> 'spiceproxy',
1525 path
=> '{vmid}/spiceproxy',
1530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1532 description
=> "Returns a SPICE configuration to connect to the VM.",
1534 additionalProperties
=> 0,
1536 node
=> get_standard_option
('pve-node'),
1537 vmid
=> get_standard_option
('pve-vmid'),
1538 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1541 returns
=> get_standard_option
('remote-viewer-config'),
1545 my $rpcenv = PVE
::RPCEnvironment
::get
();
1547 my $authuser = $rpcenv->get_user();
1549 my $vmid = $param->{vmid
};
1550 my $node = $param->{node
};
1551 my $proxy = $param->{proxy
};
1553 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1554 my $title = "VM $vmid";
1555 $title .= " - ". $conf->{name
} if $conf->{name
};
1557 my $port = PVE
::QemuServer
::spice_port
($vmid);
1559 my ($ticket, undef, $remote_viewer_config) =
1560 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1562 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1563 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1565 return $remote_viewer_config;
1568 __PACKAGE__-
>register_method({
1570 path
=> '{vmid}/status',
1573 description
=> "Directory index",
1578 additionalProperties
=> 0,
1580 node
=> get_standard_option
('pve-node'),
1581 vmid
=> get_standard_option
('pve-vmid'),
1589 subdir
=> { type
=> 'string' },
1592 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1598 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1601 { subdir
=> 'current' },
1602 { subdir
=> 'start' },
1603 { subdir
=> 'stop' },
1609 __PACKAGE__-
>register_method({
1610 name
=> 'vm_status',
1611 path
=> '{vmid}/status/current',
1614 protected
=> 1, # qemu pid files are only readable by root
1615 description
=> "Get virtual machine status.",
1617 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1626 returns
=> { type
=> 'object' },
1631 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1633 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1634 my $status = $vmstatus->{$param->{vmid
}};
1636 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1638 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1643 __PACKAGE__-
>register_method({
1645 path
=> '{vmid}/status/start',
1649 description
=> "Start virtual machine.",
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid',
1658 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1659 skiplock
=> get_standard_option
('skiplock'),
1660 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1661 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1664 enum
=> ['secure', 'insecure'],
1665 description
=> "Migration traffic is encrypted using an SSH " .
1666 "tunnel by default. On secure, completely private networks " .
1667 "this can be disabled to increase performance.",
1670 migration_network
=> {
1671 type
=> 'string', format
=> 'CIDR',
1672 description
=> "CIDR of the (sub) network that is used for migration.",
1675 machine
=> get_standard_option
('pve-qm-machine'),
1677 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1689 my $rpcenv = PVE
::RPCEnvironment
::get
();
1691 my $authuser = $rpcenv->get_user();
1693 my $node = extract_param
($param, 'node');
1695 my $vmid = extract_param
($param, 'vmid');
1697 my $machine = extract_param
($param, 'machine');
1699 my $stateuri = extract_param
($param, 'stateuri');
1700 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1701 if $stateuri && $authuser ne 'root@pam';
1703 my $skiplock = extract_param
($param, 'skiplock');
1704 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1705 if $skiplock && $authuser ne 'root@pam';
1707 my $migratedfrom = extract_param
($param, 'migratedfrom');
1708 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1709 if $migratedfrom && $authuser ne 'root@pam';
1711 my $migration_type = extract_param
($param, 'migration_type');
1712 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1713 if $migration_type && $authuser ne 'root@pam';
1715 my $migration_network = extract_param
($param, 'migration_network');
1716 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1717 if $migration_network && $authuser ne 'root@pam';
1719 my $targetstorage = extract_param
($param, 'targetstorage');
1720 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1721 if $targetstorage && $authuser ne 'root@pam';
1723 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1724 if $targetstorage && !$migratedfrom;
1726 # read spice ticket from STDIN
1728 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1729 if (defined(my $line = <>)) {
1731 $spice_ticket = $line;
1735 PVE
::Cluster
::check_cfs_quorum
();
1737 my $storecfg = PVE
::Storage
::config
();
1739 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1740 $rpcenv->{type
} ne 'ha') {
1745 my $service = "vm:$vmid";
1747 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1749 print "Executing HA start for VM $vmid\n";
1751 PVE
::Tools
::run_command
($cmd);
1756 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1763 syslog
('info', "start VM $vmid: $upid\n");
1765 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1766 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1771 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1775 __PACKAGE__-
>register_method({
1777 path
=> '{vmid}/status/stop',
1781 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1782 "is akin to pulling the power plug of a running computer and may damage the VM data",
1784 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid',
1791 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1792 skiplock
=> get_standard_option
('skiplock'),
1793 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1795 description
=> "Wait maximal timeout seconds.",
1801 description
=> "Do not deactivate storage volumes.",
1814 my $rpcenv = PVE
::RPCEnvironment
::get
();
1816 my $authuser = $rpcenv->get_user();
1818 my $node = extract_param
($param, 'node');
1820 my $vmid = extract_param
($param, 'vmid');
1822 my $skiplock = extract_param
($param, 'skiplock');
1823 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1824 if $skiplock && $authuser ne 'root@pam';
1826 my $keepActive = extract_param
($param, 'keepActive');
1827 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1828 if $keepActive && $authuser ne 'root@pam';
1830 my $migratedfrom = extract_param
($param, 'migratedfrom');
1831 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1832 if $migratedfrom && $authuser ne 'root@pam';
1835 my $storecfg = PVE
::Storage
::config
();
1837 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1842 my $service = "vm:$vmid";
1844 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1846 print "Executing HA stop for VM $vmid\n";
1848 PVE
::Tools
::run_command
($cmd);
1853 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1859 syslog
('info', "stop VM $vmid: $upid\n");
1861 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1862 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1867 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1871 __PACKAGE__-
>register_method({
1873 path
=> '{vmid}/status/reset',
1877 description
=> "Reset virtual machine.",
1879 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1882 additionalProperties
=> 0,
1884 node
=> get_standard_option
('pve-node'),
1885 vmid
=> get_standard_option
('pve-vmid',
1886 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1887 skiplock
=> get_standard_option
('skiplock'),
1896 my $rpcenv = PVE
::RPCEnvironment
::get
();
1898 my $authuser = $rpcenv->get_user();
1900 my $node = extract_param
($param, 'node');
1902 my $vmid = extract_param
($param, 'vmid');
1904 my $skiplock = extract_param
($param, 'skiplock');
1905 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1906 if $skiplock && $authuser ne 'root@pam';
1908 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1913 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1918 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1921 __PACKAGE__-
>register_method({
1922 name
=> 'vm_shutdown',
1923 path
=> '{vmid}/status/shutdown',
1927 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1928 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1930 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1933 additionalProperties
=> 0,
1935 node
=> get_standard_option
('pve-node'),
1936 vmid
=> get_standard_option
('pve-vmid',
1937 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1938 skiplock
=> get_standard_option
('skiplock'),
1940 description
=> "Wait maximal timeout seconds.",
1946 description
=> "Make sure the VM stops.",
1952 description
=> "Do not deactivate storage volumes.",
1965 my $rpcenv = PVE
::RPCEnvironment
::get
();
1967 my $authuser = $rpcenv->get_user();
1969 my $node = extract_param
($param, 'node');
1971 my $vmid = extract_param
($param, 'vmid');
1973 my $skiplock = extract_param
($param, 'skiplock');
1974 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1975 if $skiplock && $authuser ne 'root@pam';
1977 my $keepActive = extract_param
($param, 'keepActive');
1978 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1979 if $keepActive && $authuser ne 'root@pam';
1981 my $storecfg = PVE
::Storage
::config
();
1985 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1986 # otherwise, we will infer a shutdown command, but run into the timeout,
1987 # then when the vm is resumed, it will instantly shutdown
1989 # checking the qmp status here to get feedback to the gui/cli/api
1990 # and the status query should not take too long
1993 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1997 if (!$err && $qmpstatus->{status
} eq "paused") {
1998 if ($param->{forceStop
}) {
1999 warn "VM is paused - stop instead of shutdown\n";
2002 die "VM is paused - cannot shutdown\n";
2006 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2007 ($rpcenv->{type
} ne 'ha')) {
2012 my $service = "vm:$vmid";
2014 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2016 print "Executing HA stop for VM $vmid\n";
2018 PVE
::Tools
::run_command
($cmd);
2023 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2030 syslog
('info', "shutdown VM $vmid: $upid\n");
2032 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2033 $shutdown, $param->{forceStop
}, $keepActive);
2038 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2042 __PACKAGE__-
>register_method({
2043 name
=> 'vm_suspend',
2044 path
=> '{vmid}/status/suspend',
2048 description
=> "Suspend virtual machine.",
2050 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2053 additionalProperties
=> 0,
2055 node
=> get_standard_option
('pve-node'),
2056 vmid
=> get_standard_option
('pve-vmid',
2057 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2058 skiplock
=> get_standard_option
('skiplock'),
2067 my $rpcenv = PVE
::RPCEnvironment
::get
();
2069 my $authuser = $rpcenv->get_user();
2071 my $node = extract_param
($param, 'node');
2073 my $vmid = extract_param
($param, 'vmid');
2075 my $skiplock = extract_param
($param, 'skiplock');
2076 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2077 if $skiplock && $authuser ne 'root@pam';
2079 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2084 syslog
('info', "suspend VM $vmid: $upid\n");
2086 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2091 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2094 __PACKAGE__-
>register_method({
2095 name
=> 'vm_resume',
2096 path
=> '{vmid}/status/resume',
2100 description
=> "Resume virtual machine.",
2102 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2105 additionalProperties
=> 0,
2107 node
=> get_standard_option
('pve-node'),
2108 vmid
=> get_standard_option
('pve-vmid',
2109 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2110 skiplock
=> get_standard_option
('skiplock'),
2111 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2121 my $rpcenv = PVE
::RPCEnvironment
::get
();
2123 my $authuser = $rpcenv->get_user();
2125 my $node = extract_param
($param, 'node');
2127 my $vmid = extract_param
($param, 'vmid');
2129 my $skiplock = extract_param
($param, 'skiplock');
2130 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2131 if $skiplock && $authuser ne 'root@pam';
2133 my $nocheck = extract_param
($param, 'nocheck');
2135 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2140 syslog
('info', "resume VM $vmid: $upid\n");
2142 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2147 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2150 __PACKAGE__-
>register_method({
2151 name
=> 'vm_sendkey',
2152 path
=> '{vmid}/sendkey',
2156 description
=> "Send key event to virtual machine.",
2158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2161 additionalProperties
=> 0,
2163 node
=> get_standard_option
('pve-node'),
2164 vmid
=> get_standard_option
('pve-vmid',
2165 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2166 skiplock
=> get_standard_option
('skiplock'),
2168 description
=> "The key (qemu monitor encoding).",
2173 returns
=> { type
=> 'null'},
2177 my $rpcenv = PVE
::RPCEnvironment
::get
();
2179 my $authuser = $rpcenv->get_user();
2181 my $node = extract_param
($param, 'node');
2183 my $vmid = extract_param
($param, 'vmid');
2185 my $skiplock = extract_param
($param, 'skiplock');
2186 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2187 if $skiplock && $authuser ne 'root@pam';
2189 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2194 __PACKAGE__-
>register_method({
2195 name
=> 'vm_feature',
2196 path
=> '{vmid}/feature',
2200 description
=> "Check if feature for virtual machine is available.",
2202 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2205 additionalProperties
=> 0,
2207 node
=> get_standard_option
('pve-node'),
2208 vmid
=> get_standard_option
('pve-vmid'),
2210 description
=> "Feature to check.",
2212 enum
=> [ 'snapshot', 'clone', 'copy' ],
2214 snapname
=> get_standard_option
('pve-snapshot-name', {
2222 hasFeature
=> { type
=> 'boolean' },
2225 items
=> { type
=> 'string' },
2232 my $node = extract_param
($param, 'node');
2234 my $vmid = extract_param
($param, 'vmid');
2236 my $snapname = extract_param
($param, 'snapname');
2238 my $feature = extract_param
($param, 'feature');
2240 my $running = PVE
::QemuServer
::check_running
($vmid);
2242 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2245 my $snap = $conf->{snapshots
}->{$snapname};
2246 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2249 my $storecfg = PVE
::Storage
::config
();
2251 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2252 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2255 hasFeature
=> $hasFeature,
2256 nodes
=> [ keys %$nodelist ],
2260 __PACKAGE__-
>register_method({
2262 path
=> '{vmid}/clone',
2266 description
=> "Create a copy of virtual machine/template.",
2268 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2269 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2270 "'Datastore.AllocateSpace' on any used storage.",
2273 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2275 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2276 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2281 additionalProperties
=> 0,
2283 node
=> get_standard_option
('pve-node'),
2284 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2285 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2288 type
=> 'string', format
=> 'dns-name',
2289 description
=> "Set a name for the new VM.",
2294 description
=> "Description for the new VM.",
2298 type
=> 'string', format
=> 'pve-poolid',
2299 description
=> "Add the new VM to the specified pool.",
2301 snapname
=> get_standard_option
('pve-snapshot-name', {
2304 storage
=> get_standard_option
('pve-storage-id', {
2305 description
=> "Target storage for full clone.",
2310 description
=> "Target format for file storage.",
2314 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2319 description
=> "Create a full copy of all disk. This is always done when " .
2320 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2323 target
=> get_standard_option
('pve-node', {
2324 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2335 my $rpcenv = PVE
::RPCEnvironment
::get
();
2337 my $authuser = $rpcenv->get_user();
2339 my $node = extract_param
($param, 'node');
2341 my $vmid = extract_param
($param, 'vmid');
2343 my $newid = extract_param
($param, 'newid');
2345 my $pool = extract_param
($param, 'pool');
2347 if (defined($pool)) {
2348 $rpcenv->check_pool_exist($pool);
2351 my $snapname = extract_param
($param, 'snapname');
2353 my $storage = extract_param
($param, 'storage');
2355 my $format = extract_param
($param, 'format');
2357 my $target = extract_param
($param, 'target');
2359 my $localnode = PVE
::INotify
::nodename
();
2361 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2363 PVE
::Cluster
::check_node_exists
($target) if $target;
2365 my $storecfg = PVE
::Storage
::config
();
2368 # check if storage is enabled on local node
2369 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2371 # check if storage is available on target node
2372 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2373 # clone only works if target storage is shared
2374 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2375 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2379 PVE
::Cluster
::check_cfs_quorum
();
2381 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2383 # exclusive lock if VM is running - else shared lock is enough;
2384 my $shared_lock = $running ?
0 : 1;
2388 # do all tests after lock
2389 # we also try to do all tests before we fork the worker
2391 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2393 PVE
::QemuConfig-
>check_lock($conf);
2395 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2397 die "unexpected state change\n" if $verify_running != $running;
2399 die "snapshot '$snapname' does not exist\n"
2400 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2402 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2404 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2406 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2408 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2410 die "unable to create VM $newid: config file already exists\n"
2413 my $newconf = { lock => 'clone' };
2418 foreach my $opt (keys %$oldconf) {
2419 my $value = $oldconf->{$opt};
2421 # do not copy snapshot related info
2422 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2423 $opt eq 'vmstate' || $opt eq 'snapstate';
2425 # no need to copy unused images, because VMID(owner) changes anyways
2426 next if $opt =~ m/^unused\d+$/;
2428 # always change MAC! address
2429 if ($opt =~ m/^net(\d+)$/) {
2430 my $net = PVE
::QemuServer
::parse_net
($value);
2431 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2432 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2433 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2434 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2435 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2436 die "unable to parse drive options for '$opt'\n" if !$drive;
2437 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2438 $newconf->{$opt} = $value; # simply copy configuration
2440 if ($param->{full
}) {
2441 die "Full clone feature is not available"
2442 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2443 $fullclone->{$opt} = 1;
2445 # not full means clone instead of copy
2446 die "Linked clone feature is not available"
2447 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2449 $drives->{$opt} = $drive;
2450 push @$vollist, $drive->{file
};
2453 # copy everything else
2454 $newconf->{$opt} = $value;
2458 # auto generate a new uuid
2459 my ($uuid, $uuid_str);
2460 UUID
::generate
($uuid);
2461 UUID
::unparse
($uuid, $uuid_str);
2462 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2463 $smbios1->{uuid
} = $uuid_str;
2464 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2466 delete $newconf->{template
};
2468 if ($param->{name
}) {
2469 $newconf->{name
} = $param->{name
};
2471 if ($oldconf->{name
}) {
2472 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2474 $newconf->{name
} = "Copy-of-VM-$vmid";
2478 if ($param->{description
}) {
2479 $newconf->{description
} = $param->{description
};
2482 # create empty/temp config - this fails if VM already exists on other node
2483 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2488 my $newvollist = [];
2492 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2494 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2496 my $total_jobs = scalar(keys %{$drives});
2499 foreach my $opt (keys %$drives) {
2500 my $drive = $drives->{$opt};
2501 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2503 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2504 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2505 $jobs, $skipcomplete, $oldconf->{agent
});
2507 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2509 PVE
::QemuConfig-
>write_config($newid, $newconf);
2513 delete $newconf->{lock};
2514 PVE
::QemuConfig-
>write_config($newid, $newconf);
2517 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2518 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2519 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2521 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2522 die "Failed to move config to node '$target' - rename failed: $!\n"
2523 if !rename($conffile, $newconffile);
2526 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2531 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2533 sleep 1; # some storage like rbd need to wait before release volume - really?
2535 foreach my $volid (@$newvollist) {
2536 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2539 die "clone failed: $err";
2545 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2547 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2550 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2551 # Aquire exclusive lock lock for $newid
2552 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2557 __PACKAGE__-
>register_method({
2558 name
=> 'move_vm_disk',
2559 path
=> '{vmid}/move_disk',
2563 description
=> "Move volume to different storage.",
2565 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2567 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2568 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2572 additionalProperties
=> 0,
2574 node
=> get_standard_option
('pve-node'),
2575 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2578 description
=> "The disk you want to move.",
2579 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2581 storage
=> get_standard_option
('pve-storage-id', {
2582 description
=> "Target storage.",
2583 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2587 description
=> "Target Format.",
2588 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2593 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2599 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2607 description
=> "the task ID.",
2612 my $rpcenv = PVE
::RPCEnvironment
::get
();
2614 my $authuser = $rpcenv->get_user();
2616 my $node = extract_param
($param, 'node');
2618 my $vmid = extract_param
($param, 'vmid');
2620 my $digest = extract_param
($param, 'digest');
2622 my $disk = extract_param
($param, 'disk');
2624 my $storeid = extract_param
($param, 'storage');
2626 my $format = extract_param
($param, 'format');
2628 my $storecfg = PVE
::Storage
::config
();
2630 my $updatefn = sub {
2632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2634 PVE
::QemuConfig-
>check_lock($conf);
2636 die "checksum missmatch (file change by other user?)\n"
2637 if $digest && $digest ne $conf->{digest
};
2639 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2641 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2643 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2645 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2648 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2649 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2653 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2654 (!$format || !$oldfmt || $oldfmt eq $format);
2656 # this only checks snapshots because $disk is passed!
2657 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2658 die "you can't move a disk with snapshots and delete the source\n"
2659 if $snapshotted && $param->{delete};
2661 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2663 my $running = PVE
::QemuServer
::check_running
($vmid);
2665 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2669 my $newvollist = [];
2672 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2674 warn "moving disk with snapshots, snapshots will not be moved!\n"
2677 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2678 $vmid, $storeid, $format, 1, $newvollist);
2680 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2682 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2684 # convert moved disk to base if part of template
2685 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2686 if PVE
::QemuConfig-
>is_template($conf);
2688 PVE
::QemuConfig-
>write_config($vmid, $conf);
2691 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2692 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2699 foreach my $volid (@$newvollist) {
2700 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2703 die "storage migration failed: $err";
2706 if ($param->{delete}) {
2708 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2709 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2715 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2718 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2721 __PACKAGE__-
>register_method({
2722 name
=> 'migrate_vm',
2723 path
=> '{vmid}/migrate',
2727 description
=> "Migrate virtual machine. Creates a new migration task.",
2729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2732 additionalProperties
=> 0,
2734 node
=> get_standard_option
('pve-node'),
2735 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2736 target
=> get_standard_option
('pve-node', {
2737 description
=> "Target node.",
2738 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2742 description
=> "Use online/live migration.",
2747 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2752 enum
=> ['secure', 'insecure'],
2753 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2756 migration_network
=> {
2757 type
=> 'string', format
=> 'CIDR',
2758 description
=> "CIDR of the (sub) network that is used for migration.",
2761 "with-local-disks" => {
2763 description
=> "Enable live storage migration for local disk",
2766 targetstorage
=> get_standard_option
('pve-storage-id', {
2767 description
=> "Default target storage.",
2769 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2775 description
=> "the task ID.",
2780 my $rpcenv = PVE
::RPCEnvironment
::get
();
2782 my $authuser = $rpcenv->get_user();
2784 my $target = extract_param
($param, 'target');
2786 my $localnode = PVE
::INotify
::nodename
();
2787 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2789 PVE
::Cluster
::check_cfs_quorum
();
2791 PVE
::Cluster
::check_node_exists
($target);
2793 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2795 my $vmid = extract_param
($param, 'vmid');
2797 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2798 if !$param->{online
} && $param->{targetstorage
};
2800 raise_param_exc
({ force
=> "Only root may use this option." })
2801 if $param->{force
} && $authuser ne 'root@pam';
2803 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2804 if $param->{migration_type
} && $authuser ne 'root@pam';
2806 # allow root only until better network permissions are available
2807 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2808 if $param->{migration_network
} && $authuser ne 'root@pam';
2811 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2813 # try to detect errors early
2815 PVE
::QemuConfig-
>check_lock($conf);
2817 if (PVE
::QemuServer
::check_running
($vmid)) {
2818 die "cant migrate running VM without --online\n"
2819 if !$param->{online
};
2822 my $storecfg = PVE
::Storage
::config
();
2824 if( $param->{targetstorage
}) {
2825 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2827 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2830 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2835 my $service = "vm:$vmid";
2837 my $cmd = ['ha-manager', 'migrate', $service, $target];
2839 print "Executing HA migrate for VM $vmid to node $target\n";
2841 PVE
::Tools
::run_command
($cmd);
2846 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2853 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2856 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2861 __PACKAGE__-
>register_method({
2863 path
=> '{vmid}/monitor',
2867 description
=> "Execute Qemu monitor commands.",
2869 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2873 additionalProperties
=> 0,
2875 node
=> get_standard_option
('pve-node'),
2876 vmid
=> get_standard_option
('pve-vmid'),
2879 description
=> "The monitor command.",
2883 returns
=> { type
=> 'string'},
2887 my $rpcenv = PVE
::RPCEnvironment
::get
();
2888 my $authuser = $rpcenv->get_user();
2891 my $command = shift;
2892 return $command =~ m/^\s*info(\s+|$)/
2893 || $command =~ m/^\s*help\s*$/;
2896 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2897 if !&$is_ro($param->{command
});
2899 my $vmid = $param->{vmid
};
2901 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2905 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2907 $res = "ERROR: $@" if $@;
2912 my $guest_agent_commands = [
2920 'network-get-interfaces',
2923 'get-memory-blocks',
2924 'get-memory-block-info',
2931 __PACKAGE__-
>register_method({
2933 path
=> '{vmid}/agent',
2937 description
=> "Execute Qemu Guest Agent commands.",
2939 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2942 additionalProperties
=> 0,
2944 node
=> get_standard_option
('pve-node'),
2945 vmid
=> get_standard_option
('pve-vmid', {
2946 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2949 description
=> "The QGA command.",
2950 enum
=> $guest_agent_commands,
2956 description
=> "Returns an object with a single `result` property. The type of that
2957 property depends on the executed command.",
2962 my $vmid = $param->{vmid
};
2964 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2966 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2967 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2969 my $cmd = $param->{command
};
2971 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2973 return { result
=> $res };
2976 __PACKAGE__-
>register_method({
2977 name
=> 'resize_vm',
2978 path
=> '{vmid}/resize',
2982 description
=> "Extend volume size.",
2984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2987 additionalProperties
=> 0,
2989 node
=> get_standard_option
('pve-node'),
2990 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2991 skiplock
=> get_standard_option
('skiplock'),
2994 description
=> "The disk you want to resize.",
2995 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2999 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3000 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.",
3004 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3010 returns
=> { type
=> 'null'},
3014 my $rpcenv = PVE
::RPCEnvironment
::get
();
3016 my $authuser = $rpcenv->get_user();
3018 my $node = extract_param
($param, 'node');
3020 my $vmid = extract_param
($param, 'vmid');
3022 my $digest = extract_param
($param, 'digest');
3024 my $disk = extract_param
($param, 'disk');
3026 my $sizestr = extract_param
($param, 'size');
3028 my $skiplock = extract_param
($param, 'skiplock');
3029 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3030 if $skiplock && $authuser ne 'root@pam';
3032 my $storecfg = PVE
::Storage
::config
();
3034 my $updatefn = sub {
3036 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3038 die "checksum missmatch (file change by other user?)\n"
3039 if $digest && $digest ne $conf->{digest
};
3040 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3042 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3044 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3046 my (undef, undef, undef, undef, undef, undef, $format) =
3047 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3049 die "can't resize volume: $disk if snapshot exists\n"
3050 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3052 my $volid = $drive->{file
};
3054 die "disk '$disk' has no associated volume\n" if !$volid;
3056 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3058 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3060 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3062 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3063 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3065 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3066 my ($ext, $newsize, $unit) = ($1, $2, $4);
3069 $newsize = $newsize * 1024;
3070 } elsif ($unit eq 'M') {
3071 $newsize = $newsize * 1024 * 1024;
3072 } elsif ($unit eq 'G') {
3073 $newsize = $newsize * 1024 * 1024 * 1024;
3074 } elsif ($unit eq 'T') {
3075 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3078 $newsize += $size if $ext;
3079 $newsize = int($newsize);
3081 die "shrinking disks is not supported\n" if $newsize < $size;
3083 return if $size == $newsize;
3085 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3087 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3089 $drive->{size
} = $newsize;
3090 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3092 PVE
::QemuConfig-
>write_config($vmid, $conf);
3095 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3099 __PACKAGE__-
>register_method({
3100 name
=> 'snapshot_list',
3101 path
=> '{vmid}/snapshot',
3103 description
=> "List all snapshots.",
3105 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3108 protected
=> 1, # qemu pid files are only readable by root
3110 additionalProperties
=> 0,
3112 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3113 node
=> get_standard_option
('pve-node'),
3122 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3127 my $vmid = $param->{vmid
};
3129 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3130 my $snaphash = $conf->{snapshots
} || {};
3134 foreach my $name (keys %$snaphash) {
3135 my $d = $snaphash->{$name};
3138 snaptime
=> $d->{snaptime
} || 0,
3139 vmstate
=> $d->{vmstate
} ?
1 : 0,
3140 description
=> $d->{description
} || '',
3142 $item->{parent
} = $d->{parent
} if $d->{parent
};
3143 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3147 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3148 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3149 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3151 push @$res, $current;
3156 __PACKAGE__-
>register_method({
3158 path
=> '{vmid}/snapshot',
3162 description
=> "Snapshot a VM.",
3164 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3167 additionalProperties
=> 0,
3169 node
=> get_standard_option
('pve-node'),
3170 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3171 snapname
=> get_standard_option
('pve-snapshot-name'),
3175 description
=> "Save the vmstate",
3180 description
=> "A textual description or comment.",
3186 description
=> "the task ID.",
3191 my $rpcenv = PVE
::RPCEnvironment
::get
();
3193 my $authuser = $rpcenv->get_user();
3195 my $node = extract_param
($param, 'node');
3197 my $vmid = extract_param
($param, 'vmid');
3199 my $snapname = extract_param
($param, 'snapname');
3201 die "unable to use snapshot name 'current' (reserved name)\n"
3202 if $snapname eq 'current';
3205 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3206 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3207 $param->{description
});
3210 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3213 __PACKAGE__-
>register_method({
3214 name
=> 'snapshot_cmd_idx',
3215 path
=> '{vmid}/snapshot/{snapname}',
3222 additionalProperties
=> 0,
3224 vmid
=> get_standard_option
('pve-vmid'),
3225 node
=> get_standard_option
('pve-node'),
3226 snapname
=> get_standard_option
('pve-snapshot-name'),
3235 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3242 push @$res, { cmd
=> 'rollback' };
3243 push @$res, { cmd
=> 'config' };
3248 __PACKAGE__-
>register_method({
3249 name
=> 'update_snapshot_config',
3250 path
=> '{vmid}/snapshot/{snapname}/config',
3254 description
=> "Update snapshot metadata.",
3256 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3259 additionalProperties
=> 0,
3261 node
=> get_standard_option
('pve-node'),
3262 vmid
=> get_standard_option
('pve-vmid'),
3263 snapname
=> get_standard_option
('pve-snapshot-name'),
3267 description
=> "A textual description or comment.",
3271 returns
=> { type
=> 'null' },
3275 my $rpcenv = PVE
::RPCEnvironment
::get
();
3277 my $authuser = $rpcenv->get_user();
3279 my $vmid = extract_param
($param, 'vmid');
3281 my $snapname = extract_param
($param, 'snapname');
3283 return undef if !defined($param->{description
});
3285 my $updatefn = sub {
3287 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3289 PVE
::QemuConfig-
>check_lock($conf);
3291 my $snap = $conf->{snapshots
}->{$snapname};
3293 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3295 $snap->{description
} = $param->{description
} if defined($param->{description
});
3297 PVE
::QemuConfig-
>write_config($vmid, $conf);
3300 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3305 __PACKAGE__-
>register_method({
3306 name
=> 'get_snapshot_config',
3307 path
=> '{vmid}/snapshot/{snapname}/config',
3310 description
=> "Get snapshot configuration",
3312 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3315 additionalProperties
=> 0,
3317 node
=> get_standard_option
('pve-node'),
3318 vmid
=> get_standard_option
('pve-vmid'),
3319 snapname
=> get_standard_option
('pve-snapshot-name'),
3322 returns
=> { type
=> "object" },
3326 my $rpcenv = PVE
::RPCEnvironment
::get
();
3328 my $authuser = $rpcenv->get_user();
3330 my $vmid = extract_param
($param, 'vmid');
3332 my $snapname = extract_param
($param, 'snapname');
3334 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3336 my $snap = $conf->{snapshots
}->{$snapname};
3338 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3343 __PACKAGE__-
>register_method({
3345 path
=> '{vmid}/snapshot/{snapname}/rollback',
3349 description
=> "Rollback VM state to specified snapshot.",
3351 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3354 additionalProperties
=> 0,
3356 node
=> get_standard_option
('pve-node'),
3357 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3358 snapname
=> get_standard_option
('pve-snapshot-name'),
3363 description
=> "the task ID.",
3368 my $rpcenv = PVE
::RPCEnvironment
::get
();
3370 my $authuser = $rpcenv->get_user();
3372 my $node = extract_param
($param, 'node');
3374 my $vmid = extract_param
($param, 'vmid');
3376 my $snapname = extract_param
($param, 'snapname');
3379 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3380 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3383 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3386 __PACKAGE__-
>register_method({
3387 name
=> 'delsnapshot',
3388 path
=> '{vmid}/snapshot/{snapname}',
3392 description
=> "Delete a VM snapshot.",
3394 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3397 additionalProperties
=> 0,
3399 node
=> get_standard_option
('pve-node'),
3400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3401 snapname
=> get_standard_option
('pve-snapshot-name'),
3405 description
=> "For removal from config file, even if removing disk snapshots fails.",
3411 description
=> "the task ID.",
3416 my $rpcenv = PVE
::RPCEnvironment
::get
();
3418 my $authuser = $rpcenv->get_user();
3420 my $node = extract_param
($param, 'node');
3422 my $vmid = extract_param
($param, 'vmid');
3424 my $snapname = extract_param
($param, 'snapname');
3427 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3428 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3431 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3434 __PACKAGE__-
>register_method({
3436 path
=> '{vmid}/template',
3440 description
=> "Create a Template.",
3442 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3443 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3446 additionalProperties
=> 0,
3448 node
=> get_standard_option
('pve-node'),
3449 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3453 description
=> "If you want to convert only 1 disk to base image.",
3454 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3459 returns
=> { type
=> 'null'},
3463 my $rpcenv = PVE
::RPCEnvironment
::get
();
3465 my $authuser = $rpcenv->get_user();
3467 my $node = extract_param
($param, 'node');
3469 my $vmid = extract_param
($param, 'vmid');
3471 my $disk = extract_param
($param, 'disk');
3473 my $updatefn = sub {
3475 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3477 PVE
::QemuConfig-
>check_lock($conf);
3479 die "unable to create template, because VM contains snapshots\n"
3480 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3482 die "you can't convert a template to a template\n"
3483 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3485 die "you can't convert a VM to template if VM is running\n"
3486 if PVE
::QemuServer
::check_running
($vmid);
3489 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3492 $conf->{template
} = 1;
3493 PVE
::QemuConfig-
>write_config($vmid, $conf);
3495 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3498 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);