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) = @_;
124 my ($ds, $disk) = @_;
126 my $volid = $disk->{file
};
128 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
129 delete $disk->{size
};
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
131 } elsif ($volid =~ m!^(([^/:\s]+):)?(\d+(\.\d+)?)$!) {
132 my ($storeid, $size) = ($2 || $default_storage, $3);
133 die "no storage ID specified (and no default storage)\n" if !$storeid;
134 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
135 my $fmt = $disk->{format
} || $defformat;
138 if ($ds eq 'efidisk0') {
140 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
141 die "uefi vars image not found\n" if ! -f
$ovmfvars;
142 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
144 $disk->{file
} = $volid;
145 $disk->{size
} = 128*1024;
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
147 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
148 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
149 my $path = PVE
::Storage
::path
($storecfg, $volid);
150 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
151 push @$efidiskcmd, $ovmfvars;
152 push @$efidiskcmd, $path;
154 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
156 eval { PVE
::Tools
::run_command
($efidiskcmd); };
158 die "Copying of EFI Vars image failed: $err" if $err;
160 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
161 $fmt, undef, $size*1024*1024);
162 $disk->{file
} = $volid;
163 $disk->{size
} = $size*1024*1024*1024;
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
170 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
172 my $volid_is_new = 1;
175 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
176 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
181 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
183 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
185 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
187 die "volume $volid does not exists\n" if !$size;
189 $disk->{size
} = $size;
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
196 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
198 # free allocated images on error
200 syslog
('err', "VM $vmid creating disks failed");
201 foreach my $volid (@$vollist) {
202 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
208 # modify vm config if everything went well
209 foreach my $ds (keys %$res) {
210 $conf->{$ds} = $res->{$ds};
227 my $memoryoptions = {
233 my $hwtypeoptions = {
245 my $generaloptions = {
252 'migrate_downtime' => 1,
253 'migrate_speed' => 1,
265 my $vmpoweroptions = {
274 my $check_vm_modify_config_perm = sub {
275 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
277 return 1 if $authuser eq 'root@pam';
279 foreach my $opt (@$key_list) {
280 # disk checks need to be done somewhere else
281 next if PVE
::QemuServer
::is_valid_drivename
($opt);
282 next if $opt eq 'cdrom';
283 next if $opt =~ m/^unused\d+$/;
285 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
287 } elsif ($memoryoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
289 } elsif ($hwtypeoptions->{$opt}) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
291 } elsif ($generaloptions->{$opt}) {
292 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
293 # special case for startup since it changes host behaviour
294 if ($opt eq 'startup') {
295 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
297 } elsif ($vmpoweroptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
299 } elsif ($diskoptions->{$opt}) {
300 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
301 } elsif ($opt =~ m/^net\d+$/) {
302 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
304 # catches usb\d+, hostpci\d+, args, lock, etc.
305 # new options will be checked here
306 die "only root can set '$opt' config\n";
313 __PACKAGE__-
>register_method({
317 description
=> "Virtual machine index (per node).",
319 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
323 protected
=> 1, # qemu pid files are only readable by root
325 additionalProperties
=> 0,
327 node
=> get_standard_option
('pve-node'),
331 description
=> "Determine the full status of active VMs.",
341 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
346 my $rpcenv = PVE
::RPCEnvironment
::get
();
347 my $authuser = $rpcenv->get_user();
349 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
352 foreach my $vmid (keys %$vmstatus) {
353 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
355 my $data = $vmstatus->{$vmid};
356 $data->{vmid
} = int($vmid);
365 __PACKAGE__-
>register_method({
369 description
=> "Create or restore a virtual machine.",
371 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
372 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
373 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
374 user
=> 'all', # check inside
379 additionalProperties
=> 0,
380 properties
=> PVE
::QemuServer
::json_config_properties
(
382 node
=> get_standard_option
('pve-node'),
383 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
385 description
=> "The backup file.",
389 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
391 storage
=> get_standard_option
('pve-storage-id', {
392 description
=> "Default storage.",
394 completion
=> \
&PVE
::QemuServer
::complete_storage
,
399 description
=> "Allow to overwrite existing VM.",
400 requires
=> 'archive',
405 description
=> "Assign a unique random ethernet address.",
406 requires
=> 'archive',
410 type
=> 'string', format
=> 'pve-poolid',
411 description
=> "Add the VM to the specified pool.",
421 my $rpcenv = PVE
::RPCEnvironment
::get
();
423 my $authuser = $rpcenv->get_user();
425 my $node = extract_param
($param, 'node');
427 my $vmid = extract_param
($param, 'vmid');
429 my $archive = extract_param
($param, 'archive');
431 my $storage = extract_param
($param, 'storage');
433 my $force = extract_param
($param, 'force');
435 my $unique = extract_param
($param, 'unique');
437 my $pool = extract_param
($param, 'pool');
439 my $filename = PVE
::QemuConfig-
>config_file($vmid);
441 my $storecfg = PVE
::Storage
::config
();
443 PVE
::Cluster
::check_cfs_quorum
();
445 if (defined($pool)) {
446 $rpcenv->check_pool_exist($pool);
449 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
450 if defined($storage);
452 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
454 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
456 } elsif ($archive && $force && (-f
$filename) &&
457 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
458 # OK: user has VM.Backup permissions, and want to restore an existing VM
464 &$resolve_cdrom_alias($param);
466 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
468 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
470 foreach my $opt (keys %$param) {
471 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
472 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
473 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
475 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
476 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
480 PVE
::QemuServer
::add_random_macs
($param);
482 my $keystr = join(' ', keys %$param);
483 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
485 if ($archive eq '-') {
486 die "pipe requires cli environment\n"
487 if $rpcenv->{type
} ne 'cli';
489 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
490 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
494 my $restorefn = sub {
495 my $vmlist = PVE
::Cluster
::get_vmlist
();
496 if ($vmlist->{ids
}->{$vmid}) {
497 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
498 if ($current_node eq $node) {
499 my $conf = PVE
::QemuConfig-
>load_config($vmid);
501 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
503 die "unable to restore vm $vmid - config file already exists\n"
506 die "unable to restore vm $vmid - vm is running\n"
507 if PVE
::QemuServer
::check_running
($vmid);
509 die "unable to restore vm $vmid - vm is a template\n"
510 if PVE
::QemuConfig-
>is_template($conf);
513 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
518 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
521 unique
=> $unique });
523 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
526 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
532 PVE
::Cluster
::check_vmid_unused
($vmid);
542 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
544 # try to be smart about bootdisk
545 my @disks = PVE
::QemuServer
::valid_drive_names
();
547 foreach my $ds (reverse @disks) {
548 next if !$conf->{$ds};
549 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
550 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
554 if (!$conf->{bootdisk
} && $firstdisk) {
555 $conf->{bootdisk
} = $firstdisk;
558 # auto generate uuid if user did not specify smbios1 option
559 if (!$conf->{smbios1
}) {
560 my ($uuid, $uuid_str);
561 UUID
::generate
($uuid);
562 UUID
::unparse
($uuid, $uuid_str);
563 $conf->{smbios1
} = "uuid=$uuid_str";
566 PVE
::QemuConfig-
>write_config($vmid, $conf);
572 foreach my $volid (@$vollist) {
573 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
576 die "create failed - $err";
579 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
582 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
585 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
588 __PACKAGE__-
>register_method({
593 description
=> "Directory index",
598 additionalProperties
=> 0,
600 node
=> get_standard_option
('pve-node'),
601 vmid
=> get_standard_option
('pve-vmid'),
609 subdir
=> { type
=> 'string' },
612 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
618 { subdir
=> 'config' },
619 { subdir
=> 'pending' },
620 { subdir
=> 'status' },
621 { subdir
=> 'unlink' },
622 { subdir
=> 'vncproxy' },
623 { subdir
=> 'migrate' },
624 { subdir
=> 'resize' },
625 { subdir
=> 'move' },
627 { subdir
=> 'rrddata' },
628 { subdir
=> 'monitor' },
629 { subdir
=> 'agent' },
630 { subdir
=> 'snapshot' },
631 { subdir
=> 'spiceproxy' },
632 { subdir
=> 'sendkey' },
633 { subdir
=> 'firewall' },
639 __PACKAGE__-
>register_method ({
640 subclass
=> "PVE::API2::Firewall::VM",
641 path
=> '{vmid}/firewall',
644 __PACKAGE__-
>register_method({
646 path
=> '{vmid}/rrd',
648 protected
=> 1, # fixme: can we avoid that?
650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
652 description
=> "Read VM RRD statistics (returns PNG)",
654 additionalProperties
=> 0,
656 node
=> get_standard_option
('pve-node'),
657 vmid
=> get_standard_option
('pve-vmid'),
659 description
=> "Specify the time frame you are interested in.",
661 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
664 description
=> "The list of datasources you want to display.",
665 type
=> 'string', format
=> 'pve-configid-list',
668 description
=> "The RRD consolidation function",
670 enum
=> [ 'AVERAGE', 'MAX' ],
678 filename
=> { type
=> 'string' },
684 return PVE
::Cluster
::create_rrd_graph
(
685 "pve2-vm/$param->{vmid}", $param->{timeframe
},
686 $param->{ds
}, $param->{cf
});
690 __PACKAGE__-
>register_method({
692 path
=> '{vmid}/rrddata',
694 protected
=> 1, # fixme: can we avoid that?
696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
698 description
=> "Read VM RRD statistics",
700 additionalProperties
=> 0,
702 node
=> get_standard_option
('pve-node'),
703 vmid
=> get_standard_option
('pve-vmid'),
705 description
=> "Specify the time frame you are interested in.",
707 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
710 description
=> "The RRD consolidation function",
712 enum
=> [ 'AVERAGE', 'MAX' ],
727 return PVE
::Cluster
::create_rrd_data
(
728 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
732 __PACKAGE__-
>register_method({
734 path
=> '{vmid}/config',
737 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
742 additionalProperties
=> 0,
744 node
=> get_standard_option
('pve-node'),
745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
747 description
=> "Get current values (instead of pending values).",
759 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
766 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
768 delete $conf->{snapshots
};
770 if (!$param->{current
}) {
771 foreach my $opt (keys %{$conf->{pending
}}) {
772 next if $opt eq 'delete';
773 my $value = $conf->{pending
}->{$opt};
774 next if ref($value); # just to be sure
775 $conf->{$opt} = $value;
777 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
778 foreach my $opt (keys %$pending_delete_hash) {
779 delete $conf->{$opt} if $conf->{$opt};
783 delete $conf->{pending
};
788 __PACKAGE__-
>register_method({
789 name
=> 'vm_pending',
790 path
=> '{vmid}/pending',
793 description
=> "Get virtual machine configuration, including pending changes.",
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
798 additionalProperties
=> 0,
800 node
=> get_standard_option
('pve-node'),
801 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
810 description
=> "Configuration option name.",
814 description
=> "Current value.",
819 description
=> "Pending value.",
824 description
=> "Indicates a pending delete request if present and not 0. " .
825 "The value 2 indicates a force-delete request.",
837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
839 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
843 foreach my $opt (keys %$conf) {
844 next if ref($conf->{$opt});
845 my $item = { key
=> $opt };
846 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
847 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
848 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
852 foreach my $opt (keys %{$conf->{pending
}}) {
853 next if $opt eq 'delete';
854 next if ref($conf->{pending
}->{$opt}); # just to be sure
855 next if defined($conf->{$opt});
856 my $item = { key
=> $opt };
857 $item->{pending
} = $conf->{pending
}->{$opt};
861 while (my ($opt, $force) = each %$pending_delete_hash) {
862 next if $conf->{pending
}->{$opt}; # just to be sure
863 next if $conf->{$opt};
864 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
871 # POST/PUT {vmid}/config implementation
873 # The original API used PUT (idempotent) an we assumed that all operations
874 # are fast. But it turned out that almost any configuration change can
875 # involve hot-plug actions, or disk alloc/free. Such actions can take long
876 # time to complete and have side effects (not idempotent).
878 # The new implementation uses POST and forks a worker process. We added
879 # a new option 'background_delay'. If specified we wait up to
880 # 'background_delay' second for the worker task to complete. It returns null
881 # if the task is finished within that time, else we return the UPID.
883 my $update_vm_api = sub {
884 my ($param, $sync) = @_;
886 my $rpcenv = PVE
::RPCEnvironment
::get
();
888 my $authuser = $rpcenv->get_user();
890 my $node = extract_param
($param, 'node');
892 my $vmid = extract_param
($param, 'vmid');
894 my $digest = extract_param
($param, 'digest');
896 my $background_delay = extract_param
($param, 'background_delay');
898 my @paramarr = (); # used for log message
899 foreach my $key (keys %$param) {
900 push @paramarr, "-$key", $param->{$key};
903 my $skiplock = extract_param
($param, 'skiplock');
904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
905 if $skiplock && $authuser ne 'root@pam';
907 my $delete_str = extract_param
($param, 'delete');
909 my $revert_str = extract_param
($param, 'revert');
911 my $force = extract_param
($param, 'force');
913 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
915 my $storecfg = PVE
::Storage
::config
();
917 my $defaults = PVE
::QemuServer
::load_defaults
();
919 &$resolve_cdrom_alias($param);
921 # now try to verify all parameters
924 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
925 if (!PVE
::QemuServer
::option_exists
($opt)) {
926 raise_param_exc
({ revert
=> "unknown option '$opt'" });
929 raise_param_exc
({ delete => "you can't use '-$opt' and " .
930 "-revert $opt' at the same time" })
931 if defined($param->{$opt});
937 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
938 $opt = 'ide2' if $opt eq 'cdrom';
940 raise_param_exc
({ delete => "you can't use '-$opt' and " .
941 "-delete $opt' at the same time" })
942 if defined($param->{$opt});
944 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
945 "-revert $opt' at the same time" })
948 if (!PVE
::QemuServer
::option_exists
($opt)) {
949 raise_param_exc
({ delete => "unknown option '$opt'" });
955 foreach my $opt (keys %$param) {
956 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
958 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
959 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
960 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
961 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
962 } elsif ($opt =~ m/^net(\d+)$/) {
964 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
965 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
969 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
971 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
973 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
977 my $conf = PVE
::QemuConfig-
>load_config($vmid);
979 die "checksum missmatch (file change by other user?)\n"
980 if $digest && $digest ne $conf->{digest
};
982 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
984 foreach my $opt (keys %$revert) {
985 if (defined($conf->{$opt})) {
986 $param->{$opt} = $conf->{$opt};
987 } elsif (defined($conf->{pending
}->{$opt})) {
992 if ($param->{memory
} || defined($param->{balloon
})) {
993 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
994 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
996 die "balloon value too large (must be smaller than assigned memory)\n"
997 if $balloon && $balloon > $maxmem;
1000 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1004 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1006 # write updates to pending section
1008 my $modified = {}; # record what $option we modify
1010 foreach my $opt (@delete) {
1011 $modified->{$opt} = 1;
1012 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1013 if (!defined($conf->{$opt})) {
1014 warn "cannot delete '$opt' - not set in current configuration!\n";
1015 $modified->{$opt} = 0;
1019 if ($opt =~ m/^unused/) {
1020 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1021 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1022 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1023 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1024 delete $conf->{$opt};
1025 PVE
::QemuConfig-
>write_config($vmid, $conf);
1027 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1028 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1029 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1030 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1031 if defined($conf->{pending
}->{$opt});
1032 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1033 PVE
::QemuConfig-
>write_config($vmid, $conf);
1035 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1036 PVE
::QemuConfig-
>write_config($vmid, $conf);
1040 foreach my $opt (keys %$param) { # add/change
1041 $modified->{$opt} = 1;
1042 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1043 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1045 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1046 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1047 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1050 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1052 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1053 if defined($conf->{pending
}->{$opt});
1055 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1057 $conf->{pending
}->{$opt} = $param->{$opt};
1059 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1060 PVE
::QemuConfig-
>write_config($vmid, $conf);
1063 # remove pending changes when nothing changed
1064 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1065 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1066 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1068 return if !scalar(keys %{$conf->{pending
}});
1070 my $running = PVE
::QemuServer
::check_running
($vmid);
1072 # apply pending changes
1074 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1078 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1079 raise_param_exc
($errors) if scalar(keys %$errors);
1081 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1091 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1093 if ($background_delay) {
1095 # Note: It would be better to do that in the Event based HTTPServer
1096 # to avoid blocking call to sleep.
1098 my $end_time = time() + $background_delay;
1100 my $task = PVE
::Tools
::upid_decode
($upid);
1103 while (time() < $end_time) {
1104 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1106 sleep(1); # this gets interrupted when child process ends
1110 my $status = PVE
::Tools
::upid_read_status
($upid);
1111 return undef if $status eq 'OK';
1120 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1123 my $vm_config_perm_list = [
1128 'VM.Config.Network',
1130 'VM.Config.Options',
1133 __PACKAGE__-
>register_method({
1134 name
=> 'update_vm_async',
1135 path
=> '{vmid}/config',
1139 description
=> "Set virtual machine options (asynchrounous API).",
1141 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1144 additionalProperties
=> 0,
1145 properties
=> PVE
::QemuServer
::json_config_properties
(
1147 node
=> get_standard_option
('pve-node'),
1148 vmid
=> get_standard_option
('pve-vmid'),
1149 skiplock
=> get_standard_option
('skiplock'),
1151 type
=> 'string', format
=> 'pve-configid-list',
1152 description
=> "A list of settings you want to delete.",
1156 type
=> 'string', format
=> 'pve-configid-list',
1157 description
=> "Revert a pending change.",
1162 description
=> $opt_force_description,
1164 requires
=> 'delete',
1168 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1172 background_delay
=> {
1174 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1185 code
=> $update_vm_api,
1188 __PACKAGE__-
>register_method({
1189 name
=> 'update_vm',
1190 path
=> '{vmid}/config',
1194 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1196 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1199 additionalProperties
=> 0,
1200 properties
=> PVE
::QemuServer
::json_config_properties
(
1202 node
=> get_standard_option
('pve-node'),
1203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1204 skiplock
=> get_standard_option
('skiplock'),
1206 type
=> 'string', format
=> 'pve-configid-list',
1207 description
=> "A list of settings you want to delete.",
1211 type
=> 'string', format
=> 'pve-configid-list',
1212 description
=> "Revert a pending change.",
1217 description
=> $opt_force_description,
1219 requires
=> 'delete',
1223 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1229 returns
=> { type
=> 'null' },
1232 &$update_vm_api($param, 1);
1238 __PACKAGE__-
>register_method({
1239 name
=> 'destroy_vm',
1244 description
=> "Destroy the vm (also delete all used/owned volumes).",
1246 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1249 additionalProperties
=> 0,
1251 node
=> get_standard_option
('pve-node'),
1252 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1253 skiplock
=> get_standard_option
('skiplock'),
1262 my $rpcenv = PVE
::RPCEnvironment
::get
();
1264 my $authuser = $rpcenv->get_user();
1266 my $vmid = $param->{vmid
};
1268 my $skiplock = $param->{skiplock
};
1269 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1270 if $skiplock && $authuser ne 'root@pam';
1273 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1275 my $storecfg = PVE
::Storage
::config
();
1277 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1279 die "unable to remove VM $vmid - used in HA resources\n"
1280 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1282 # early tests (repeat after locking)
1283 die "VM $vmid is running - destroy failed\n"
1284 if PVE
::QemuServer
::check_running
($vmid);
1289 syslog
('info', "destroy VM $vmid: $upid\n");
1291 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1293 PVE
::AccessControl
::remove_vm_access
($vmid);
1295 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1298 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1301 __PACKAGE__-
>register_method({
1303 path
=> '{vmid}/unlink',
1307 description
=> "Unlink/delete disk images.",
1309 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1312 additionalProperties
=> 0,
1314 node
=> get_standard_option
('pve-node'),
1315 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1317 type
=> 'string', format
=> 'pve-configid-list',
1318 description
=> "A list of disk IDs you want to delete.",
1322 description
=> $opt_force_description,
1327 returns
=> { type
=> 'null'},
1331 $param->{delete} = extract_param
($param, 'idlist');
1333 __PACKAGE__-
>update_vm($param);
1340 __PACKAGE__-
>register_method({
1342 path
=> '{vmid}/vncproxy',
1346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1348 description
=> "Creates a TCP VNC proxy connections.",
1350 additionalProperties
=> 0,
1352 node
=> get_standard_option
('pve-node'),
1353 vmid
=> get_standard_option
('pve-vmid'),
1357 description
=> "starts websockify instead of vncproxy",
1362 additionalProperties
=> 0,
1364 user
=> { type
=> 'string' },
1365 ticket
=> { type
=> 'string' },
1366 cert
=> { type
=> 'string' },
1367 port
=> { type
=> 'integer' },
1368 upid
=> { type
=> 'string' },
1374 my $rpcenv = PVE
::RPCEnvironment
::get
();
1376 my $authuser = $rpcenv->get_user();
1378 my $vmid = $param->{vmid
};
1379 my $node = $param->{node
};
1380 my $websocket = $param->{websocket
};
1382 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1384 my $authpath = "/vms/$vmid";
1386 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1388 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1391 my ($remip, $family);
1394 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1395 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1396 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1397 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1399 $family = PVE
::Tools
::get_host_address_family
($node);
1402 my $port = PVE
::Tools
::next_vnc_port
($family);
1409 syslog
('info', "starting vnc proxy $upid\n");
1413 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1415 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1417 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1418 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1419 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1420 '-timeout', $timeout, '-authpath', $authpath,
1421 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1422 PVE
::Tools
::run_command
($cmd);
1425 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1427 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1429 my $sock = IO
::Socket
::IP-
>new(
1434 GetAddrInfoFlags
=> 0,
1435 ) or die "failed to create socket: $!\n";
1436 # Inside the worker we shouldn't have any previous alarms
1437 # running anyway...:
1439 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1441 accept(my $cli, $sock) or die "connection failed: $!\n";
1444 if (PVE
::Tools
::run_command
($cmd,
1445 output
=> '>&'.fileno($cli),
1446 input
=> '<&'.fileno($cli),
1449 die "Failed to run vncproxy.\n";
1456 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1458 PVE
::Tools
::wait_for_vnc_port
($port);
1469 __PACKAGE__-
>register_method({
1470 name
=> 'vncwebsocket',
1471 path
=> '{vmid}/vncwebsocket',
1474 description
=> "You also need to pass a valid ticket (vncticket).",
1475 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1477 description
=> "Opens a weksocket for VNC traffic.",
1479 additionalProperties
=> 0,
1481 node
=> get_standard_option
('pve-node'),
1482 vmid
=> get_standard_option
('pve-vmid'),
1484 description
=> "Ticket from previous call to vncproxy.",
1489 description
=> "Port number returned by previous vncproxy call.",
1499 port
=> { type
=> 'string' },
1505 my $rpcenv = PVE
::RPCEnvironment
::get
();
1507 my $authuser = $rpcenv->get_user();
1509 my $vmid = $param->{vmid
};
1510 my $node = $param->{node
};
1512 my $authpath = "/vms/$vmid";
1514 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1516 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1518 # Note: VNC ports are acessible from outside, so we do not gain any
1519 # security if we verify that $param->{port} belongs to VM $vmid. This
1520 # check is done by verifying the VNC ticket (inside VNC protocol).
1522 my $port = $param->{port
};
1524 return { port
=> $port };
1527 __PACKAGE__-
>register_method({
1528 name
=> 'spiceproxy',
1529 path
=> '{vmid}/spiceproxy',
1534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1536 description
=> "Returns a SPICE configuration to connect to the VM.",
1538 additionalProperties
=> 0,
1540 node
=> get_standard_option
('pve-node'),
1541 vmid
=> get_standard_option
('pve-vmid'),
1542 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1545 returns
=> get_standard_option
('remote-viewer-config'),
1549 my $rpcenv = PVE
::RPCEnvironment
::get
();
1551 my $authuser = $rpcenv->get_user();
1553 my $vmid = $param->{vmid
};
1554 my $node = $param->{node
};
1555 my $proxy = $param->{proxy
};
1557 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1558 my $title = "VM $vmid";
1559 $title .= " - ". $conf->{name
} if $conf->{name
};
1561 my $port = PVE
::QemuServer
::spice_port
($vmid);
1563 my ($ticket, undef, $remote_viewer_config) =
1564 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1566 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1567 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1569 return $remote_viewer_config;
1572 __PACKAGE__-
>register_method({
1574 path
=> '{vmid}/status',
1577 description
=> "Directory index",
1582 additionalProperties
=> 0,
1584 node
=> get_standard_option
('pve-node'),
1585 vmid
=> get_standard_option
('pve-vmid'),
1593 subdir
=> { type
=> 'string' },
1596 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1602 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1605 { subdir
=> 'current' },
1606 { subdir
=> 'start' },
1607 { subdir
=> 'stop' },
1613 __PACKAGE__-
>register_method({
1614 name
=> 'vm_status',
1615 path
=> '{vmid}/status/current',
1618 protected
=> 1, # qemu pid files are only readable by root
1619 description
=> "Get virtual machine status.",
1621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1624 additionalProperties
=> 0,
1626 node
=> get_standard_option
('pve-node'),
1627 vmid
=> get_standard_option
('pve-vmid'),
1630 returns
=> { type
=> 'object' },
1635 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1637 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1638 my $status = $vmstatus->{$param->{vmid
}};
1640 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1642 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1647 __PACKAGE__-
>register_method({
1649 path
=> '{vmid}/status/start',
1653 description
=> "Start virtual machine.",
1655 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1658 additionalProperties
=> 0,
1660 node
=> get_standard_option
('pve-node'),
1661 vmid
=> get_standard_option
('pve-vmid',
1662 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1663 skiplock
=> get_standard_option
('skiplock'),
1664 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1665 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1668 enum
=> ['secure', 'insecure'],
1669 description
=> "Migration traffic is encrypted using an SSH " .
1670 "tunnel by default. On secure, completely private networks " .
1671 "this can be disabled to increase performance.",
1674 migration_network
=> {
1675 type
=> 'string', format
=> 'CIDR',
1676 description
=> "CIDR of the (sub) network that is used for migration.",
1679 machine
=> get_standard_option
('pve-qm-machine'),
1681 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1693 my $rpcenv = PVE
::RPCEnvironment
::get
();
1695 my $authuser = $rpcenv->get_user();
1697 my $node = extract_param
($param, 'node');
1699 my $vmid = extract_param
($param, 'vmid');
1701 my $machine = extract_param
($param, 'machine');
1703 my $stateuri = extract_param
($param, 'stateuri');
1704 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1705 if $stateuri && $authuser ne 'root@pam';
1707 my $skiplock = extract_param
($param, 'skiplock');
1708 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1709 if $skiplock && $authuser ne 'root@pam';
1711 my $migratedfrom = extract_param
($param, 'migratedfrom');
1712 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1713 if $migratedfrom && $authuser ne 'root@pam';
1715 my $migration_type = extract_param
($param, 'migration_type');
1716 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1717 if $migration_type && $authuser ne 'root@pam';
1719 my $migration_network = extract_param
($param, 'migration_network');
1720 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1721 if $migration_network && $authuser ne 'root@pam';
1723 my $targetstorage = extract_param
($param, 'targetstorage');
1724 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1725 if $targetstorage && $authuser ne 'root@pam';
1727 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1728 if $targetstorage && !$migratedfrom;
1730 # read spice ticket from STDIN
1732 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1733 if (defined(my $line = <>)) {
1735 $spice_ticket = $line;
1739 PVE
::Cluster
::check_cfs_quorum
();
1741 my $storecfg = PVE
::Storage
::config
();
1743 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1744 $rpcenv->{type
} ne 'ha') {
1749 my $service = "vm:$vmid";
1751 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1753 print "Executing HA start for VM $vmid\n";
1755 PVE
::Tools
::run_command
($cmd);
1760 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1767 syslog
('info', "start VM $vmid: $upid\n");
1769 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1770 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1775 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1779 __PACKAGE__-
>register_method({
1781 path
=> '{vmid}/status/stop',
1785 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1786 "is akin to pulling the power plug of a running computer and may damage the VM data",
1788 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1791 additionalProperties
=> 0,
1793 node
=> get_standard_option
('pve-node'),
1794 vmid
=> get_standard_option
('pve-vmid',
1795 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1796 skiplock
=> get_standard_option
('skiplock'),
1797 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1799 description
=> "Wait maximal timeout seconds.",
1805 description
=> "Do not deactivate storage volumes.",
1818 my $rpcenv = PVE
::RPCEnvironment
::get
();
1820 my $authuser = $rpcenv->get_user();
1822 my $node = extract_param
($param, 'node');
1824 my $vmid = extract_param
($param, 'vmid');
1826 my $skiplock = extract_param
($param, 'skiplock');
1827 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1828 if $skiplock && $authuser ne 'root@pam';
1830 my $keepActive = extract_param
($param, 'keepActive');
1831 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1832 if $keepActive && $authuser ne 'root@pam';
1834 my $migratedfrom = extract_param
($param, 'migratedfrom');
1835 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1836 if $migratedfrom && $authuser ne 'root@pam';
1839 my $storecfg = PVE
::Storage
::config
();
1841 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1846 my $service = "vm:$vmid";
1848 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1850 print "Executing HA stop for VM $vmid\n";
1852 PVE
::Tools
::run_command
($cmd);
1857 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1863 syslog
('info', "stop VM $vmid: $upid\n");
1865 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1866 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1871 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1875 __PACKAGE__-
>register_method({
1877 path
=> '{vmid}/status/reset',
1881 description
=> "Reset virtual machine.",
1883 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1886 additionalProperties
=> 0,
1888 node
=> get_standard_option
('pve-node'),
1889 vmid
=> get_standard_option
('pve-vmid',
1890 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1891 skiplock
=> get_standard_option
('skiplock'),
1900 my $rpcenv = PVE
::RPCEnvironment
::get
();
1902 my $authuser = $rpcenv->get_user();
1904 my $node = extract_param
($param, 'node');
1906 my $vmid = extract_param
($param, 'vmid');
1908 my $skiplock = extract_param
($param, 'skiplock');
1909 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1910 if $skiplock && $authuser ne 'root@pam';
1912 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1917 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1922 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_shutdown',
1927 path
=> '{vmid}/status/shutdown',
1931 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1932 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1934 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1937 additionalProperties
=> 0,
1939 node
=> get_standard_option
('pve-node'),
1940 vmid
=> get_standard_option
('pve-vmid',
1941 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1942 skiplock
=> get_standard_option
('skiplock'),
1944 description
=> "Wait maximal timeout seconds.",
1950 description
=> "Make sure the VM stops.",
1956 description
=> "Do not deactivate storage volumes.",
1969 my $rpcenv = PVE
::RPCEnvironment
::get
();
1971 my $authuser = $rpcenv->get_user();
1973 my $node = extract_param
($param, 'node');
1975 my $vmid = extract_param
($param, 'vmid');
1977 my $skiplock = extract_param
($param, 'skiplock');
1978 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1979 if $skiplock && $authuser ne 'root@pam';
1981 my $keepActive = extract_param
($param, 'keepActive');
1982 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1983 if $keepActive && $authuser ne 'root@pam';
1985 my $storecfg = PVE
::Storage
::config
();
1989 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1990 # otherwise, we will infer a shutdown command, but run into the timeout,
1991 # then when the vm is resumed, it will instantly shutdown
1993 # checking the qmp status here to get feedback to the gui/cli/api
1994 # and the status query should not take too long
1997 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2001 if (!$err && $qmpstatus->{status
} eq "paused") {
2002 if ($param->{forceStop
}) {
2003 warn "VM is paused - stop instead of shutdown\n";
2006 die "VM is paused - cannot shutdown\n";
2010 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2011 ($rpcenv->{type
} ne 'ha')) {
2016 my $service = "vm:$vmid";
2018 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2020 print "Executing HA stop for VM $vmid\n";
2022 PVE
::Tools
::run_command
($cmd);
2027 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2034 syslog
('info', "shutdown VM $vmid: $upid\n");
2036 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2037 $shutdown, $param->{forceStop
}, $keepActive);
2042 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2046 __PACKAGE__-
>register_method({
2047 name
=> 'vm_suspend',
2048 path
=> '{vmid}/status/suspend',
2052 description
=> "Suspend virtual machine.",
2054 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2057 additionalProperties
=> 0,
2059 node
=> get_standard_option
('pve-node'),
2060 vmid
=> get_standard_option
('pve-vmid',
2061 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2062 skiplock
=> get_standard_option
('skiplock'),
2071 my $rpcenv = PVE
::RPCEnvironment
::get
();
2073 my $authuser = $rpcenv->get_user();
2075 my $node = extract_param
($param, 'node');
2077 my $vmid = extract_param
($param, 'vmid');
2079 my $skiplock = extract_param
($param, 'skiplock');
2080 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2081 if $skiplock && $authuser ne 'root@pam';
2083 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2088 syslog
('info', "suspend VM $vmid: $upid\n");
2090 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2095 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2098 __PACKAGE__-
>register_method({
2099 name
=> 'vm_resume',
2100 path
=> '{vmid}/status/resume',
2104 description
=> "Resume virtual machine.",
2106 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2109 additionalProperties
=> 0,
2111 node
=> get_standard_option
('pve-node'),
2112 vmid
=> get_standard_option
('pve-vmid',
2113 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2114 skiplock
=> get_standard_option
('skiplock'),
2115 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2125 my $rpcenv = PVE
::RPCEnvironment
::get
();
2127 my $authuser = $rpcenv->get_user();
2129 my $node = extract_param
($param, 'node');
2131 my $vmid = extract_param
($param, 'vmid');
2133 my $skiplock = extract_param
($param, 'skiplock');
2134 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2135 if $skiplock && $authuser ne 'root@pam';
2137 my $nocheck = extract_param
($param, 'nocheck');
2139 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2144 syslog
('info', "resume VM $vmid: $upid\n");
2146 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2151 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2154 __PACKAGE__-
>register_method({
2155 name
=> 'vm_sendkey',
2156 path
=> '{vmid}/sendkey',
2160 description
=> "Send key event to virtual machine.",
2162 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2165 additionalProperties
=> 0,
2167 node
=> get_standard_option
('pve-node'),
2168 vmid
=> get_standard_option
('pve-vmid',
2169 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2170 skiplock
=> get_standard_option
('skiplock'),
2172 description
=> "The key (qemu monitor encoding).",
2177 returns
=> { type
=> 'null'},
2181 my $rpcenv = PVE
::RPCEnvironment
::get
();
2183 my $authuser = $rpcenv->get_user();
2185 my $node = extract_param
($param, 'node');
2187 my $vmid = extract_param
($param, 'vmid');
2189 my $skiplock = extract_param
($param, 'skiplock');
2190 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2191 if $skiplock && $authuser ne 'root@pam';
2193 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2198 __PACKAGE__-
>register_method({
2199 name
=> 'vm_feature',
2200 path
=> '{vmid}/feature',
2204 description
=> "Check if feature for virtual machine is available.",
2206 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2209 additionalProperties
=> 0,
2211 node
=> get_standard_option
('pve-node'),
2212 vmid
=> get_standard_option
('pve-vmid'),
2214 description
=> "Feature to check.",
2216 enum
=> [ 'snapshot', 'clone', 'copy' ],
2218 snapname
=> get_standard_option
('pve-snapshot-name', {
2226 hasFeature
=> { type
=> 'boolean' },
2229 items
=> { type
=> 'string' },
2236 my $node = extract_param
($param, 'node');
2238 my $vmid = extract_param
($param, 'vmid');
2240 my $snapname = extract_param
($param, 'snapname');
2242 my $feature = extract_param
($param, 'feature');
2244 my $running = PVE
::QemuServer
::check_running
($vmid);
2246 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2249 my $snap = $conf->{snapshots
}->{$snapname};
2250 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2253 my $storecfg = PVE
::Storage
::config
();
2255 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2256 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2259 hasFeature
=> $hasFeature,
2260 nodes
=> [ keys %$nodelist ],
2264 __PACKAGE__-
>register_method({
2266 path
=> '{vmid}/clone',
2270 description
=> "Create a copy of virtual machine/template.",
2272 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2273 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2274 "'Datastore.AllocateSpace' on any used storage.",
2277 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2279 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2280 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2285 additionalProperties
=> 0,
2287 node
=> get_standard_option
('pve-node'),
2288 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2289 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2292 type
=> 'string', format
=> 'dns-name',
2293 description
=> "Set a name for the new VM.",
2298 description
=> "Description for the new VM.",
2302 type
=> 'string', format
=> 'pve-poolid',
2303 description
=> "Add the new VM to the specified pool.",
2305 snapname
=> get_standard_option
('pve-snapshot-name', {
2308 storage
=> get_standard_option
('pve-storage-id', {
2309 description
=> "Target storage for full clone.",
2314 description
=> "Target format for file storage.",
2318 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2323 description
=> "Create a full copy of all disk. This is always done when " .
2324 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2327 target
=> get_standard_option
('pve-node', {
2328 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2339 my $rpcenv = PVE
::RPCEnvironment
::get
();
2341 my $authuser = $rpcenv->get_user();
2343 my $node = extract_param
($param, 'node');
2345 my $vmid = extract_param
($param, 'vmid');
2347 my $newid = extract_param
($param, 'newid');
2349 my $pool = extract_param
($param, 'pool');
2351 if (defined($pool)) {
2352 $rpcenv->check_pool_exist($pool);
2355 my $snapname = extract_param
($param, 'snapname');
2357 my $storage = extract_param
($param, 'storage');
2359 my $format = extract_param
($param, 'format');
2361 my $target = extract_param
($param, 'target');
2363 my $localnode = PVE
::INotify
::nodename
();
2365 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2367 PVE
::Cluster
::check_node_exists
($target) if $target;
2369 my $storecfg = PVE
::Storage
::config
();
2372 # check if storage is enabled on local node
2373 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2375 # check if storage is available on target node
2376 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2377 # clone only works if target storage is shared
2378 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2379 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2383 PVE
::Cluster
::check_cfs_quorum
();
2385 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2387 # exclusive lock if VM is running - else shared lock is enough;
2388 my $shared_lock = $running ?
0 : 1;
2392 # do all tests after lock
2393 # we also try to do all tests before we fork the worker
2395 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2397 PVE
::QemuConfig-
>check_lock($conf);
2399 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2401 die "unexpected state change\n" if $verify_running != $running;
2403 die "snapshot '$snapname' does not exist\n"
2404 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2406 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2408 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2410 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2412 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2414 die "unable to create VM $newid: config file already exists\n"
2417 my $newconf = { lock => 'clone' };
2422 foreach my $opt (keys %$oldconf) {
2423 my $value = $oldconf->{$opt};
2425 # do not copy snapshot related info
2426 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2427 $opt eq 'vmstate' || $opt eq 'snapstate';
2429 # no need to copy unused images, because VMID(owner) changes anyways
2430 next if $opt =~ m/^unused\d+$/;
2432 # always change MAC! address
2433 if ($opt =~ m/^net(\d+)$/) {
2434 my $net = PVE
::QemuServer
::parse_net
($value);
2435 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2436 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2437 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2438 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2439 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2440 die "unable to parse drive options for '$opt'\n" if !$drive;
2441 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2442 $newconf->{$opt} = $value; # simply copy configuration
2444 if ($param->{full
}) {
2445 die "Full clone feature is not supported for drive '$opt'\n"
2446 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2447 $fullclone->{$opt} = 1;
2449 # not full means clone instead of copy
2450 die "Linked clone feature is not supported for drive '$opt'\n"
2451 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2453 $drives->{$opt} = $drive;
2454 push @$vollist, $drive->{file
};
2457 # copy everything else
2458 $newconf->{$opt} = $value;
2462 # auto generate a new uuid
2463 my ($uuid, $uuid_str);
2464 UUID
::generate
($uuid);
2465 UUID
::unparse
($uuid, $uuid_str);
2466 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2467 $smbios1->{uuid
} = $uuid_str;
2468 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2470 delete $newconf->{template
};
2472 if ($param->{name
}) {
2473 $newconf->{name
} = $param->{name
};
2475 if ($oldconf->{name
}) {
2476 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2478 $newconf->{name
} = "Copy-of-VM-$vmid";
2482 if ($param->{description
}) {
2483 $newconf->{description
} = $param->{description
};
2486 # create empty/temp config - this fails if VM already exists on other node
2487 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2492 my $newvollist = [];
2496 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2498 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2500 my $total_jobs = scalar(keys %{$drives});
2503 foreach my $opt (keys %$drives) {
2504 my $drive = $drives->{$opt};
2505 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2507 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2508 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2509 $jobs, $skipcomplete, $oldconf->{agent
});
2511 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2513 PVE
::QemuConfig-
>write_config($newid, $newconf);
2517 delete $newconf->{lock};
2518 PVE
::QemuConfig-
>write_config($newid, $newconf);
2521 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2522 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2523 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2525 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2526 die "Failed to move config to node '$target' - rename failed: $!\n"
2527 if !rename($conffile, $newconffile);
2530 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2535 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2537 sleep 1; # some storage like rbd need to wait before release volume - really?
2539 foreach my $volid (@$newvollist) {
2540 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2543 die "clone failed: $err";
2549 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2551 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2554 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2555 # Aquire exclusive lock lock for $newid
2556 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2561 __PACKAGE__-
>register_method({
2562 name
=> 'move_vm_disk',
2563 path
=> '{vmid}/move_disk',
2567 description
=> "Move volume to different storage.",
2569 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2571 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2572 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2576 additionalProperties
=> 0,
2578 node
=> get_standard_option
('pve-node'),
2579 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2582 description
=> "The disk you want to move.",
2583 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2585 storage
=> get_standard_option
('pve-storage-id', {
2586 description
=> "Target storage.",
2587 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2591 description
=> "Target Format.",
2592 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2597 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2603 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2611 description
=> "the task ID.",
2616 my $rpcenv = PVE
::RPCEnvironment
::get
();
2618 my $authuser = $rpcenv->get_user();
2620 my $node = extract_param
($param, 'node');
2622 my $vmid = extract_param
($param, 'vmid');
2624 my $digest = extract_param
($param, 'digest');
2626 my $disk = extract_param
($param, 'disk');
2628 my $storeid = extract_param
($param, 'storage');
2630 my $format = extract_param
($param, 'format');
2632 my $storecfg = PVE
::Storage
::config
();
2634 my $updatefn = sub {
2636 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2638 PVE
::QemuConfig-
>check_lock($conf);
2640 die "checksum missmatch (file change by other user?)\n"
2641 if $digest && $digest ne $conf->{digest
};
2643 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2645 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2647 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2649 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2652 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2653 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2657 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2658 (!$format || !$oldfmt || $oldfmt eq $format);
2660 # this only checks snapshots because $disk is passed!
2661 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2662 die "you can't move a disk with snapshots and delete the source\n"
2663 if $snapshotted && $param->{delete};
2665 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2667 my $running = PVE
::QemuServer
::check_running
($vmid);
2669 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2673 my $newvollist = [];
2676 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2678 warn "moving disk with snapshots, snapshots will not be moved!\n"
2681 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2682 $vmid, $storeid, $format, 1, $newvollist);
2684 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2686 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2688 # convert moved disk to base if part of template
2689 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2690 if PVE
::QemuConfig-
>is_template($conf);
2692 PVE
::QemuConfig-
>write_config($vmid, $conf);
2695 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2696 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2703 foreach my $volid (@$newvollist) {
2704 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2707 die "storage migration failed: $err";
2710 if ($param->{delete}) {
2712 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2713 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2719 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2722 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2725 __PACKAGE__-
>register_method({
2726 name
=> 'migrate_vm',
2727 path
=> '{vmid}/migrate',
2731 description
=> "Migrate virtual machine. Creates a new migration task.",
2733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2736 additionalProperties
=> 0,
2738 node
=> get_standard_option
('pve-node'),
2739 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2740 target
=> get_standard_option
('pve-node', {
2741 description
=> "Target node.",
2742 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2746 description
=> "Use online/live migration.",
2751 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2756 enum
=> ['secure', 'insecure'],
2757 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2760 migration_network
=> {
2761 type
=> 'string', format
=> 'CIDR',
2762 description
=> "CIDR of the (sub) network that is used for migration.",
2765 "with-local-disks" => {
2767 description
=> "Enable live storage migration for local disk",
2770 targetstorage
=> get_standard_option
('pve-storage-id', {
2771 description
=> "Default target storage.",
2773 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2779 description
=> "the task ID.",
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2786 my $authuser = $rpcenv->get_user();
2788 my $target = extract_param
($param, 'target');
2790 my $localnode = PVE
::INotify
::nodename
();
2791 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2793 PVE
::Cluster
::check_cfs_quorum
();
2795 PVE
::Cluster
::check_node_exists
($target);
2797 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2799 my $vmid = extract_param
($param, 'vmid');
2801 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2802 if !$param->{online
} && $param->{targetstorage
};
2804 raise_param_exc
({ force
=> "Only root may use this option." })
2805 if $param->{force
} && $authuser ne 'root@pam';
2807 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2808 if $param->{migration_type
} && $authuser ne 'root@pam';
2810 # allow root only until better network permissions are available
2811 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2812 if $param->{migration_network
} && $authuser ne 'root@pam';
2815 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2817 # try to detect errors early
2819 PVE
::QemuConfig-
>check_lock($conf);
2821 if (PVE
::QemuServer
::check_running
($vmid)) {
2822 die "cant migrate running VM without --online\n"
2823 if !$param->{online
};
2826 my $storecfg = PVE
::Storage
::config
();
2828 if( $param->{targetstorage
}) {
2829 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2831 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2834 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2839 my $service = "vm:$vmid";
2841 my $cmd = ['ha-manager', 'migrate', $service, $target];
2843 print "Executing HA migrate for VM $vmid to node $target\n";
2845 PVE
::Tools
::run_command
($cmd);
2850 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2857 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2860 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2865 __PACKAGE__-
>register_method({
2867 path
=> '{vmid}/monitor',
2871 description
=> "Execute Qemu monitor commands.",
2873 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2874 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2877 additionalProperties
=> 0,
2879 node
=> get_standard_option
('pve-node'),
2880 vmid
=> get_standard_option
('pve-vmid'),
2883 description
=> "The monitor command.",
2887 returns
=> { type
=> 'string'},
2891 my $rpcenv = PVE
::RPCEnvironment
::get
();
2892 my $authuser = $rpcenv->get_user();
2895 my $command = shift;
2896 return $command =~ m/^\s*info(\s+|$)/
2897 || $command =~ m/^\s*help\s*$/;
2900 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2901 if !&$is_ro($param->{command
});
2903 my $vmid = $param->{vmid
};
2905 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2909 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2911 $res = "ERROR: $@" if $@;
2916 my $guest_agent_commands = [
2924 'network-get-interfaces',
2927 'get-memory-blocks',
2928 'get-memory-block-info',
2935 __PACKAGE__-
>register_method({
2937 path
=> '{vmid}/agent',
2941 description
=> "Execute Qemu Guest Agent commands.",
2943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2946 additionalProperties
=> 0,
2948 node
=> get_standard_option
('pve-node'),
2949 vmid
=> get_standard_option
('pve-vmid', {
2950 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2953 description
=> "The QGA command.",
2954 enum
=> $guest_agent_commands,
2960 description
=> "Returns an object with a single `result` property. The type of that
2961 property depends on the executed command.",
2966 my $vmid = $param->{vmid
};
2968 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2970 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2971 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2973 my $cmd = $param->{command
};
2975 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2977 return { result
=> $res };
2980 __PACKAGE__-
>register_method({
2981 name
=> 'resize_vm',
2982 path
=> '{vmid}/resize',
2986 description
=> "Extend volume size.",
2988 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2991 additionalProperties
=> 0,
2993 node
=> get_standard_option
('pve-node'),
2994 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2995 skiplock
=> get_standard_option
('skiplock'),
2998 description
=> "The disk you want to resize.",
2999 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3003 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3004 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.",
3008 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3014 returns
=> { type
=> 'null'},
3018 my $rpcenv = PVE
::RPCEnvironment
::get
();
3020 my $authuser = $rpcenv->get_user();
3022 my $node = extract_param
($param, 'node');
3024 my $vmid = extract_param
($param, 'vmid');
3026 my $digest = extract_param
($param, 'digest');
3028 my $disk = extract_param
($param, 'disk');
3030 my $sizestr = extract_param
($param, 'size');
3032 my $skiplock = extract_param
($param, 'skiplock');
3033 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3034 if $skiplock && $authuser ne 'root@pam';
3036 my $storecfg = PVE
::Storage
::config
();
3038 my $updatefn = sub {
3040 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3042 die "checksum missmatch (file change by other user?)\n"
3043 if $digest && $digest ne $conf->{digest
};
3044 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3046 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3048 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3050 my (undef, undef, undef, undef, undef, undef, $format) =
3051 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3053 die "can't resize volume: $disk if snapshot exists\n"
3054 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3056 my $volid = $drive->{file
};
3058 die "disk '$disk' has no associated volume\n" if !$volid;
3060 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3062 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3064 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3066 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3067 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3069 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3070 my ($ext, $newsize, $unit) = ($1, $2, $4);
3073 $newsize = $newsize * 1024;
3074 } elsif ($unit eq 'M') {
3075 $newsize = $newsize * 1024 * 1024;
3076 } elsif ($unit eq 'G') {
3077 $newsize = $newsize * 1024 * 1024 * 1024;
3078 } elsif ($unit eq 'T') {
3079 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3082 $newsize += $size if $ext;
3083 $newsize = int($newsize);
3085 die "shrinking disks is not supported\n" if $newsize < $size;
3087 return if $size == $newsize;
3089 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3091 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3093 $drive->{size
} = $newsize;
3094 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3096 PVE
::QemuConfig-
>write_config($vmid, $conf);
3099 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3103 __PACKAGE__-
>register_method({
3104 name
=> 'snapshot_list',
3105 path
=> '{vmid}/snapshot',
3107 description
=> "List all snapshots.",
3109 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3112 protected
=> 1, # qemu pid files are only readable by root
3114 additionalProperties
=> 0,
3116 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3117 node
=> get_standard_option
('pve-node'),
3126 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3131 my $vmid = $param->{vmid
};
3133 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3134 my $snaphash = $conf->{snapshots
} || {};
3138 foreach my $name (keys %$snaphash) {
3139 my $d = $snaphash->{$name};
3142 snaptime
=> $d->{snaptime
} || 0,
3143 vmstate
=> $d->{vmstate
} ?
1 : 0,
3144 description
=> $d->{description
} || '',
3146 $item->{parent
} = $d->{parent
} if $d->{parent
};
3147 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3151 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3152 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3153 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3155 push @$res, $current;
3160 __PACKAGE__-
>register_method({
3162 path
=> '{vmid}/snapshot',
3166 description
=> "Snapshot a VM.",
3168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3171 additionalProperties
=> 0,
3173 node
=> get_standard_option
('pve-node'),
3174 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3175 snapname
=> get_standard_option
('pve-snapshot-name'),
3179 description
=> "Save the vmstate",
3184 description
=> "A textual description or comment.",
3190 description
=> "the task ID.",
3195 my $rpcenv = PVE
::RPCEnvironment
::get
();
3197 my $authuser = $rpcenv->get_user();
3199 my $node = extract_param
($param, 'node');
3201 my $vmid = extract_param
($param, 'vmid');
3203 my $snapname = extract_param
($param, 'snapname');
3205 die "unable to use snapshot name 'current' (reserved name)\n"
3206 if $snapname eq 'current';
3209 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3210 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3211 $param->{description
});
3214 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3217 __PACKAGE__-
>register_method({
3218 name
=> 'snapshot_cmd_idx',
3219 path
=> '{vmid}/snapshot/{snapname}',
3226 additionalProperties
=> 0,
3228 vmid
=> get_standard_option
('pve-vmid'),
3229 node
=> get_standard_option
('pve-node'),
3230 snapname
=> get_standard_option
('pve-snapshot-name'),
3239 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3246 push @$res, { cmd
=> 'rollback' };
3247 push @$res, { cmd
=> 'config' };
3252 __PACKAGE__-
>register_method({
3253 name
=> 'update_snapshot_config',
3254 path
=> '{vmid}/snapshot/{snapname}/config',
3258 description
=> "Update snapshot metadata.",
3260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3263 additionalProperties
=> 0,
3265 node
=> get_standard_option
('pve-node'),
3266 vmid
=> get_standard_option
('pve-vmid'),
3267 snapname
=> get_standard_option
('pve-snapshot-name'),
3271 description
=> "A textual description or comment.",
3275 returns
=> { type
=> 'null' },
3279 my $rpcenv = PVE
::RPCEnvironment
::get
();
3281 my $authuser = $rpcenv->get_user();
3283 my $vmid = extract_param
($param, 'vmid');
3285 my $snapname = extract_param
($param, 'snapname');
3287 return undef if !defined($param->{description
});
3289 my $updatefn = sub {
3291 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3293 PVE
::QemuConfig-
>check_lock($conf);
3295 my $snap = $conf->{snapshots
}->{$snapname};
3297 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3299 $snap->{description
} = $param->{description
} if defined($param->{description
});
3301 PVE
::QemuConfig-
>write_config($vmid, $conf);
3304 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3309 __PACKAGE__-
>register_method({
3310 name
=> 'get_snapshot_config',
3311 path
=> '{vmid}/snapshot/{snapname}/config',
3314 description
=> "Get snapshot configuration",
3316 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3319 additionalProperties
=> 0,
3321 node
=> get_standard_option
('pve-node'),
3322 vmid
=> get_standard_option
('pve-vmid'),
3323 snapname
=> get_standard_option
('pve-snapshot-name'),
3326 returns
=> { type
=> "object" },
3330 my $rpcenv = PVE
::RPCEnvironment
::get
();
3332 my $authuser = $rpcenv->get_user();
3334 my $vmid = extract_param
($param, 'vmid');
3336 my $snapname = extract_param
($param, 'snapname');
3338 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3340 my $snap = $conf->{snapshots
}->{$snapname};
3342 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3347 __PACKAGE__-
>register_method({
3349 path
=> '{vmid}/snapshot/{snapname}/rollback',
3353 description
=> "Rollback VM state to specified snapshot.",
3355 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3358 additionalProperties
=> 0,
3360 node
=> get_standard_option
('pve-node'),
3361 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3362 snapname
=> get_standard_option
('pve-snapshot-name'),
3367 description
=> "the task ID.",
3372 my $rpcenv = PVE
::RPCEnvironment
::get
();
3374 my $authuser = $rpcenv->get_user();
3376 my $node = extract_param
($param, 'node');
3378 my $vmid = extract_param
($param, 'vmid');
3380 my $snapname = extract_param
($param, 'snapname');
3383 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3384 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3387 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3390 __PACKAGE__-
>register_method({
3391 name
=> 'delsnapshot',
3392 path
=> '{vmid}/snapshot/{snapname}',
3396 description
=> "Delete a VM snapshot.",
3398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3401 additionalProperties
=> 0,
3403 node
=> get_standard_option
('pve-node'),
3404 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3405 snapname
=> get_standard_option
('pve-snapshot-name'),
3409 description
=> "For removal from config file, even if removing disk snapshots fails.",
3415 description
=> "the task ID.",
3420 my $rpcenv = PVE
::RPCEnvironment
::get
();
3422 my $authuser = $rpcenv->get_user();
3424 my $node = extract_param
($param, 'node');
3426 my $vmid = extract_param
($param, 'vmid');
3428 my $snapname = extract_param
($param, 'snapname');
3431 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3432 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3435 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3438 __PACKAGE__-
>register_method({
3440 path
=> '{vmid}/template',
3444 description
=> "Create a Template.",
3446 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3447 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3450 additionalProperties
=> 0,
3452 node
=> get_standard_option
('pve-node'),
3453 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3457 description
=> "If you want to convert only 1 disk to base image.",
3458 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3463 returns
=> { type
=> 'null'},
3467 my $rpcenv = PVE
::RPCEnvironment
::get
();
3469 my $authuser = $rpcenv->get_user();
3471 my $node = extract_param
($param, 'node');
3473 my $vmid = extract_param
($param, 'vmid');
3475 my $disk = extract_param
($param, 'disk');
3477 my $updatefn = sub {
3479 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3481 PVE
::QemuConfig-
>check_lock($conf);
3483 die "unable to create template, because VM contains snapshots\n"
3484 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3486 die "you can't convert a template to a template\n"
3487 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3489 die "you can't convert a VM to template if VM is running\n"
3490 if PVE
::QemuServer
::check_running
($vmid);
3493 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3496 $conf->{template
} = 1;
3497 PVE
::QemuConfig-
>write_config($vmid, $conf);
3499 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3502 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);