1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
27 if (!$ENV{PVE_GENERATING_DOCS
}) {
28 require PVE
::HA
::Env
::PVE2
;
29 import PVE
::HA
::Env
::PVE2
;
30 require PVE
::HA
::Config
;
31 import PVE
::HA
::Config
;
35 use Data
::Dumper
; # fixme: remove
37 use base
qw(PVE::RESTHandler);
39 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.";
41 my $resolve_cdrom_alias = sub {
44 if (my $value = $param->{cdrom
}) {
45 $value .= ",media=cdrom" if $value !~ m/media=/;
46 $param->{ide2
} = $value;
47 delete $param->{cdrom
};
51 my $check_storage_access = sub {
52 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
54 PVE
::QemuServer
::foreach_drive
($settings, sub {
55 my ($ds, $drive) = @_;
57 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
59 my $volid = $drive->{file
};
61 if (!$volid || $volid eq 'none') {
63 } elsif ($isCDROM && ($volid eq 'cdrom')) {
64 $rpcenv->check($authuser, "/", ['Sys.Console']);
65 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
66 my ($storeid, $size) = ($2 || $default_storage, $3);
67 die "no storage ID specified (and no default storage)\n" if !$storeid;
68 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
70 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
75 my $check_storage_access_clone = sub {
76 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
80 PVE
::QemuServer
::foreach_drive
($conf, sub {
81 my ($ds, $drive) = @_;
83 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
85 my $volid = $drive->{file
};
87 return if !$volid || $volid eq 'none';
90 if ($volid eq 'cdrom') {
91 $rpcenv->check($authuser, "/", ['Sys.Console']);
93 # we simply allow access
94 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
95 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
96 $sharedvm = 0 if !$scfg->{shared
};
100 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
101 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
102 $sharedvm = 0 if !$scfg->{shared
};
104 $sid = $storage if $storage;
105 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
112 # Note: $pool is only needed when creating a VM, because pool permissions
113 # are automatically inherited if VM already exists inside a pool.
114 my $create_disks = sub {
115 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
120 PVE
::QemuServer
::foreach_drive
($settings, sub {
121 my ($ds, $disk) = @_;
123 my $volid = $disk->{file
};
125 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
126 delete $disk->{size
};
127 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
128 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
129 my ($storeid, $size) = ($2 || $default_storage, $3);
130 die "no storage ID specified (and no default storage)\n" if !$storeid;
131 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
132 my $fmt = $disk->{format
} || $defformat;
135 if ($ds eq 'efidisk0') {
137 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
138 die "uefi vars image not found\n" if ! -f
$ovmfvars;
139 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
141 $disk->{file
} = $volid;
142 $disk->{size
} = 128*1024;
143 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
144 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
145 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
146 my $path = PVE
::Storage
::path
($storecfg, $volid);
147 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
148 push @$efidiskcmd, $ovmfvars;
149 push @$efidiskcmd, $path;
151 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
153 eval { PVE
::Tools
::run_command
($efidiskcmd); };
155 die "Copying of EFI Vars image failed: $err" if $err;
157 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
158 $fmt, undef, $size*1024*1024);
159 $disk->{file
} = $volid;
160 $disk->{size
} = $size*1024*1024*1024;
162 push @$vollist, $volid;
163 delete $disk->{format
}; # no longer needed
164 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
167 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
169 my $volid_is_new = 1;
172 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
173 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
178 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
180 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
182 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
184 die "volume $volid does not exists\n" if !$size;
186 $disk->{size
} = $size;
189 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
193 # free allocated images on error
195 syslog
('err', "VM $vmid creating disks failed");
196 foreach my $volid (@$vollist) {
197 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
203 # modify vm config if everything went well
204 foreach my $ds (keys %$res) {
205 $conf->{$ds} = $res->{$ds};
222 my $memoryoptions = {
228 my $hwtypeoptions = {
240 my $generaloptions = {
247 'migrate_downtime' => 1,
248 'migrate_speed' => 1,
260 my $vmpoweroptions = {
269 my $check_vm_modify_config_perm = sub {
270 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
272 return 1 if $authuser eq 'root@pam';
274 foreach my $opt (@$key_list) {
275 # disk checks need to be done somewhere else
276 next if PVE
::QemuServer
::is_valid_drivename
($opt);
277 next if $opt eq 'cdrom';
278 next if $opt =~ m/^unused\d+$/;
280 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
281 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
282 } elsif ($memoryoptions->{$opt}) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
284 } elsif ($hwtypeoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
286 } elsif ($generaloptions->{$opt}) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
288 # special case for startup since it changes host behaviour
289 if ($opt eq 'startup') {
290 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
292 } elsif ($vmpoweroptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
294 } elsif ($diskoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
296 } elsif ($opt =~ m/^net\d+$/) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
299 # catches usb\d+, hostpci\d+, args, lock, etc.
300 # new options will be checked here
301 die "only root can set '$opt' config\n";
308 __PACKAGE__-
>register_method({
312 description
=> "Virtual machine index (per node).",
314 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
318 protected
=> 1, # qemu pid files are only readable by root
320 additionalProperties
=> 0,
322 node
=> get_standard_option
('pve-node'),
326 description
=> "Determine the full status of active VMs.",
336 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
341 my $rpcenv = PVE
::RPCEnvironment
::get
();
342 my $authuser = $rpcenv->get_user();
344 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
347 foreach my $vmid (keys %$vmstatus) {
348 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
350 my $data = $vmstatus->{$vmid};
351 $data->{vmid
} = int($vmid);
360 __PACKAGE__-
>register_method({
364 description
=> "Create or restore a virtual machine.",
366 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
367 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
368 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
369 user
=> 'all', # check inside
374 additionalProperties
=> 0,
375 properties
=> PVE
::QemuServer
::json_config_properties
(
377 node
=> get_standard_option
('pve-node'),
378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
380 description
=> "The backup file.",
384 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
386 storage
=> get_standard_option
('pve-storage-id', {
387 description
=> "Default storage.",
389 completion
=> \
&PVE
::QemuServer
::complete_storage
,
394 description
=> "Allow to overwrite existing VM.",
395 requires
=> 'archive',
400 description
=> "Assign a unique random ethernet address.",
401 requires
=> 'archive',
405 type
=> 'string', format
=> 'pve-poolid',
406 description
=> "Add the VM to the specified pool.",
416 my $rpcenv = PVE
::RPCEnvironment
::get
();
418 my $authuser = $rpcenv->get_user();
420 my $node = extract_param
($param, 'node');
422 my $vmid = extract_param
($param, 'vmid');
424 my $archive = extract_param
($param, 'archive');
426 my $storage = extract_param
($param, 'storage');
428 my $force = extract_param
($param, 'force');
430 my $unique = extract_param
($param, 'unique');
432 my $pool = extract_param
($param, 'pool');
434 my $filename = PVE
::QemuConfig-
>config_file($vmid);
436 my $storecfg = PVE
::Storage
::config
();
438 PVE
::Cluster
::check_cfs_quorum
();
440 if (defined($pool)) {
441 $rpcenv->check_pool_exist($pool);
444 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
445 if defined($storage);
447 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
449 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
451 } elsif ($archive && $force && (-f
$filename) &&
452 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
453 # OK: user has VM.Backup permissions, and want to restore an existing VM
459 &$resolve_cdrom_alias($param);
461 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
463 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
465 foreach my $opt (keys %$param) {
466 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
468 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
470 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
471 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
475 PVE
::QemuServer
::add_random_macs
($param);
477 my $keystr = join(' ', keys %$param);
478 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
480 if ($archive eq '-') {
481 die "pipe requires cli environment\n"
482 if $rpcenv->{type
} ne 'cli';
484 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
485 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
489 my $restorefn = sub {
490 my $vmlist = PVE
::Cluster
::get_vmlist
();
491 if ($vmlist->{ids
}->{$vmid}) {
492 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
493 if ($current_node eq $node) {
494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
496 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
498 die "unable to restore vm $vmid - config file already exists\n"
501 die "unable to restore vm $vmid - vm is running\n"
502 if PVE
::QemuServer
::check_running
($vmid);
504 die "unable to restore vm $vmid - vm is a template\n"
505 if PVE
::QemuConfig-
>is_template($conf);
508 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
513 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
516 unique
=> $unique });
518 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
521 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
527 PVE
::Cluster
::check_vmid_unused
($vmid);
537 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
539 # try to be smart about bootdisk
540 my @disks = PVE
::QemuServer
::valid_drive_names
();
542 foreach my $ds (reverse @disks) {
543 next if !$conf->{$ds};
544 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
545 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
549 if (!$conf->{bootdisk
} && $firstdisk) {
550 $conf->{bootdisk
} = $firstdisk;
553 # auto generate uuid if user did not specify smbios1 option
554 if (!$conf->{smbios1
}) {
555 my ($uuid, $uuid_str);
556 UUID
::generate
($uuid);
557 UUID
::unparse
($uuid, $uuid_str);
558 $conf->{smbios1
} = "uuid=$uuid_str";
561 PVE
::QemuConfig-
>write_config($vmid, $conf);
567 foreach my $volid (@$vollist) {
568 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
571 die "create failed - $err";
574 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
577 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
580 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
583 __PACKAGE__-
>register_method({
588 description
=> "Directory index",
593 additionalProperties
=> 0,
595 node
=> get_standard_option
('pve-node'),
596 vmid
=> get_standard_option
('pve-vmid'),
604 subdir
=> { type
=> 'string' },
607 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
613 { subdir
=> 'config' },
614 { subdir
=> 'pending' },
615 { subdir
=> 'status' },
616 { subdir
=> 'unlink' },
617 { subdir
=> 'vncproxy' },
618 { subdir
=> 'migrate' },
619 { subdir
=> 'resize' },
620 { subdir
=> 'move' },
622 { subdir
=> 'rrddata' },
623 { subdir
=> 'monitor' },
624 { subdir
=> 'agent' },
625 { subdir
=> 'snapshot' },
626 { subdir
=> 'spiceproxy' },
627 { subdir
=> 'sendkey' },
628 { subdir
=> 'firewall' },
634 __PACKAGE__-
>register_method ({
635 subclass
=> "PVE::API2::Firewall::VM",
636 path
=> '{vmid}/firewall',
639 __PACKAGE__-
>register_method({
641 path
=> '{vmid}/rrd',
643 protected
=> 1, # fixme: can we avoid that?
645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
647 description
=> "Read VM RRD statistics (returns PNG)",
649 additionalProperties
=> 0,
651 node
=> get_standard_option
('pve-node'),
652 vmid
=> get_standard_option
('pve-vmid'),
654 description
=> "Specify the time frame you are interested in.",
656 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
659 description
=> "The list of datasources you want to display.",
660 type
=> 'string', format
=> 'pve-configid-list',
663 description
=> "The RRD consolidation function",
665 enum
=> [ 'AVERAGE', 'MAX' ],
673 filename
=> { type
=> 'string' },
679 return PVE
::Cluster
::create_rrd_graph
(
680 "pve2-vm/$param->{vmid}", $param->{timeframe
},
681 $param->{ds
}, $param->{cf
});
685 __PACKAGE__-
>register_method({
687 path
=> '{vmid}/rrddata',
689 protected
=> 1, # fixme: can we avoid that?
691 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
693 description
=> "Read VM RRD statistics",
695 additionalProperties
=> 0,
697 node
=> get_standard_option
('pve-node'),
698 vmid
=> get_standard_option
('pve-vmid'),
700 description
=> "Specify the time frame you are interested in.",
702 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
705 description
=> "The RRD consolidation function",
707 enum
=> [ 'AVERAGE', 'MAX' ],
722 return PVE
::Cluster
::create_rrd_data
(
723 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
727 __PACKAGE__-
>register_method({
729 path
=> '{vmid}/config',
732 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
742 description
=> "Get current values (instead of pending values).",
754 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
761 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
763 delete $conf->{snapshots
};
765 if (!$param->{current
}) {
766 foreach my $opt (keys %{$conf->{pending
}}) {
767 next if $opt eq 'delete';
768 my $value = $conf->{pending
}->{$opt};
769 next if ref($value); # just to be sure
770 $conf->{$opt} = $value;
772 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
773 foreach my $opt (keys %$pending_delete_hash) {
774 delete $conf->{$opt} if $conf->{$opt};
778 delete $conf->{pending
};
783 __PACKAGE__-
>register_method({
784 name
=> 'vm_pending',
785 path
=> '{vmid}/pending',
788 description
=> "Get virtual machine configuration, including pending changes.",
790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
805 description
=> "Configuration option name.",
809 description
=> "Current value.",
814 description
=> "Pending value.",
819 description
=> "Indicates a pending delete request if present and not 0. " .
820 "The value 2 indicates a force-delete request.",
832 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
834 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
838 foreach my $opt (keys %$conf) {
839 next if ref($conf->{$opt});
840 my $item = { key
=> $opt };
841 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
842 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
843 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
847 foreach my $opt (keys %{$conf->{pending
}}) {
848 next if $opt eq 'delete';
849 next if ref($conf->{pending
}->{$opt}); # just to be sure
850 next if defined($conf->{$opt});
851 my $item = { key
=> $opt };
852 $item->{pending
} = $conf->{pending
}->{$opt};
856 while (my ($opt, $force) = each %$pending_delete_hash) {
857 next if $conf->{pending
}->{$opt}; # just to be sure
858 next if $conf->{$opt};
859 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
866 # POST/PUT {vmid}/config implementation
868 # The original API used PUT (idempotent) an we assumed that all operations
869 # are fast. But it turned out that almost any configuration change can
870 # involve hot-plug actions, or disk alloc/free. Such actions can take long
871 # time to complete and have side effects (not idempotent).
873 # The new implementation uses POST and forks a worker process. We added
874 # a new option 'background_delay'. If specified we wait up to
875 # 'background_delay' second for the worker task to complete. It returns null
876 # if the task is finished within that time, else we return the UPID.
878 my $update_vm_api = sub {
879 my ($param, $sync) = @_;
881 my $rpcenv = PVE
::RPCEnvironment
::get
();
883 my $authuser = $rpcenv->get_user();
885 my $node = extract_param
($param, 'node');
887 my $vmid = extract_param
($param, 'vmid');
889 my $digest = extract_param
($param, 'digest');
891 my $background_delay = extract_param
($param, 'background_delay');
893 my @paramarr = (); # used for log message
894 foreach my $key (keys %$param) {
895 push @paramarr, "-$key", $param->{$key};
898 my $skiplock = extract_param
($param, 'skiplock');
899 raise_param_exc
({ skiplock
=> "Only root may use this option." })
900 if $skiplock && $authuser ne 'root@pam';
902 my $delete_str = extract_param
($param, 'delete');
904 my $revert_str = extract_param
($param, 'revert');
906 my $force = extract_param
($param, 'force');
908 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
910 my $storecfg = PVE
::Storage
::config
();
912 my $defaults = PVE
::QemuServer
::load_defaults
();
914 &$resolve_cdrom_alias($param);
916 # now try to verify all parameters
919 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
920 if (!PVE
::QemuServer
::option_exists
($opt)) {
921 raise_param_exc
({ revert
=> "unknown option '$opt'" });
924 raise_param_exc
({ delete => "you can't use '-$opt' and " .
925 "-revert $opt' at the same time" })
926 if defined($param->{$opt});
932 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
933 $opt = 'ide2' if $opt eq 'cdrom';
935 raise_param_exc
({ delete => "you can't use '-$opt' and " .
936 "-delete $opt' at the same time" })
937 if defined($param->{$opt});
939 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
940 "-revert $opt' at the same time" })
943 if (!PVE
::QemuServer
::option_exists
($opt)) {
944 raise_param_exc
({ delete => "unknown option '$opt'" });
950 foreach my $opt (keys %$param) {
951 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
953 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
954 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
955 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
956 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
957 } elsif ($opt =~ m/^net(\d+)$/) {
959 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
960 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
964 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
966 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
968 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
972 my $conf = PVE
::QemuConfig-
>load_config($vmid);
974 die "checksum missmatch (file change by other user?)\n"
975 if $digest && $digest ne $conf->{digest
};
977 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
979 foreach my $opt (keys %$revert) {
980 if (defined($conf->{$opt})) {
981 $param->{$opt} = $conf->{$opt};
982 } elsif (defined($conf->{pending
}->{$opt})) {
987 if ($param->{memory
} || defined($param->{balloon
})) {
988 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
989 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
991 die "balloon value too large (must be smaller than assigned memory)\n"
992 if $balloon && $balloon > $maxmem;
995 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
999 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1001 # write updates to pending section
1003 my $modified = {}; # record what $option we modify
1005 foreach my $opt (@delete) {
1006 $modified->{$opt} = 1;
1007 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1008 if ($opt =~ m/^unused/) {
1009 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1010 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1011 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1012 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1013 delete $conf->{$opt};
1014 PVE
::QemuConfig-
>write_config($vmid, $conf);
1016 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1017 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1018 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1019 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1020 if defined($conf->{pending
}->{$opt});
1021 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1022 PVE
::QemuConfig-
>write_config($vmid, $conf);
1024 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1025 PVE
::QemuConfig-
>write_config($vmid, $conf);
1029 foreach my $opt (keys %$param) { # add/change
1030 $modified->{$opt} = 1;
1031 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1032 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1034 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1035 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1036 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1037 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1039 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1041 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1042 if defined($conf->{pending
}->{$opt});
1044 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1046 $conf->{pending
}->{$opt} = $param->{$opt};
1048 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1049 PVE
::QemuConfig-
>write_config($vmid, $conf);
1052 # remove pending changes when nothing changed
1053 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1054 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1055 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1057 return if !scalar(keys %{$conf->{pending
}});
1059 my $running = PVE
::QemuServer
::check_running
($vmid);
1061 # apply pending changes
1063 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1067 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1068 raise_param_exc
($errors) if scalar(keys %$errors);
1070 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1080 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1082 if ($background_delay) {
1084 # Note: It would be better to do that in the Event based HTTPServer
1085 # to avoid blocking call to sleep.
1087 my $end_time = time() + $background_delay;
1089 my $task = PVE
::Tools
::upid_decode
($upid);
1092 while (time() < $end_time) {
1093 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1095 sleep(1); # this gets interrupted when child process ends
1099 my $status = PVE
::Tools
::upid_read_status
($upid);
1100 return undef if $status eq 'OK';
1109 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1112 my $vm_config_perm_list = [
1117 'VM.Config.Network',
1119 'VM.Config.Options',
1122 __PACKAGE__-
>register_method({
1123 name
=> 'update_vm_async',
1124 path
=> '{vmid}/config',
1128 description
=> "Set virtual machine options (asynchrounous API).",
1130 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1133 additionalProperties
=> 0,
1134 properties
=> PVE
::QemuServer
::json_config_properties
(
1136 node
=> get_standard_option
('pve-node'),
1137 vmid
=> get_standard_option
('pve-vmid'),
1138 skiplock
=> get_standard_option
('skiplock'),
1140 type
=> 'string', format
=> 'pve-configid-list',
1141 description
=> "A list of settings you want to delete.",
1145 type
=> 'string', format
=> 'pve-configid-list',
1146 description
=> "Revert a pending change.",
1151 description
=> $opt_force_description,
1153 requires
=> 'delete',
1157 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1161 background_delay
=> {
1163 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1174 code
=> $update_vm_api,
1177 __PACKAGE__-
>register_method({
1178 name
=> 'update_vm',
1179 path
=> '{vmid}/config',
1183 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1185 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1188 additionalProperties
=> 0,
1189 properties
=> PVE
::QemuServer
::json_config_properties
(
1191 node
=> get_standard_option
('pve-node'),
1192 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1193 skiplock
=> get_standard_option
('skiplock'),
1195 type
=> 'string', format
=> 'pve-configid-list',
1196 description
=> "A list of settings you want to delete.",
1200 type
=> 'string', format
=> 'pve-configid-list',
1201 description
=> "Revert a pending change.",
1206 description
=> $opt_force_description,
1208 requires
=> 'delete',
1212 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1218 returns
=> { type
=> 'null' },
1221 &$update_vm_api($param, 1);
1227 __PACKAGE__-
>register_method({
1228 name
=> 'destroy_vm',
1233 description
=> "Destroy the vm (also delete all used/owned volumes).",
1235 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1238 additionalProperties
=> 0,
1240 node
=> get_standard_option
('pve-node'),
1241 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1242 skiplock
=> get_standard_option
('skiplock'),
1251 my $rpcenv = PVE
::RPCEnvironment
::get
();
1253 my $authuser = $rpcenv->get_user();
1255 my $vmid = $param->{vmid
};
1257 my $skiplock = $param->{skiplock
};
1258 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1259 if $skiplock && $authuser ne 'root@pam';
1262 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1264 my $storecfg = PVE
::Storage
::config
();
1266 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1268 die "unable to remove VM $vmid - used in HA resources\n"
1269 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1271 # early tests (repeat after locking)
1272 die "VM $vmid is running - destroy failed\n"
1273 if PVE
::QemuServer
::check_running
($vmid);
1278 syslog
('info', "destroy VM $vmid: $upid\n");
1280 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1282 PVE
::AccessControl
::remove_vm_access
($vmid);
1284 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1287 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1290 __PACKAGE__-
>register_method({
1292 path
=> '{vmid}/unlink',
1296 description
=> "Unlink/delete disk images.",
1298 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1301 additionalProperties
=> 0,
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1306 type
=> 'string', format
=> 'pve-configid-list',
1307 description
=> "A list of disk IDs you want to delete.",
1311 description
=> $opt_force_description,
1316 returns
=> { type
=> 'null'},
1320 $param->{delete} = extract_param
($param, 'idlist');
1322 __PACKAGE__-
>update_vm($param);
1329 __PACKAGE__-
>register_method({
1331 path
=> '{vmid}/vncproxy',
1335 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1337 description
=> "Creates a TCP VNC proxy connections.",
1339 additionalProperties
=> 0,
1341 node
=> get_standard_option
('pve-node'),
1342 vmid
=> get_standard_option
('pve-vmid'),
1346 description
=> "starts websockify instead of vncproxy",
1351 additionalProperties
=> 0,
1353 user
=> { type
=> 'string' },
1354 ticket
=> { type
=> 'string' },
1355 cert
=> { type
=> 'string' },
1356 port
=> { type
=> 'integer' },
1357 upid
=> { type
=> 'string' },
1363 my $rpcenv = PVE
::RPCEnvironment
::get
();
1365 my $authuser = $rpcenv->get_user();
1367 my $vmid = $param->{vmid
};
1368 my $node = $param->{node
};
1369 my $websocket = $param->{websocket
};
1371 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1373 my $authpath = "/vms/$vmid";
1375 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1377 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1380 my ($remip, $family);
1383 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1384 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1385 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1386 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1388 $family = PVE
::Tools
::get_host_address_family
($node);
1391 my $port = PVE
::Tools
::next_vnc_port
($family);
1398 syslog
('info', "starting vnc proxy $upid\n");
1402 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1404 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1406 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1407 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1408 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1409 '-timeout', $timeout, '-authpath', $authpath,
1410 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1413 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1415 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1417 my $qmstr = join(' ', @$qmcmd);
1419 # also redirect stderr (else we get RFB protocol errors)
1420 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1423 PVE
::Tools
::run_command
($cmd);
1428 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1430 PVE
::Tools
::wait_for_vnc_port
($port);
1441 __PACKAGE__-
>register_method({
1442 name
=> 'vncwebsocket',
1443 path
=> '{vmid}/vncwebsocket',
1446 description
=> "You also need to pass a valid ticket (vncticket).",
1447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1449 description
=> "Opens a weksocket for VNC traffic.",
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid'),
1456 description
=> "Ticket from previous call to vncproxy.",
1461 description
=> "Port number returned by previous vncproxy call.",
1471 port
=> { type
=> 'string' },
1477 my $rpcenv = PVE
::RPCEnvironment
::get
();
1479 my $authuser = $rpcenv->get_user();
1481 my $vmid = $param->{vmid
};
1482 my $node = $param->{node
};
1484 my $authpath = "/vms/$vmid";
1486 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1488 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1490 # Note: VNC ports are acessible from outside, so we do not gain any
1491 # security if we verify that $param->{port} belongs to VM $vmid. This
1492 # check is done by verifying the VNC ticket (inside VNC protocol).
1494 my $port = $param->{port
};
1496 return { port
=> $port };
1499 __PACKAGE__-
>register_method({
1500 name
=> 'spiceproxy',
1501 path
=> '{vmid}/spiceproxy',
1506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1508 description
=> "Returns a SPICE configuration to connect to the VM.",
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid'),
1514 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1517 returns
=> get_standard_option
('remote-viewer-config'),
1521 my $rpcenv = PVE
::RPCEnvironment
::get
();
1523 my $authuser = $rpcenv->get_user();
1525 my $vmid = $param->{vmid
};
1526 my $node = $param->{node
};
1527 my $proxy = $param->{proxy
};
1529 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1530 my $title = "VM $vmid";
1531 $title .= " - ". $conf->{name
} if $conf->{name
};
1533 my $port = PVE
::QemuServer
::spice_port
($vmid);
1535 my ($ticket, undef, $remote_viewer_config) =
1536 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1538 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1539 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1541 return $remote_viewer_config;
1544 __PACKAGE__-
>register_method({
1546 path
=> '{vmid}/status',
1549 description
=> "Directory index",
1554 additionalProperties
=> 0,
1556 node
=> get_standard_option
('pve-node'),
1557 vmid
=> get_standard_option
('pve-vmid'),
1565 subdir
=> { type
=> 'string' },
1568 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1574 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1577 { subdir
=> 'current' },
1578 { subdir
=> 'start' },
1579 { subdir
=> 'stop' },
1585 __PACKAGE__-
>register_method({
1586 name
=> 'vm_status',
1587 path
=> '{vmid}/status/current',
1590 protected
=> 1, # qemu pid files are only readable by root
1591 description
=> "Get virtual machine status.",
1593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1596 additionalProperties
=> 0,
1598 node
=> get_standard_option
('pve-node'),
1599 vmid
=> get_standard_option
('pve-vmid'),
1602 returns
=> { type
=> 'object' },
1607 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1609 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1610 my $status = $vmstatus->{$param->{vmid
}};
1612 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1614 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1619 __PACKAGE__-
>register_method({
1621 path
=> '{vmid}/status/start',
1625 description
=> "Start virtual machine.",
1627 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1630 additionalProperties
=> 0,
1632 node
=> get_standard_option
('pve-node'),
1633 vmid
=> get_standard_option
('pve-vmid',
1634 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1635 skiplock
=> get_standard_option
('skiplock'),
1636 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1637 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1640 enum
=> ['secure', 'insecure'],
1641 description
=> "Migration traffic is encrypted using an SSH " .
1642 "tunnel by default. On secure, completely private networks " .
1643 "this can be disabled to increase performance.",
1646 migration_network
=> {
1647 type
=> 'string', format
=> 'CIDR',
1648 description
=> "CIDR of the (sub) network that is used for migration.",
1651 machine
=> get_standard_option
('pve-qm-machine'),
1653 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1665 my $rpcenv = PVE
::RPCEnvironment
::get
();
1667 my $authuser = $rpcenv->get_user();
1669 my $node = extract_param
($param, 'node');
1671 my $vmid = extract_param
($param, 'vmid');
1673 my $machine = extract_param
($param, 'machine');
1675 my $stateuri = extract_param
($param, 'stateuri');
1676 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1677 if $stateuri && $authuser ne 'root@pam';
1679 my $skiplock = extract_param
($param, 'skiplock');
1680 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1681 if $skiplock && $authuser ne 'root@pam';
1683 my $migratedfrom = extract_param
($param, 'migratedfrom');
1684 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1685 if $migratedfrom && $authuser ne 'root@pam';
1687 my $migration_type = extract_param
($param, 'migration_type');
1688 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1689 if $migration_type && $authuser ne 'root@pam';
1691 my $migration_network = extract_param
($param, 'migration_network');
1692 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1693 if $migration_network && $authuser ne 'root@pam';
1695 my $targetstorage = extract_param
($param, 'targetstorage');
1696 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1697 if $targetstorage && $authuser ne 'root@pam';
1699 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1700 if $targetstorage && !$migratedfrom;
1702 # read spice ticket from STDIN
1704 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1705 if (defined(my $line = <>)) {
1707 $spice_ticket = $line;
1711 PVE
::Cluster
::check_cfs_quorum
();
1713 my $storecfg = PVE
::Storage
::config
();
1715 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1716 $rpcenv->{type
} ne 'ha') {
1721 my $service = "vm:$vmid";
1723 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1725 print "Executing HA start for VM $vmid\n";
1727 PVE
::Tools
::run_command
($cmd);
1732 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1739 syslog
('info', "start VM $vmid: $upid\n");
1741 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1742 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1747 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1751 __PACKAGE__-
>register_method({
1753 path
=> '{vmid}/status/stop',
1757 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1758 "is akin to pulling the power plug of a running computer and may damage the VM data",
1760 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1763 additionalProperties
=> 0,
1765 node
=> get_standard_option
('pve-node'),
1766 vmid
=> get_standard_option
('pve-vmid',
1767 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1768 skiplock
=> get_standard_option
('skiplock'),
1769 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1771 description
=> "Wait maximal timeout seconds.",
1777 description
=> "Do not deactivate storage volumes.",
1790 my $rpcenv = PVE
::RPCEnvironment
::get
();
1792 my $authuser = $rpcenv->get_user();
1794 my $node = extract_param
($param, 'node');
1796 my $vmid = extract_param
($param, 'vmid');
1798 my $skiplock = extract_param
($param, 'skiplock');
1799 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1800 if $skiplock && $authuser ne 'root@pam';
1802 my $keepActive = extract_param
($param, 'keepActive');
1803 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1804 if $keepActive && $authuser ne 'root@pam';
1806 my $migratedfrom = extract_param
($param, 'migratedfrom');
1807 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1808 if $migratedfrom && $authuser ne 'root@pam';
1811 my $storecfg = PVE
::Storage
::config
();
1813 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1818 my $service = "vm:$vmid";
1820 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1822 print "Executing HA stop for VM $vmid\n";
1824 PVE
::Tools
::run_command
($cmd);
1829 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1835 syslog
('info', "stop VM $vmid: $upid\n");
1837 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1838 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1843 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1847 __PACKAGE__-
>register_method({
1849 path
=> '{vmid}/status/reset',
1853 description
=> "Reset virtual machine.",
1855 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1858 additionalProperties
=> 0,
1860 node
=> get_standard_option
('pve-node'),
1861 vmid
=> get_standard_option
('pve-vmid',
1862 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1863 skiplock
=> get_standard_option
('skiplock'),
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $node = extract_param
($param, 'node');
1878 my $vmid = extract_param
($param, 'vmid');
1880 my $skiplock = extract_param
($param, 'skiplock');
1881 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1882 if $skiplock && $authuser ne 'root@pam';
1884 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1889 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1894 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1897 __PACKAGE__-
>register_method({
1898 name
=> 'vm_shutdown',
1899 path
=> '{vmid}/status/shutdown',
1903 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1904 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1906 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1909 additionalProperties
=> 0,
1911 node
=> get_standard_option
('pve-node'),
1912 vmid
=> get_standard_option
('pve-vmid',
1913 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1914 skiplock
=> get_standard_option
('skiplock'),
1916 description
=> "Wait maximal timeout seconds.",
1922 description
=> "Make sure the VM stops.",
1928 description
=> "Do not deactivate storage volumes.",
1941 my $rpcenv = PVE
::RPCEnvironment
::get
();
1943 my $authuser = $rpcenv->get_user();
1945 my $node = extract_param
($param, 'node');
1947 my $vmid = extract_param
($param, 'vmid');
1949 my $skiplock = extract_param
($param, 'skiplock');
1950 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1951 if $skiplock && $authuser ne 'root@pam';
1953 my $keepActive = extract_param
($param, 'keepActive');
1954 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1955 if $keepActive && $authuser ne 'root@pam';
1957 my $storecfg = PVE
::Storage
::config
();
1961 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1962 # otherwise, we will infer a shutdown command, but run into the timeout,
1963 # then when the vm is resumed, it will instantly shutdown
1965 # checking the qmp status here to get feedback to the gui/cli/api
1966 # and the status query should not take too long
1969 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1973 if (!$err && $qmpstatus->{status
} eq "paused") {
1974 if ($param->{forceStop
}) {
1975 warn "VM is paused - stop instead of shutdown\n";
1978 die "VM is paused - cannot shutdown\n";
1982 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
1983 ($rpcenv->{type
} ne 'ha')) {
1988 my $service = "vm:$vmid";
1990 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1992 print "Executing HA stop for VM $vmid\n";
1994 PVE
::Tools
::run_command
($cmd);
1999 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2006 syslog
('info', "shutdown VM $vmid: $upid\n");
2008 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2009 $shutdown, $param->{forceStop
}, $keepActive);
2014 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2018 __PACKAGE__-
>register_method({
2019 name
=> 'vm_suspend',
2020 path
=> '{vmid}/status/suspend',
2024 description
=> "Suspend virtual machine.",
2026 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2029 additionalProperties
=> 0,
2031 node
=> get_standard_option
('pve-node'),
2032 vmid
=> get_standard_option
('pve-vmid',
2033 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2034 skiplock
=> get_standard_option
('skiplock'),
2043 my $rpcenv = PVE
::RPCEnvironment
::get
();
2045 my $authuser = $rpcenv->get_user();
2047 my $node = extract_param
($param, 'node');
2049 my $vmid = extract_param
($param, 'vmid');
2051 my $skiplock = extract_param
($param, 'skiplock');
2052 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2053 if $skiplock && $authuser ne 'root@pam';
2055 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2060 syslog
('info', "suspend VM $vmid: $upid\n");
2062 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2067 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2070 __PACKAGE__-
>register_method({
2071 name
=> 'vm_resume',
2072 path
=> '{vmid}/status/resume',
2076 description
=> "Resume virtual machine.",
2078 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid',
2085 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2086 skiplock
=> get_standard_option
('skiplock'),
2087 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2097 my $rpcenv = PVE
::RPCEnvironment
::get
();
2099 my $authuser = $rpcenv->get_user();
2101 my $node = extract_param
($param, 'node');
2103 my $vmid = extract_param
($param, 'vmid');
2105 my $skiplock = extract_param
($param, 'skiplock');
2106 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2107 if $skiplock && $authuser ne 'root@pam';
2109 my $nocheck = extract_param
($param, 'nocheck');
2111 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2116 syslog
('info', "resume VM $vmid: $upid\n");
2118 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2123 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2126 __PACKAGE__-
>register_method({
2127 name
=> 'vm_sendkey',
2128 path
=> '{vmid}/sendkey',
2132 description
=> "Send key event to virtual machine.",
2134 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2137 additionalProperties
=> 0,
2139 node
=> get_standard_option
('pve-node'),
2140 vmid
=> get_standard_option
('pve-vmid',
2141 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2142 skiplock
=> get_standard_option
('skiplock'),
2144 description
=> "The key (qemu monitor encoding).",
2149 returns
=> { type
=> 'null'},
2153 my $rpcenv = PVE
::RPCEnvironment
::get
();
2155 my $authuser = $rpcenv->get_user();
2157 my $node = extract_param
($param, 'node');
2159 my $vmid = extract_param
($param, 'vmid');
2161 my $skiplock = extract_param
($param, 'skiplock');
2162 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2163 if $skiplock && $authuser ne 'root@pam';
2165 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2170 __PACKAGE__-
>register_method({
2171 name
=> 'vm_feature',
2172 path
=> '{vmid}/feature',
2176 description
=> "Check if feature for virtual machine is available.",
2178 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2181 additionalProperties
=> 0,
2183 node
=> get_standard_option
('pve-node'),
2184 vmid
=> get_standard_option
('pve-vmid'),
2186 description
=> "Feature to check.",
2188 enum
=> [ 'snapshot', 'clone', 'copy' ],
2190 snapname
=> get_standard_option
('pve-snapshot-name', {
2198 hasFeature
=> { type
=> 'boolean' },
2201 items
=> { type
=> 'string' },
2208 my $node = extract_param
($param, 'node');
2210 my $vmid = extract_param
($param, 'vmid');
2212 my $snapname = extract_param
($param, 'snapname');
2214 my $feature = extract_param
($param, 'feature');
2216 my $running = PVE
::QemuServer
::check_running
($vmid);
2218 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2221 my $snap = $conf->{snapshots
}->{$snapname};
2222 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2225 my $storecfg = PVE
::Storage
::config
();
2227 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2228 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2231 hasFeature
=> $hasFeature,
2232 nodes
=> [ keys %$nodelist ],
2236 __PACKAGE__-
>register_method({
2238 path
=> '{vmid}/clone',
2242 description
=> "Create a copy of virtual machine/template.",
2244 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2245 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2246 "'Datastore.AllocateSpace' on any used storage.",
2249 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2251 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2252 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2257 additionalProperties
=> 0,
2259 node
=> get_standard_option
('pve-node'),
2260 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2261 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2264 type
=> 'string', format
=> 'dns-name',
2265 description
=> "Set a name for the new VM.",
2270 description
=> "Description for the new VM.",
2274 type
=> 'string', format
=> 'pve-poolid',
2275 description
=> "Add the new VM to the specified pool.",
2277 snapname
=> get_standard_option
('pve-snapshot-name', {
2280 storage
=> get_standard_option
('pve-storage-id', {
2281 description
=> "Target storage for full clone.",
2286 description
=> "Target format for file storage.",
2290 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2295 description
=> "Create a full copy of all disk. This is always done when " .
2296 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2299 target
=> get_standard_option
('pve-node', {
2300 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2311 my $rpcenv = PVE
::RPCEnvironment
::get
();
2313 my $authuser = $rpcenv->get_user();
2315 my $node = extract_param
($param, 'node');
2317 my $vmid = extract_param
($param, 'vmid');
2319 my $newid = extract_param
($param, 'newid');
2321 my $pool = extract_param
($param, 'pool');
2323 if (defined($pool)) {
2324 $rpcenv->check_pool_exist($pool);
2327 my $snapname = extract_param
($param, 'snapname');
2329 my $storage = extract_param
($param, 'storage');
2331 my $format = extract_param
($param, 'format');
2333 my $target = extract_param
($param, 'target');
2335 my $localnode = PVE
::INotify
::nodename
();
2337 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2339 PVE
::Cluster
::check_node_exists
($target) if $target;
2341 my $storecfg = PVE
::Storage
::config
();
2344 # check if storage is enabled on local node
2345 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2347 # check if storage is available on target node
2348 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2349 # clone only works if target storage is shared
2350 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2351 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2355 PVE
::Cluster
::check_cfs_quorum
();
2357 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2359 # exclusive lock if VM is running - else shared lock is enough;
2360 my $shared_lock = $running ?
0 : 1;
2364 # do all tests after lock
2365 # we also try to do all tests before we fork the worker
2367 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2369 PVE
::QemuConfig-
>check_lock($conf);
2371 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2373 die "unexpected state change\n" if $verify_running != $running;
2375 die "snapshot '$snapname' does not exist\n"
2376 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2378 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2380 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2382 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2384 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2386 die "unable to create VM $newid: config file already exists\n"
2389 my $newconf = { lock => 'clone' };
2394 foreach my $opt (keys %$oldconf) {
2395 my $value = $oldconf->{$opt};
2397 # do not copy snapshot related info
2398 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2399 $opt eq 'vmstate' || $opt eq 'snapstate';
2401 # no need to copy unused images, because VMID(owner) changes anyways
2402 next if $opt =~ m/^unused\d+$/;
2404 # always change MAC! address
2405 if ($opt =~ m/^net(\d+)$/) {
2406 my $net = PVE
::QemuServer
::parse_net
($value);
2407 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2408 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2409 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2410 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2411 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2412 die "unable to parse drive options for '$opt'\n" if !$drive;
2413 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2414 $newconf->{$opt} = $value; # simply copy configuration
2416 if ($param->{full
}) {
2417 die "Full clone feature is not available"
2418 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2419 $fullclone->{$opt} = 1;
2421 # not full means clone instead of copy
2422 die "Linked clone feature is not available"
2423 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2425 $drives->{$opt} = $drive;
2426 push @$vollist, $drive->{file
};
2429 # copy everything else
2430 $newconf->{$opt} = $value;
2434 # auto generate a new uuid
2435 my ($uuid, $uuid_str);
2436 UUID
::generate
($uuid);
2437 UUID
::unparse
($uuid, $uuid_str);
2438 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2439 $smbios1->{uuid
} = $uuid_str;
2440 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2442 delete $newconf->{template
};
2444 if ($param->{name
}) {
2445 $newconf->{name
} = $param->{name
};
2447 if ($oldconf->{name
}) {
2448 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2450 $newconf->{name
} = "Copy-of-VM-$vmid";
2454 if ($param->{description
}) {
2455 $newconf->{description
} = $param->{description
};
2458 # create empty/temp config - this fails if VM already exists on other node
2459 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2464 my $newvollist = [];
2468 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2470 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2472 my $total_jobs = scalar(keys %{$drives});
2475 foreach my $opt (keys %$drives) {
2476 my $drive = $drives->{$opt};
2477 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2479 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2480 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2481 $jobs, $skipcomplete, $oldconf->{agent
});
2483 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2485 PVE
::QemuConfig-
>write_config($newid, $newconf);
2489 delete $newconf->{lock};
2490 PVE
::QemuConfig-
>write_config($newid, $newconf);
2493 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2494 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2495 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2497 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2498 die "Failed to move config to node '$target' - rename failed: $!\n"
2499 if !rename($conffile, $newconffile);
2502 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2507 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2509 sleep 1; # some storage like rbd need to wait before release volume - really?
2511 foreach my $volid (@$newvollist) {
2512 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2515 die "clone failed: $err";
2521 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2523 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2526 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2527 # Aquire exclusive lock lock for $newid
2528 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2533 __PACKAGE__-
>register_method({
2534 name
=> 'move_vm_disk',
2535 path
=> '{vmid}/move_disk',
2539 description
=> "Move volume to different storage.",
2541 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2543 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2544 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2548 additionalProperties
=> 0,
2550 node
=> get_standard_option
('pve-node'),
2551 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2554 description
=> "The disk you want to move.",
2555 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2557 storage
=> get_standard_option
('pve-storage-id', {
2558 description
=> "Target storage.",
2559 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2563 description
=> "Target Format.",
2564 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2569 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2575 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2583 description
=> "the task ID.",
2588 my $rpcenv = PVE
::RPCEnvironment
::get
();
2590 my $authuser = $rpcenv->get_user();
2592 my $node = extract_param
($param, 'node');
2594 my $vmid = extract_param
($param, 'vmid');
2596 my $digest = extract_param
($param, 'digest');
2598 my $disk = extract_param
($param, 'disk');
2600 my $storeid = extract_param
($param, 'storage');
2602 my $format = extract_param
($param, 'format');
2604 my $storecfg = PVE
::Storage
::config
();
2606 my $updatefn = sub {
2608 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2610 PVE
::QemuConfig-
>check_lock($conf);
2612 die "checksum missmatch (file change by other user?)\n"
2613 if $digest && $digest ne $conf->{digest
};
2615 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2617 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2619 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2621 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2624 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2625 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2629 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2630 (!$format || !$oldfmt || $oldfmt eq $format);
2632 # this only checks snapshots because $disk is passed!
2633 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2634 die "you can't move a disk with snapshots and delete the source\n"
2635 if $snapshotted && $param->{delete};
2637 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2639 my $running = PVE
::QemuServer
::check_running
($vmid);
2641 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2645 my $newvollist = [];
2648 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2650 warn "moving disk with snapshots, snapshots will not be moved!\n"
2653 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2654 $vmid, $storeid, $format, 1, $newvollist);
2656 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2658 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2660 # convert moved disk to base if part of template
2661 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2662 if PVE
::QemuConfig-
>is_template($conf);
2664 PVE
::QemuConfig-
>write_config($vmid, $conf);
2667 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2668 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2675 foreach my $volid (@$newvollist) {
2676 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2679 die "storage migration failed: $err";
2682 if ($param->{delete}) {
2684 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2685 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2691 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2694 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2697 __PACKAGE__-
>register_method({
2698 name
=> 'migrate_vm',
2699 path
=> '{vmid}/migrate',
2703 description
=> "Migrate virtual machine. Creates a new migration task.",
2705 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2708 additionalProperties
=> 0,
2710 node
=> get_standard_option
('pve-node'),
2711 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2712 target
=> get_standard_option
('pve-node', {
2713 description
=> "Target node.",
2714 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2718 description
=> "Use online/live migration.",
2723 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2728 enum
=> ['secure', 'insecure'],
2729 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2732 migration_network
=> {
2733 type
=> 'string', format
=> 'CIDR',
2734 description
=> "CIDR of the (sub) network that is used for migration.",
2737 "with-local-disks" => {
2739 description
=> "Enable live storage migration for local disk",
2742 targetstorage
=> get_standard_option
('pve-storage-id', {
2743 description
=> "Default target storage.",
2745 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2751 description
=> "the task ID.",
2756 my $rpcenv = PVE
::RPCEnvironment
::get
();
2758 my $authuser = $rpcenv->get_user();
2760 my $target = extract_param
($param, 'target');
2762 my $localnode = PVE
::INotify
::nodename
();
2763 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2765 PVE
::Cluster
::check_cfs_quorum
();
2767 PVE
::Cluster
::check_node_exists
($target);
2769 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2771 my $vmid = extract_param
($param, 'vmid');
2773 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2774 if !$param->{online
} && $param->{targetstorage
};
2776 raise_param_exc
({ force
=> "Only root may use this option." })
2777 if $param->{force
} && $authuser ne 'root@pam';
2779 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2780 if $param->{migration_type
} && $authuser ne 'root@pam';
2782 # allow root only until better network permissions are available
2783 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2784 if $param->{migration_network
} && $authuser ne 'root@pam';
2787 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2789 # try to detect errors early
2791 PVE
::QemuConfig-
>check_lock($conf);
2793 if (PVE
::QemuServer
::check_running
($vmid)) {
2794 die "cant migrate running VM without --online\n"
2795 if !$param->{online
};
2798 my $storecfg = PVE
::Storage
::config
();
2799 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2801 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2806 my $service = "vm:$vmid";
2808 my $cmd = ['ha-manager', 'migrate', $service, $target];
2810 print "Executing HA migrate for VM $vmid to node $target\n";
2812 PVE
::Tools
::run_command
($cmd);
2817 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2824 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2827 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2832 __PACKAGE__-
>register_method({
2834 path
=> '{vmid}/monitor',
2838 description
=> "Execute Qemu monitor commands.",
2840 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2844 additionalProperties
=> 0,
2846 node
=> get_standard_option
('pve-node'),
2847 vmid
=> get_standard_option
('pve-vmid'),
2850 description
=> "The monitor command.",
2854 returns
=> { type
=> 'string'},
2858 my $rpcenv = PVE
::RPCEnvironment
::get
();
2859 my $authuser = $rpcenv->get_user();
2862 my $command = shift;
2863 return $command =~ m/^\s*info(\s+|$)/
2864 || $command =~ m/^\s*help\s*$/;
2867 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2868 if !&$is_ro($param->{command
});
2870 my $vmid = $param->{vmid
};
2872 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2876 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2878 $res = "ERROR: $@" if $@;
2883 my $guest_agent_commands = [
2891 'network-get-interfaces',
2894 'get-memory-blocks',
2895 'get-memory-block-info',
2902 __PACKAGE__-
>register_method({
2904 path
=> '{vmid}/agent',
2908 description
=> "Execute Qemu Guest Agent commands.",
2910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2913 additionalProperties
=> 0,
2915 node
=> get_standard_option
('pve-node'),
2916 vmid
=> get_standard_option
('pve-vmid', {
2917 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2920 description
=> "The QGA command.",
2921 enum
=> $guest_agent_commands,
2927 description
=> "Returns an object with a single `result` property. The type of that
2928 property depends on the executed command.",
2933 my $vmid = $param->{vmid
};
2935 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2937 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2938 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2940 my $cmd = $param->{command
};
2942 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2944 return { result
=> $res };
2947 __PACKAGE__-
>register_method({
2948 name
=> 'resize_vm',
2949 path
=> '{vmid}/resize',
2953 description
=> "Extend volume size.",
2955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2958 additionalProperties
=> 0,
2960 node
=> get_standard_option
('pve-node'),
2961 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2962 skiplock
=> get_standard_option
('skiplock'),
2965 description
=> "The disk you want to resize.",
2966 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2970 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2971 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.",
2975 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2981 returns
=> { type
=> 'null'},
2985 my $rpcenv = PVE
::RPCEnvironment
::get
();
2987 my $authuser = $rpcenv->get_user();
2989 my $node = extract_param
($param, 'node');
2991 my $vmid = extract_param
($param, 'vmid');
2993 my $digest = extract_param
($param, 'digest');
2995 my $disk = extract_param
($param, 'disk');
2997 my $sizestr = extract_param
($param, 'size');
2999 my $skiplock = extract_param
($param, 'skiplock');
3000 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3001 if $skiplock && $authuser ne 'root@pam';
3003 my $storecfg = PVE
::Storage
::config
();
3005 my $updatefn = sub {
3007 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3009 die "checksum missmatch (file change by other user?)\n"
3010 if $digest && $digest ne $conf->{digest
};
3011 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3013 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3015 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3017 my (undef, undef, undef, undef, undef, undef, $format) =
3018 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3020 die "can't resize volume: $disk if snapshot exists\n"
3021 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3023 my $volid = $drive->{file
};
3025 die "disk '$disk' has no associated volume\n" if !$volid;
3027 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3029 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3031 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3033 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3034 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3036 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3037 my ($ext, $newsize, $unit) = ($1, $2, $4);
3040 $newsize = $newsize * 1024;
3041 } elsif ($unit eq 'M') {
3042 $newsize = $newsize * 1024 * 1024;
3043 } elsif ($unit eq 'G') {
3044 $newsize = $newsize * 1024 * 1024 * 1024;
3045 } elsif ($unit eq 'T') {
3046 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3049 $newsize += $size if $ext;
3050 $newsize = int($newsize);
3052 die "unable to skrink disk size\n" if $newsize < $size;
3054 return if $size == $newsize;
3056 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3058 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3060 $drive->{size
} = $newsize;
3061 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3063 PVE
::QemuConfig-
>write_config($vmid, $conf);
3066 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3070 __PACKAGE__-
>register_method({
3071 name
=> 'snapshot_list',
3072 path
=> '{vmid}/snapshot',
3074 description
=> "List all snapshots.",
3076 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3079 protected
=> 1, # qemu pid files are only readable by root
3081 additionalProperties
=> 0,
3083 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3084 node
=> get_standard_option
('pve-node'),
3093 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3098 my $vmid = $param->{vmid
};
3100 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3101 my $snaphash = $conf->{snapshots
} || {};
3105 foreach my $name (keys %$snaphash) {
3106 my $d = $snaphash->{$name};
3109 snaptime
=> $d->{snaptime
} || 0,
3110 vmstate
=> $d->{vmstate
} ?
1 : 0,
3111 description
=> $d->{description
} || '',
3113 $item->{parent
} = $d->{parent
} if $d->{parent
};
3114 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3118 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3119 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3120 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3122 push @$res, $current;
3127 __PACKAGE__-
>register_method({
3129 path
=> '{vmid}/snapshot',
3133 description
=> "Snapshot a VM.",
3135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3138 additionalProperties
=> 0,
3140 node
=> get_standard_option
('pve-node'),
3141 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3142 snapname
=> get_standard_option
('pve-snapshot-name'),
3146 description
=> "Save the vmstate",
3151 description
=> "A textual description or comment.",
3157 description
=> "the task ID.",
3162 my $rpcenv = PVE
::RPCEnvironment
::get
();
3164 my $authuser = $rpcenv->get_user();
3166 my $node = extract_param
($param, 'node');
3168 my $vmid = extract_param
($param, 'vmid');
3170 my $snapname = extract_param
($param, 'snapname');
3172 die "unable to use snapshot name 'current' (reserved name)\n"
3173 if $snapname eq 'current';
3176 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3177 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3178 $param->{description
});
3181 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3184 __PACKAGE__-
>register_method({
3185 name
=> 'snapshot_cmd_idx',
3186 path
=> '{vmid}/snapshot/{snapname}',
3193 additionalProperties
=> 0,
3195 vmid
=> get_standard_option
('pve-vmid'),
3196 node
=> get_standard_option
('pve-node'),
3197 snapname
=> get_standard_option
('pve-snapshot-name'),
3206 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3213 push @$res, { cmd
=> 'rollback' };
3214 push @$res, { cmd
=> 'config' };
3219 __PACKAGE__-
>register_method({
3220 name
=> 'update_snapshot_config',
3221 path
=> '{vmid}/snapshot/{snapname}/config',
3225 description
=> "Update snapshot metadata.",
3227 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3230 additionalProperties
=> 0,
3232 node
=> get_standard_option
('pve-node'),
3233 vmid
=> get_standard_option
('pve-vmid'),
3234 snapname
=> get_standard_option
('pve-snapshot-name'),
3238 description
=> "A textual description or comment.",
3242 returns
=> { type
=> 'null' },
3246 my $rpcenv = PVE
::RPCEnvironment
::get
();
3248 my $authuser = $rpcenv->get_user();
3250 my $vmid = extract_param
($param, 'vmid');
3252 my $snapname = extract_param
($param, 'snapname');
3254 return undef if !defined($param->{description
});
3256 my $updatefn = sub {
3258 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3260 PVE
::QemuConfig-
>check_lock($conf);
3262 my $snap = $conf->{snapshots
}->{$snapname};
3264 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3266 $snap->{description
} = $param->{description
} if defined($param->{description
});
3268 PVE
::QemuConfig-
>write_config($vmid, $conf);
3271 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3276 __PACKAGE__-
>register_method({
3277 name
=> 'get_snapshot_config',
3278 path
=> '{vmid}/snapshot/{snapname}/config',
3281 description
=> "Get snapshot configuration",
3283 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3286 additionalProperties
=> 0,
3288 node
=> get_standard_option
('pve-node'),
3289 vmid
=> get_standard_option
('pve-vmid'),
3290 snapname
=> get_standard_option
('pve-snapshot-name'),
3293 returns
=> { type
=> "object" },
3297 my $rpcenv = PVE
::RPCEnvironment
::get
();
3299 my $authuser = $rpcenv->get_user();
3301 my $vmid = extract_param
($param, 'vmid');
3303 my $snapname = extract_param
($param, 'snapname');
3305 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3307 my $snap = $conf->{snapshots
}->{$snapname};
3309 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3314 __PACKAGE__-
>register_method({
3316 path
=> '{vmid}/snapshot/{snapname}/rollback',
3320 description
=> "Rollback VM state to specified snapshot.",
3322 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3325 additionalProperties
=> 0,
3327 node
=> get_standard_option
('pve-node'),
3328 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3329 snapname
=> get_standard_option
('pve-snapshot-name'),
3334 description
=> "the task ID.",
3339 my $rpcenv = PVE
::RPCEnvironment
::get
();
3341 my $authuser = $rpcenv->get_user();
3343 my $node = extract_param
($param, 'node');
3345 my $vmid = extract_param
($param, 'vmid');
3347 my $snapname = extract_param
($param, 'snapname');
3350 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3351 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3354 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3357 __PACKAGE__-
>register_method({
3358 name
=> 'delsnapshot',
3359 path
=> '{vmid}/snapshot/{snapname}',
3363 description
=> "Delete a VM snapshot.",
3365 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3368 additionalProperties
=> 0,
3370 node
=> get_standard_option
('pve-node'),
3371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3372 snapname
=> get_standard_option
('pve-snapshot-name'),
3376 description
=> "For removal from config file, even if removing disk snapshots fails.",
3382 description
=> "the task ID.",
3387 my $rpcenv = PVE
::RPCEnvironment
::get
();
3389 my $authuser = $rpcenv->get_user();
3391 my $node = extract_param
($param, 'node');
3393 my $vmid = extract_param
($param, 'vmid');
3395 my $snapname = extract_param
($param, 'snapname');
3398 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3399 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3402 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3405 __PACKAGE__-
>register_method({
3407 path
=> '{vmid}/template',
3411 description
=> "Create a Template.",
3413 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3414 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3417 additionalProperties
=> 0,
3419 node
=> get_standard_option
('pve-node'),
3420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3424 description
=> "If you want to convert only 1 disk to base image.",
3425 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3430 returns
=> { type
=> 'null'},
3434 my $rpcenv = PVE
::RPCEnvironment
::get
();
3436 my $authuser = $rpcenv->get_user();
3438 my $node = extract_param
($param, 'node');
3440 my $vmid = extract_param
($param, 'vmid');
3442 my $disk = extract_param
($param, 'disk');
3444 my $updatefn = sub {
3446 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3448 PVE
::QemuConfig-
>check_lock($conf);
3450 die "unable to create template, because VM contains snapshots\n"
3451 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3453 die "you can't convert a template to a template\n"
3454 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3456 die "you can't convert a VM to template if VM is running\n"
3457 if PVE
::QemuServer
::check_running
($vmid);
3460 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3463 $conf->{template
} = 1;
3464 PVE
::QemuConfig-
>write_config($vmid, $conf);
3466 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3469 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);