1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 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.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
159 # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
160 my $cloudinit_iso_size = 4; # in MB
161 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, $name, $cloudinit_iso_size*1024);
163 $disk->{file
} = $volid;
164 $disk->{media
} = 'cdrom';
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
168 } elsif ($volid =~ $NEW_DISK_RE) {
169 my ($storeid, $size) = ($2 || $default_storage, $3);
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
172 my $fmt = $disk->{format
} || $defformat;
174 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
177 if ($ds eq 'efidisk0') {
178 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
180 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
182 push @$vollist, $volid;
183 $disk->{file
} = $volid;
184 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
185 delete $disk->{format
}; # no longer needed
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
189 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
191 my $volid_is_new = 1;
194 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
195 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
200 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
304 my $check_vm_modify_config_perm = sub {
305 my ($rpcenv, $authuser, $vmid, $pool, $key_list, $values) = @_;
307 return 1 if $authuser eq 'root@pam';
309 foreach my $opt (@$key_list) {
310 # disk checks need to be done somewhere else
311 next if PVE
::QemuServer
::is_valid_drivename
($opt);
312 next if $opt eq 'cdrom';
313 next if $opt =~ m/^unused\d+$/;
315 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
316 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
317 } elsif ($memoryoptions->{$opt}) {
318 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
319 } elsif ($hwtypeoptions->{$opt}) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
321 } elsif ($generaloptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
323 # special case for startup since it changes host behaviour
324 if ($opt eq 'startup') {
325 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
327 } elsif ($vmpoweroptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
329 } elsif ($diskoptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
331 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 } elsif ($opt =~ m/^serial\d+$/) {
334 if ($values && $values->{$opt} eq 'socket') {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
337 next; # deletion will be checked later since we do not have the config here
339 die "only root can set '$opt' config to real devices\n";
342 # catches usb\d+, hostpci\d+, args, lock, etc.
343 # new options will be checked here
344 die "only root can set '$opt' config\n";
351 __PACKAGE__-
>register_method({
355 description
=> "Virtual machine index (per node).",
357 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
361 protected
=> 1, # qemu pid files are only readable by root
363 additionalProperties
=> 0,
365 node
=> get_standard_option
('pve-node'),
369 description
=> "Determine the full status of active VMs.",
377 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
379 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
384 my $rpcenv = PVE
::RPCEnvironment
::get
();
385 my $authuser = $rpcenv->get_user();
387 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
390 foreach my $vmid (keys %$vmstatus) {
391 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
393 my $data = $vmstatus->{$vmid};
402 __PACKAGE__-
>register_method({
406 description
=> "Create or restore a virtual machine.",
408 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
409 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
410 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
411 user
=> 'all', # check inside
416 additionalProperties
=> 0,
417 properties
=> PVE
::QemuServer
::json_config_properties
(
419 node
=> get_standard_option
('pve-node'),
420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
422 description
=> "The backup file.",
426 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
428 storage
=> get_standard_option
('pve-storage-id', {
429 description
=> "Default storage.",
431 completion
=> \
&PVE
::QemuServer
::complete_storage
,
436 description
=> "Allow to overwrite existing VM.",
437 requires
=> 'archive',
442 description
=> "Assign a unique random ethernet address.",
443 requires
=> 'archive',
447 type
=> 'string', format
=> 'pve-poolid',
448 description
=> "Add the VM to the specified pool.",
451 description
=> "Override I/O bandwidth limit (in KiB/s).",
455 default => 'restore limit from datacenter or storage config',
461 description
=> "Start VM after it was created successfully.",
471 my $rpcenv = PVE
::RPCEnvironment
::get
();
473 my $authuser = $rpcenv->get_user();
475 my $node = extract_param
($param, 'node');
477 my $vmid = extract_param
($param, 'vmid');
479 my $archive = extract_param
($param, 'archive');
480 my $is_restore = !!$archive;
482 my $storage = extract_param
($param, 'storage');
484 my $force = extract_param
($param, 'force');
486 my $unique = extract_param
($param, 'unique');
488 my $pool = extract_param
($param, 'pool');
490 my $bwlimit = extract_param
($param, 'bwlimit');
492 my $start_after_create = extract_param
($param, 'start');
494 my $filename = PVE
::QemuConfig-
>config_file($vmid);
496 my $storecfg = PVE
::Storage
::config
();
498 if (defined(my $ssh_keys = $param->{sshkeys
})) {
499 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
500 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
503 PVE
::Cluster
::check_cfs_quorum
();
505 if (defined($pool)) {
506 $rpcenv->check_pool_exist($pool);
509 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
510 if defined($storage);
512 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
514 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
516 } elsif ($archive && $force && (-f
$filename) &&
517 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
518 # OK: user has VM.Backup permissions, and want to restore an existing VM
524 &$resolve_cdrom_alias($param);
526 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
528 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param], $param);
530 foreach my $opt (keys %$param) {
531 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
532 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
533 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
535 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
536 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
540 PVE
::QemuServer
::add_random_macs
($param);
542 my $keystr = join(' ', keys %$param);
543 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
545 if ($archive eq '-') {
546 die "pipe requires cli environment\n"
547 if $rpcenv->{type
} ne 'cli';
549 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
550 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
554 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
556 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
557 die "$emsg $@" if $@;
559 my $restorefn = sub {
560 my $conf = PVE
::QemuConfig-
>load_config($vmid);
562 PVE
::QemuConfig-
>check_protection($conf, $emsg);
564 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
565 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
568 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
572 bwlimit
=> $bwlimit, });
574 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
576 if ($start_after_create) {
577 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
582 # ensure no old replication state are exists
583 PVE
::ReplicationState
::delete_guest_states
($vmid);
585 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
589 # ensure no old replication state are exists
590 PVE
::ReplicationState
::delete_guest_states
($vmid);
598 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
602 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
604 if (!$conf->{bootdisk
}) {
605 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
606 $conf->{bootdisk
} = $firstdisk if $firstdisk;
609 # auto generate uuid if user did not specify smbios1 option
610 if (!$conf->{smbios1
}) {
611 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
614 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
615 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
618 PVE
::QemuConfig-
>write_config($vmid, $conf);
624 foreach my $volid (@$vollist) {
625 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
631 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
634 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
636 if ($start_after_create) {
637 print "Execute autostart\n";
638 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
643 my ($code, $worker_name);
645 $worker_name = 'qmrestore';
647 eval { $restorefn->() };
649 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
655 $worker_name = 'qmcreate';
657 eval { $createfn->() };
660 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
661 unlink($conffile) or die "failed to remove config file: $!\n";
669 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
672 __PACKAGE__-
>register_method({
677 description
=> "Directory index",
682 additionalProperties
=> 0,
684 node
=> get_standard_option
('pve-node'),
685 vmid
=> get_standard_option
('pve-vmid'),
693 subdir
=> { type
=> 'string' },
696 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
702 { subdir
=> 'config' },
703 { subdir
=> 'pending' },
704 { subdir
=> 'status' },
705 { subdir
=> 'unlink' },
706 { subdir
=> 'vncproxy' },
707 { subdir
=> 'termproxy' },
708 { subdir
=> 'migrate' },
709 { subdir
=> 'resize' },
710 { subdir
=> 'move' },
712 { subdir
=> 'rrddata' },
713 { subdir
=> 'monitor' },
714 { subdir
=> 'agent' },
715 { subdir
=> 'snapshot' },
716 { subdir
=> 'spiceproxy' },
717 { subdir
=> 'sendkey' },
718 { subdir
=> 'firewall' },
724 __PACKAGE__-
>register_method ({
725 subclass
=> "PVE::API2::Firewall::VM",
726 path
=> '{vmid}/firewall',
729 __PACKAGE__-
>register_method ({
730 subclass
=> "PVE::API2::Qemu::Agent",
731 path
=> '{vmid}/agent',
734 __PACKAGE__-
>register_method({
736 path
=> '{vmid}/rrd',
738 protected
=> 1, # fixme: can we avoid that?
740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
742 description
=> "Read VM RRD statistics (returns PNG)",
744 additionalProperties
=> 0,
746 node
=> get_standard_option
('pve-node'),
747 vmid
=> get_standard_option
('pve-vmid'),
749 description
=> "Specify the time frame you are interested in.",
751 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
754 description
=> "The list of datasources you want to display.",
755 type
=> 'string', format
=> 'pve-configid-list',
758 description
=> "The RRD consolidation function",
760 enum
=> [ 'AVERAGE', 'MAX' ],
768 filename
=> { type
=> 'string' },
774 return PVE
::Cluster
::create_rrd_graph
(
775 "pve2-vm/$param->{vmid}", $param->{timeframe
},
776 $param->{ds
}, $param->{cf
});
780 __PACKAGE__-
>register_method({
782 path
=> '{vmid}/rrddata',
784 protected
=> 1, # fixme: can we avoid that?
786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
788 description
=> "Read VM RRD statistics",
790 additionalProperties
=> 0,
792 node
=> get_standard_option
('pve-node'),
793 vmid
=> get_standard_option
('pve-vmid'),
795 description
=> "Specify the time frame you are interested in.",
797 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
800 description
=> "The RRD consolidation function",
802 enum
=> [ 'AVERAGE', 'MAX' ],
817 return PVE
::Cluster
::create_rrd_data
(
818 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
822 __PACKAGE__-
>register_method({
824 path
=> '{vmid}/config',
827 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
832 additionalProperties
=> 0,
834 node
=> get_standard_option
('pve-node'),
835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
837 description
=> "Get current values (instead of pending values).",
842 snapshot
=> get_standard_option
('pve-snapshot-name', {
843 description
=> "Fetch config values from given snapshot.",
846 my ($cmd, $pname, $cur, $args) = @_;
847 PVE
::QemuConfig-
>snapshot_list($args->[0]);
853 description
=> "The current VM configuration.",
855 properties
=> PVE
::QemuServer
::json_config_properties
({
858 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
865 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
867 if (my $snapname = $param->{snapshot
}) {
868 my $snapshot = $conf->{snapshots
}->{$snapname};
869 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
871 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
876 delete $conf->{snapshots
};
878 if (!$param->{current
}) {
879 foreach my $opt (keys %{$conf->{pending
}}) {
880 next if $opt eq 'delete';
881 my $value = $conf->{pending
}->{$opt};
882 next if ref($value); # just to be sure
883 $conf->{$opt} = $value;
885 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
886 foreach my $opt (keys %$pending_delete_hash) {
887 delete $conf->{$opt} if $conf->{$opt};
891 delete $conf->{pending
};
893 # hide cloudinit password
894 if ($conf->{cipassword
}) {
895 $conf->{cipassword
} = '**********';
901 __PACKAGE__-
>register_method({
902 name
=> 'vm_pending',
903 path
=> '{vmid}/pending',
906 description
=> "Get virtual machine configuration, including pending changes.",
908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
911 additionalProperties
=> 0,
913 node
=> get_standard_option
('pve-node'),
914 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
923 description
=> "Configuration option name.",
927 description
=> "Current value.",
932 description
=> "Pending value.",
937 description
=> "Indicates a pending delete request if present and not 0. " .
938 "The value 2 indicates a force-delete request.",
950 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
952 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
956 foreach my $opt (keys %$conf) {
957 next if ref($conf->{$opt});
958 my $item = { key
=> $opt };
959 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
960 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
961 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
963 # hide cloudinit password
964 if ($opt eq 'cipassword') {
965 $item->{value
} = '**********' if defined($item->{value
});
966 # the trailing space so that the pending string is different
967 $item->{pending
} = '********** ' if defined($item->{pending
});
972 foreach my $opt (keys %{$conf->{pending
}}) {
973 next if $opt eq 'delete';
974 next if ref($conf->{pending
}->{$opt}); # just to be sure
975 next if defined($conf->{$opt});
976 my $item = { key
=> $opt };
977 $item->{pending
} = $conf->{pending
}->{$opt};
979 # hide cloudinit password
980 if ($opt eq 'cipassword') {
981 $item->{pending
} = '**********' if defined($item->{pending
});
986 while (my ($opt, $force) = each %$pending_delete_hash) {
987 next if $conf->{pending
}->{$opt}; # just to be sure
988 next if $conf->{$opt};
989 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
996 # POST/PUT {vmid}/config implementation
998 # The original API used PUT (idempotent) an we assumed that all operations
999 # are fast. But it turned out that almost any configuration change can
1000 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1001 # time to complete and have side effects (not idempotent).
1003 # The new implementation uses POST and forks a worker process. We added
1004 # a new option 'background_delay'. If specified we wait up to
1005 # 'background_delay' second for the worker task to complete. It returns null
1006 # if the task is finished within that time, else we return the UPID.
1008 my $update_vm_api = sub {
1009 my ($param, $sync) = @_;
1011 my $rpcenv = PVE
::RPCEnvironment
::get
();
1013 my $authuser = $rpcenv->get_user();
1015 my $node = extract_param
($param, 'node');
1017 my $vmid = extract_param
($param, 'vmid');
1019 my $digest = extract_param
($param, 'digest');
1021 my $background_delay = extract_param
($param, 'background_delay');
1023 if (defined(my $cipassword = $param->{cipassword
})) {
1024 # Same logic as in cloud-init (but with the regex fixed...)
1025 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1026 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1029 my @paramarr = (); # used for log message
1030 foreach my $key (sort keys %$param) {
1031 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1032 push @paramarr, "-$key", $value;
1035 my $skiplock = extract_param
($param, 'skiplock');
1036 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1037 if $skiplock && $authuser ne 'root@pam';
1039 my $delete_str = extract_param
($param, 'delete');
1041 my $revert_str = extract_param
($param, 'revert');
1043 my $force = extract_param
($param, 'force');
1045 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1046 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1047 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1050 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1052 my $storecfg = PVE
::Storage
::config
();
1054 my $defaults = PVE
::QemuServer
::load_defaults
();
1056 &$resolve_cdrom_alias($param);
1058 # now try to verify all parameters
1061 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1062 if (!PVE
::QemuServer
::option_exists
($opt)) {
1063 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1066 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1067 "-revert $opt' at the same time" })
1068 if defined($param->{$opt});
1070 $revert->{$opt} = 1;
1074 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1075 $opt = 'ide2' if $opt eq 'cdrom';
1077 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1078 "-delete $opt' at the same time" })
1079 if defined($param->{$opt});
1081 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1082 "-revert $opt' at the same time" })
1085 if (!PVE
::QemuServer
::option_exists
($opt)) {
1086 raise_param_exc
({ delete => "unknown option '$opt'" });
1092 my $repl_conf = PVE
::ReplicationConfig-
>new();
1093 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1094 my $check_replication = sub {
1096 return if !$is_replicated;
1097 my $volid = $drive->{file
};
1098 return if !$volid || !($drive->{replicate
}//1);
1099 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1100 my ($storeid, $format);
1101 if ($volid =~ $NEW_DISK_RE) {
1103 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1105 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1106 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1108 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1110 return if $scfg->{shared
};
1111 die "cannot add non-replicatable volume to a replicated VM\n";
1114 foreach my $opt (keys %$param) {
1115 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1116 # cleanup drive path
1117 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1118 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1119 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1120 $check_replication->($drive);
1121 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1122 } elsif ($opt =~ m/^net(\d+)$/) {
1124 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1125 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1126 } elsif ($opt eq 'vmgenid') {
1127 if ($param->{$opt} eq '1') {
1128 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1130 } elsif ($opt eq 'hookscript') {
1131 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1132 raise_param_exc
({ $opt => $@ }) if $@;
1136 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1138 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param], $param);
1140 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1142 my $updatefn = sub {
1144 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1146 die "checksum missmatch (file change by other user?)\n"
1147 if $digest && $digest ne $conf->{digest
};
1149 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1151 foreach my $opt (keys %$revert) {
1152 if (defined($conf->{$opt})) {
1153 $param->{$opt} = $conf->{$opt};
1154 } elsif (defined($conf->{pending
}->{$opt})) {
1159 if ($param->{memory
} || defined($param->{balloon
})) {
1160 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1161 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1163 die "balloon value too large (must be smaller than assigned memory)\n"
1164 if $balloon && $balloon > $maxmem;
1167 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1171 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1173 # write updates to pending section
1175 my $modified = {}; # record what $option we modify
1177 foreach my $opt (@delete) {
1178 $modified->{$opt} = 1;
1179 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1180 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1181 warn "cannot delete '$opt' - not set in current configuration!\n";
1182 $modified->{$opt} = 0;
1186 if ($opt =~ m/^unused/) {
1187 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1188 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1189 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1190 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1191 delete $conf->{$opt};
1192 PVE
::QemuConfig-
>write_config($vmid, $conf);
1194 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1195 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1196 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1197 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1198 if defined($conf->{pending
}->{$opt});
1199 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1200 PVE
::QemuConfig-
>write_config($vmid, $conf);
1201 } elsif ($opt =~ m/^serial\d$/) {
1202 if ($conf->{$opt} eq 'socket') {
1203 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1204 } elsif ($authuser ne 'root@pam') {
1205 die "only root can delete '$opt' config for real devices\n";
1207 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1208 PVE
::QemuConfig-
>write_config($vmid, $conf);
1210 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1211 PVE
::QemuConfig-
>write_config($vmid, $conf);
1215 foreach my $opt (keys %$param) { # add/change
1216 $modified->{$opt} = 1;
1217 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1218 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1220 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1222 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1223 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1224 # FIXME: cloudinit: CDROM or Disk?
1225 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1226 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1228 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1230 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1231 if defined($conf->{pending
}->{$opt});
1233 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1235 $conf->{pending
}->{$opt} = $param->{$opt};
1237 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1238 PVE
::QemuConfig-
>write_config($vmid, $conf);
1241 # remove pending changes when nothing changed
1242 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1243 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1244 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1246 return if !scalar(keys %{$conf->{pending
}});
1248 my $running = PVE
::QemuServer
::check_running
($vmid);
1250 # apply pending changes
1252 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1256 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1257 raise_param_exc
($errors) if scalar(keys %$errors);
1259 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1269 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1271 if ($background_delay) {
1273 # Note: It would be better to do that in the Event based HTTPServer
1274 # to avoid blocking call to sleep.
1276 my $end_time = time() + $background_delay;
1278 my $task = PVE
::Tools
::upid_decode
($upid);
1281 while (time() < $end_time) {
1282 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1284 sleep(1); # this gets interrupted when child process ends
1288 my $status = PVE
::Tools
::upid_read_status
($upid);
1289 return undef if $status eq 'OK';
1298 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1301 my $vm_config_perm_list = [
1306 'VM.Config.Network',
1308 'VM.Config.Options',
1311 __PACKAGE__-
>register_method({
1312 name
=> 'update_vm_async',
1313 path
=> '{vmid}/config',
1317 description
=> "Set virtual machine options (asynchrounous API).",
1319 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1322 additionalProperties
=> 0,
1323 properties
=> PVE
::QemuServer
::json_config_properties
(
1325 node
=> get_standard_option
('pve-node'),
1326 vmid
=> get_standard_option
('pve-vmid'),
1327 skiplock
=> get_standard_option
('skiplock'),
1329 type
=> 'string', format
=> 'pve-configid-list',
1330 description
=> "A list of settings you want to delete.",
1334 type
=> 'string', format
=> 'pve-configid-list',
1335 description
=> "Revert a pending change.",
1340 description
=> $opt_force_description,
1342 requires
=> 'delete',
1346 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1350 background_delay
=> {
1352 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1363 code
=> $update_vm_api,
1366 __PACKAGE__-
>register_method({
1367 name
=> 'update_vm',
1368 path
=> '{vmid}/config',
1372 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1374 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1377 additionalProperties
=> 0,
1378 properties
=> PVE
::QemuServer
::json_config_properties
(
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1382 skiplock
=> get_standard_option
('skiplock'),
1384 type
=> 'string', format
=> 'pve-configid-list',
1385 description
=> "A list of settings you want to delete.",
1389 type
=> 'string', format
=> 'pve-configid-list',
1390 description
=> "Revert a pending change.",
1395 description
=> $opt_force_description,
1397 requires
=> 'delete',
1401 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1407 returns
=> { type
=> 'null' },
1410 &$update_vm_api($param, 1);
1416 __PACKAGE__-
>register_method({
1417 name
=> 'destroy_vm',
1422 description
=> "Destroy the vm (also delete all used/owned volumes).",
1424 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1427 additionalProperties
=> 0,
1429 node
=> get_standard_option
('pve-node'),
1430 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1431 skiplock
=> get_standard_option
('skiplock'),
1440 my $rpcenv = PVE
::RPCEnvironment
::get
();
1442 my $authuser = $rpcenv->get_user();
1444 my $vmid = $param->{vmid
};
1446 my $skiplock = $param->{skiplock
};
1447 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1448 if $skiplock && $authuser ne 'root@pam';
1451 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1453 my $storecfg = PVE
::Storage
::config
();
1455 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1457 die "unable to remove VM $vmid - used in HA resources\n"
1458 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1460 # do not allow destroy if there are replication jobs
1461 my $repl_conf = PVE
::ReplicationConfig-
>new();
1462 $repl_conf->check_for_existing_jobs($vmid);
1464 # early tests (repeat after locking)
1465 die "VM $vmid is running - destroy failed\n"
1466 if PVE
::QemuServer
::check_running
($vmid);
1471 syslog
('info', "destroy VM $vmid: $upid\n");
1473 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1475 PVE
::AccessControl
::remove_vm_access
($vmid);
1477 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1480 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1483 __PACKAGE__-
>register_method({
1485 path
=> '{vmid}/unlink',
1489 description
=> "Unlink/delete disk images.",
1491 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1499 type
=> 'string', format
=> 'pve-configid-list',
1500 description
=> "A list of disk IDs you want to delete.",
1504 description
=> $opt_force_description,
1509 returns
=> { type
=> 'null'},
1513 $param->{delete} = extract_param
($param, 'idlist');
1515 __PACKAGE__-
>update_vm($param);
1522 __PACKAGE__-
>register_method({
1524 path
=> '{vmid}/vncproxy',
1528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1530 description
=> "Creates a TCP VNC proxy connections.",
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid'),
1539 description
=> "starts websockify instead of vncproxy",
1544 additionalProperties
=> 0,
1546 user
=> { type
=> 'string' },
1547 ticket
=> { type
=> 'string' },
1548 cert
=> { type
=> 'string' },
1549 port
=> { type
=> 'integer' },
1550 upid
=> { type
=> 'string' },
1556 my $rpcenv = PVE
::RPCEnvironment
::get
();
1558 my $authuser = $rpcenv->get_user();
1560 my $vmid = $param->{vmid
};
1561 my $node = $param->{node
};
1562 my $websocket = $param->{websocket
};
1564 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1565 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1567 my $authpath = "/vms/$vmid";
1569 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1571 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1577 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1578 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1579 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1580 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1581 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1583 $family = PVE
::Tools
::get_host_address_family
($node);
1586 my $port = PVE
::Tools
::next_vnc_port
($family);
1593 syslog
('info', "starting vnc proxy $upid\n");
1599 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1601 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1602 '-timeout', $timeout, '-authpath', $authpath,
1603 '-perm', 'Sys.Console'];
1605 if ($param->{websocket
}) {
1606 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1607 push @$cmd, '-notls', '-listen', 'localhost';
1610 push @$cmd, '-c', @$remcmd, @$termcmd;
1612 PVE
::Tools
::run_command
($cmd);
1616 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1618 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1620 my $sock = IO
::Socket
::IP-
>new(
1625 GetAddrInfoFlags
=> 0,
1626 ) or die "failed to create socket: $!\n";
1627 # Inside the worker we shouldn't have any previous alarms
1628 # running anyway...:
1630 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1632 accept(my $cli, $sock) or die "connection failed: $!\n";
1635 if (PVE
::Tools
::run_command
($cmd,
1636 output
=> '>&'.fileno($cli),
1637 input
=> '<&'.fileno($cli),
1640 die "Failed to run vncproxy.\n";
1647 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1649 PVE
::Tools
::wait_for_vnc_port
($port);
1660 __PACKAGE__-
>register_method({
1661 name
=> 'termproxy',
1662 path
=> '{vmid}/termproxy',
1666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1668 description
=> "Creates a TCP proxy connections.",
1670 additionalProperties
=> 0,
1672 node
=> get_standard_option
('pve-node'),
1673 vmid
=> get_standard_option
('pve-vmid'),
1677 enum
=> [qw(serial0 serial1 serial2 serial3)],
1678 description
=> "opens a serial terminal (defaults to display)",
1683 additionalProperties
=> 0,
1685 user
=> { type
=> 'string' },
1686 ticket
=> { type
=> 'string' },
1687 port
=> { type
=> 'integer' },
1688 upid
=> { type
=> 'string' },
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $vmid = $param->{vmid
};
1699 my $node = $param->{node
};
1700 my $serial = $param->{serial
};
1702 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1704 if (!defined($serial)) {
1705 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1706 $serial = $conf->{vga
};
1710 my $authpath = "/vms/$vmid";
1712 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1717 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1718 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1719 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1720 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1721 push @$remcmd, '--';
1723 $family = PVE
::Tools
::get_host_address_family
($node);
1726 my $port = PVE
::Tools
::next_vnc_port
($family);
1728 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1729 push @$termcmd, '-iface', $serial if $serial;
1734 syslog
('info', "starting qemu termproxy $upid\n");
1736 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1737 '--perm', 'VM.Console', '--'];
1738 push @$cmd, @$remcmd, @$termcmd;
1740 PVE
::Tools
::run_command
($cmd);
1743 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1745 PVE
::Tools
::wait_for_vnc_port
($port);
1755 __PACKAGE__-
>register_method({
1756 name
=> 'vncwebsocket',
1757 path
=> '{vmid}/vncwebsocket',
1760 description
=> "You also need to pass a valid ticket (vncticket).",
1761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1763 description
=> "Opens a weksocket for VNC traffic.",
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1770 description
=> "Ticket from previous call to vncproxy.",
1775 description
=> "Port number returned by previous vncproxy call.",
1785 port
=> { type
=> 'string' },
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $vmid = $param->{vmid
};
1796 my $node = $param->{node
};
1798 my $authpath = "/vms/$vmid";
1800 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1802 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1804 # Note: VNC ports are acessible from outside, so we do not gain any
1805 # security if we verify that $param->{port} belongs to VM $vmid. This
1806 # check is done by verifying the VNC ticket (inside VNC protocol).
1808 my $port = $param->{port
};
1810 return { port
=> $port };
1813 __PACKAGE__-
>register_method({
1814 name
=> 'spiceproxy',
1815 path
=> '{vmid}/spiceproxy',
1820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1822 description
=> "Returns a SPICE configuration to connect to the VM.",
1824 additionalProperties
=> 0,
1826 node
=> get_standard_option
('pve-node'),
1827 vmid
=> get_standard_option
('pve-vmid'),
1828 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1831 returns
=> get_standard_option
('remote-viewer-config'),
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $vmid = $param->{vmid
};
1840 my $node = $param->{node
};
1841 my $proxy = $param->{proxy
};
1843 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1844 my $title = "VM $vmid";
1845 $title .= " - ". $conf->{name
} if $conf->{name
};
1847 my $port = PVE
::QemuServer
::spice_port
($vmid);
1849 my ($ticket, undef, $remote_viewer_config) =
1850 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1852 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1853 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1855 return $remote_viewer_config;
1858 __PACKAGE__-
>register_method({
1860 path
=> '{vmid}/status',
1863 description
=> "Directory index",
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1879 subdir
=> { type
=> 'string' },
1882 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1888 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1891 { subdir
=> 'current' },
1892 { subdir
=> 'start' },
1893 { subdir
=> 'stop' },
1899 __PACKAGE__-
>register_method({
1900 name
=> 'vm_status',
1901 path
=> '{vmid}/status/current',
1904 protected
=> 1, # qemu pid files are only readable by root
1905 description
=> "Get virtual machine status.",
1907 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1910 additionalProperties
=> 0,
1912 node
=> get_standard_option
('pve-node'),
1913 vmid
=> get_standard_option
('pve-vmid'),
1919 %$PVE::QemuServer
::vmstatus_return_properties
,
1921 description
=> "HA manager service status.",
1925 description
=> "Qemu VGA configuration supports spice.",
1930 description
=> "Qemu GuestAgent enabled in config.",
1940 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1942 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1943 my $status = $vmstatus->{$param->{vmid
}};
1945 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1947 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1948 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1953 __PACKAGE__-
>register_method({
1955 path
=> '{vmid}/status/start',
1959 description
=> "Start virtual machine.",
1961 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1964 additionalProperties
=> 0,
1966 node
=> get_standard_option
('pve-node'),
1967 vmid
=> get_standard_option
('pve-vmid',
1968 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1969 skiplock
=> get_standard_option
('skiplock'),
1970 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1971 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1974 enum
=> ['secure', 'insecure'],
1975 description
=> "Migration traffic is encrypted using an SSH " .
1976 "tunnel by default. On secure, completely private networks " .
1977 "this can be disabled to increase performance.",
1980 migration_network
=> {
1981 type
=> 'string', format
=> 'CIDR',
1982 description
=> "CIDR of the (sub) network that is used for migration.",
1985 machine
=> get_standard_option
('pve-qm-machine'),
1987 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1999 my $rpcenv = PVE
::RPCEnvironment
::get
();
2001 my $authuser = $rpcenv->get_user();
2003 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $machine = extract_param
($param, 'machine');
2009 my $stateuri = extract_param
($param, 'stateuri');
2010 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2011 if $stateuri && $authuser ne 'root@pam';
2013 my $skiplock = extract_param
($param, 'skiplock');
2014 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2015 if $skiplock && $authuser ne 'root@pam';
2017 my $migratedfrom = extract_param
($param, 'migratedfrom');
2018 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2019 if $migratedfrom && $authuser ne 'root@pam';
2021 my $migration_type = extract_param
($param, 'migration_type');
2022 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2023 if $migration_type && $authuser ne 'root@pam';
2025 my $migration_network = extract_param
($param, 'migration_network');
2026 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2027 if $migration_network && $authuser ne 'root@pam';
2029 my $targetstorage = extract_param
($param, 'targetstorage');
2030 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2031 if $targetstorage && $authuser ne 'root@pam';
2033 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2034 if $targetstorage && !$migratedfrom;
2036 # read spice ticket from STDIN
2038 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2039 if (defined(my $line = <STDIN
>)) {
2041 $spice_ticket = $line;
2045 PVE
::Cluster
::check_cfs_quorum
();
2047 my $storecfg = PVE
::Storage
::config
();
2049 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2050 $rpcenv->{type
} ne 'ha') {
2055 my $service = "vm:$vmid";
2057 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2059 print "Requesting HA start for VM $vmid\n";
2061 PVE
::Tools
::run_command
($cmd);
2066 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2073 syslog
('info', "start VM $vmid: $upid\n");
2075 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2076 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2081 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2085 __PACKAGE__-
>register_method({
2087 path
=> '{vmid}/status/stop',
2091 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2092 "is akin to pulling the power plug of a running computer and may damage the VM data",
2094 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2097 additionalProperties
=> 0,
2099 node
=> get_standard_option
('pve-node'),
2100 vmid
=> get_standard_option
('pve-vmid',
2101 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2102 skiplock
=> get_standard_option
('skiplock'),
2103 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2105 description
=> "Wait maximal timeout seconds.",
2111 description
=> "Do not deactivate storage volumes.",
2124 my $rpcenv = PVE
::RPCEnvironment
::get
();
2126 my $authuser = $rpcenv->get_user();
2128 my $node = extract_param
($param, 'node');
2130 my $vmid = extract_param
($param, 'vmid');
2132 my $skiplock = extract_param
($param, 'skiplock');
2133 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2134 if $skiplock && $authuser ne 'root@pam';
2136 my $keepActive = extract_param
($param, 'keepActive');
2137 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2138 if $keepActive && $authuser ne 'root@pam';
2140 my $migratedfrom = extract_param
($param, 'migratedfrom');
2141 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2142 if $migratedfrom && $authuser ne 'root@pam';
2145 my $storecfg = PVE
::Storage
::config
();
2147 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2152 my $service = "vm:$vmid";
2154 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2156 print "Requesting HA stop for VM $vmid\n";
2158 PVE
::Tools
::run_command
($cmd);
2163 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2169 syslog
('info', "stop VM $vmid: $upid\n");
2171 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2172 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2177 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2181 __PACKAGE__-
>register_method({
2183 path
=> '{vmid}/status/reset',
2187 description
=> "Reset virtual machine.",
2189 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2192 additionalProperties
=> 0,
2194 node
=> get_standard_option
('pve-node'),
2195 vmid
=> get_standard_option
('pve-vmid',
2196 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2197 skiplock
=> get_standard_option
('skiplock'),
2206 my $rpcenv = PVE
::RPCEnvironment
::get
();
2208 my $authuser = $rpcenv->get_user();
2210 my $node = extract_param
($param, 'node');
2212 my $vmid = extract_param
($param, 'vmid');
2214 my $skiplock = extract_param
($param, 'skiplock');
2215 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2216 if $skiplock && $authuser ne 'root@pam';
2218 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2223 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2228 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2231 __PACKAGE__-
>register_method({
2232 name
=> 'vm_shutdown',
2233 path
=> '{vmid}/status/shutdown',
2237 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2238 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2240 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2243 additionalProperties
=> 0,
2245 node
=> get_standard_option
('pve-node'),
2246 vmid
=> get_standard_option
('pve-vmid',
2247 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2248 skiplock
=> get_standard_option
('skiplock'),
2250 description
=> "Wait maximal timeout seconds.",
2256 description
=> "Make sure the VM stops.",
2262 description
=> "Do not deactivate storage volumes.",
2275 my $rpcenv = PVE
::RPCEnvironment
::get
();
2277 my $authuser = $rpcenv->get_user();
2279 my $node = extract_param
($param, 'node');
2281 my $vmid = extract_param
($param, 'vmid');
2283 my $skiplock = extract_param
($param, 'skiplock');
2284 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2285 if $skiplock && $authuser ne 'root@pam';
2287 my $keepActive = extract_param
($param, 'keepActive');
2288 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2289 if $keepActive && $authuser ne 'root@pam';
2291 my $storecfg = PVE
::Storage
::config
();
2295 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2296 # otherwise, we will infer a shutdown command, but run into the timeout,
2297 # then when the vm is resumed, it will instantly shutdown
2299 # checking the qmp status here to get feedback to the gui/cli/api
2300 # and the status query should not take too long
2303 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2307 if (!$err && $qmpstatus->{status
} eq "paused") {
2308 if ($param->{forceStop
}) {
2309 warn "VM is paused - stop instead of shutdown\n";
2312 die "VM is paused - cannot shutdown\n";
2316 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2317 ($rpcenv->{type
} ne 'ha')) {
2322 my $service = "vm:$vmid";
2324 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2326 print "Requesting HA stop for VM $vmid\n";
2328 PVE
::Tools
::run_command
($cmd);
2333 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2340 syslog
('info', "shutdown VM $vmid: $upid\n");
2342 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2343 $shutdown, $param->{forceStop
}, $keepActive);
2348 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2352 __PACKAGE__-
>register_method({
2353 name
=> 'vm_suspend',
2354 path
=> '{vmid}/status/suspend',
2358 description
=> "Suspend virtual machine.",
2360 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2363 additionalProperties
=> 0,
2365 node
=> get_standard_option
('pve-node'),
2366 vmid
=> get_standard_option
('pve-vmid',
2367 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2368 skiplock
=> get_standard_option
('skiplock'),
2373 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2375 statestorage
=> get_standard_option
('pve-storage-id', {
2376 description
=> "The storage for the VM state",
2377 requires
=> 'todisk',
2379 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2389 my $rpcenv = PVE
::RPCEnvironment
::get
();
2391 my $authuser = $rpcenv->get_user();
2393 my $node = extract_param
($param, 'node');
2395 my $vmid = extract_param
($param, 'vmid');
2397 my $todisk = extract_param
($param, 'todisk') // 0;
2399 my $statestorage = extract_param
($param, 'statestorage');
2401 my $skiplock = extract_param
($param, 'skiplock');
2402 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2403 if $skiplock && $authuser ne 'root@pam';
2405 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2407 die "Cannot suspend HA managed VM to disk\n"
2408 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2410 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2415 syslog
('info', "suspend VM $vmid: $upid\n");
2417 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2422 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2425 __PACKAGE__-
>register_method({
2426 name
=> 'vm_resume',
2427 path
=> '{vmid}/status/resume',
2431 description
=> "Resume virtual machine.",
2433 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2436 additionalProperties
=> 0,
2438 node
=> get_standard_option
('pve-node'),
2439 vmid
=> get_standard_option
('pve-vmid',
2440 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2441 skiplock
=> get_standard_option
('skiplock'),
2442 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2452 my $rpcenv = PVE
::RPCEnvironment
::get
();
2454 my $authuser = $rpcenv->get_user();
2456 my $node = extract_param
($param, 'node');
2458 my $vmid = extract_param
($param, 'vmid');
2460 my $skiplock = extract_param
($param, 'skiplock');
2461 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2462 if $skiplock && $authuser ne 'root@pam';
2464 my $nocheck = extract_param
($param, 'nocheck');
2466 my $to_disk_suspended;
2468 PVE
::QemuConfig-
>lock_config($vmid, sub {
2469 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2470 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2474 die "VM $vmid not running\n"
2475 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2480 syslog
('info', "resume VM $vmid: $upid\n");
2482 if (!$to_disk_suspended) {
2483 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2485 my $storecfg = PVE
::Storage
::config
();
2486 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2492 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2495 __PACKAGE__-
>register_method({
2496 name
=> 'vm_sendkey',
2497 path
=> '{vmid}/sendkey',
2501 description
=> "Send key event to virtual machine.",
2503 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2506 additionalProperties
=> 0,
2508 node
=> get_standard_option
('pve-node'),
2509 vmid
=> get_standard_option
('pve-vmid',
2510 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2511 skiplock
=> get_standard_option
('skiplock'),
2513 description
=> "The key (qemu monitor encoding).",
2518 returns
=> { type
=> 'null'},
2522 my $rpcenv = PVE
::RPCEnvironment
::get
();
2524 my $authuser = $rpcenv->get_user();
2526 my $node = extract_param
($param, 'node');
2528 my $vmid = extract_param
($param, 'vmid');
2530 my $skiplock = extract_param
($param, 'skiplock');
2531 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2532 if $skiplock && $authuser ne 'root@pam';
2534 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2539 __PACKAGE__-
>register_method({
2540 name
=> 'vm_feature',
2541 path
=> '{vmid}/feature',
2545 description
=> "Check if feature for virtual machine is available.",
2547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2550 additionalProperties
=> 0,
2552 node
=> get_standard_option
('pve-node'),
2553 vmid
=> get_standard_option
('pve-vmid'),
2555 description
=> "Feature to check.",
2557 enum
=> [ 'snapshot', 'clone', 'copy' ],
2559 snapname
=> get_standard_option
('pve-snapshot-name', {
2567 hasFeature
=> { type
=> 'boolean' },
2570 items
=> { type
=> 'string' },
2577 my $node = extract_param
($param, 'node');
2579 my $vmid = extract_param
($param, 'vmid');
2581 my $snapname = extract_param
($param, 'snapname');
2583 my $feature = extract_param
($param, 'feature');
2585 my $running = PVE
::QemuServer
::check_running
($vmid);
2587 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2590 my $snap = $conf->{snapshots
}->{$snapname};
2591 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2594 my $storecfg = PVE
::Storage
::config
();
2596 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2597 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2600 hasFeature
=> $hasFeature,
2601 nodes
=> [ keys %$nodelist ],
2605 __PACKAGE__-
>register_method({
2607 path
=> '{vmid}/clone',
2611 description
=> "Create a copy of virtual machine/template.",
2613 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2614 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2615 "'Datastore.AllocateSpace' on any used storage.",
2618 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2620 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2621 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2626 additionalProperties
=> 0,
2628 node
=> get_standard_option
('pve-node'),
2629 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2630 newid
=> get_standard_option
('pve-vmid', {
2631 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2632 description
=> 'VMID for the clone.' }),
2635 type
=> 'string', format
=> 'dns-name',
2636 description
=> "Set a name for the new VM.",
2641 description
=> "Description for the new VM.",
2645 type
=> 'string', format
=> 'pve-poolid',
2646 description
=> "Add the new VM to the specified pool.",
2648 snapname
=> get_standard_option
('pve-snapshot-name', {
2651 storage
=> get_standard_option
('pve-storage-id', {
2652 description
=> "Target storage for full clone.",
2656 description
=> "Target format for file storage. Only valid for full clone.",
2659 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2664 description
=> "Create a full copy of all disks. This is always done when " .
2665 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2667 target
=> get_standard_option
('pve-node', {
2668 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2672 description
=> "Override I/O bandwidth limit (in KiB/s).",
2676 default => 'clone limit from datacenter or storage config',
2686 my $rpcenv = PVE
::RPCEnvironment
::get
();
2688 my $authuser = $rpcenv->get_user();
2690 my $node = extract_param
($param, 'node');
2692 my $vmid = extract_param
($param, 'vmid');
2694 my $newid = extract_param
($param, 'newid');
2696 my $pool = extract_param
($param, 'pool');
2698 if (defined($pool)) {
2699 $rpcenv->check_pool_exist($pool);
2702 my $snapname = extract_param
($param, 'snapname');
2704 my $storage = extract_param
($param, 'storage');
2706 my $format = extract_param
($param, 'format');
2708 my $target = extract_param
($param, 'target');
2710 my $localnode = PVE
::INotify
::nodename
();
2712 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2714 PVE
::Cluster
::check_node_exists
($target) if $target;
2716 my $storecfg = PVE
::Storage
::config
();
2719 # check if storage is enabled on local node
2720 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2722 # check if storage is available on target node
2723 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2724 # clone only works if target storage is shared
2725 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2726 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2730 PVE
::Cluster
::check_cfs_quorum
();
2732 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2734 # exclusive lock if VM is running - else shared lock is enough;
2735 my $shared_lock = $running ?
0 : 1;
2739 # do all tests after lock
2740 # we also try to do all tests before we fork the worker
2742 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2744 PVE
::QemuConfig-
>check_lock($conf);
2746 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2748 die "unexpected state change\n" if $verify_running != $running;
2750 die "snapshot '$snapname' does not exist\n"
2751 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2753 my $full = extract_param
($param, 'full');
2754 if (!defined($full)) {
2755 $full = !PVE
::QemuConfig-
>is_template($conf);
2758 die "parameter 'storage' not allowed for linked clones\n"
2759 if defined($storage) && !$full;
2761 die "parameter 'format' not allowed for linked clones\n"
2762 if defined($format) && !$full;
2764 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2766 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2768 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2770 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2772 die "unable to create VM $newid: config file already exists\n"
2775 my $newconf = { lock => 'clone' };
2780 foreach my $opt (keys %$oldconf) {
2781 my $value = $oldconf->{$opt};
2783 # do not copy snapshot related info
2784 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2785 $opt eq 'vmstate' || $opt eq 'snapstate';
2787 # no need to copy unused images, because VMID(owner) changes anyways
2788 next if $opt =~ m/^unused\d+$/;
2790 # always change MAC! address
2791 if ($opt =~ m/^net(\d+)$/) {
2792 my $net = PVE
::QemuServer
::parse_net
($value);
2793 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2794 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2795 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2796 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2797 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2798 die "unable to parse drive options for '$opt'\n" if !$drive;
2799 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2800 $newconf->{$opt} = $value; # simply copy configuration
2802 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2803 die "Full clone feature is not supported for drive '$opt'\n"
2804 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2805 $fullclone->{$opt} = 1;
2807 # not full means clone instead of copy
2808 die "Linked clone feature is not supported for drive '$opt'\n"
2809 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2811 $drives->{$opt} = $drive;
2812 push @$vollist, $drive->{file
};
2815 # copy everything else
2816 $newconf->{$opt} = $value;
2820 # auto generate a new uuid
2821 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2822 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2823 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2825 # auto generate a new vmgenid if the option was set
2826 if ($newconf->{vmgenid
}) {
2827 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2830 delete $newconf->{template
};
2832 if ($param->{name
}) {
2833 $newconf->{name
} = $param->{name
};
2835 if ($oldconf->{name
}) {
2836 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2838 $newconf->{name
} = "Copy-of-VM-$vmid";
2842 if ($param->{description
}) {
2843 $newconf->{description
} = $param->{description
};
2846 # create empty/temp config - this fails if VM already exists on other node
2847 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2852 my $newvollist = [];
2859 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2861 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2863 my $bwlimit = extract_param
($param, 'bwlimit');
2865 my $total_jobs = scalar(keys %{$drives});
2868 foreach my $opt (keys %$drives) {
2869 my $drive = $drives->{$opt};
2870 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2872 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2873 my $storage_list = [ $src_sid ];
2874 push @$storage_list, $storage if defined($storage);
2875 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2877 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2878 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2879 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2881 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2883 PVE
::QemuConfig-
>write_config($newid, $newconf);
2887 delete $newconf->{lock};
2889 # do not write pending changes
2890 if (my @changes = keys %{$newconf->{pending
}}) {
2891 my $pending = join(',', @changes);
2892 warn "found pending changes for '$pending', discarding for clone\n";
2893 delete $newconf->{pending
};
2896 PVE
::QemuConfig-
>write_config($newid, $newconf);
2899 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2900 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2901 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2903 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2904 die "Failed to move config to node '$target' - rename failed: $!\n"
2905 if !rename($conffile, $newconffile);
2908 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2913 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2915 sleep 1; # some storage like rbd need to wait before release volume - really?
2917 foreach my $volid (@$newvollist) {
2918 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2921 die "clone failed: $err";
2927 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2929 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2932 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2933 # Aquire exclusive lock lock for $newid
2934 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2939 __PACKAGE__-
>register_method({
2940 name
=> 'move_vm_disk',
2941 path
=> '{vmid}/move_disk',
2945 description
=> "Move volume to different storage.",
2947 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2949 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2950 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2954 additionalProperties
=> 0,
2956 node
=> get_standard_option
('pve-node'),
2957 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2960 description
=> "The disk you want to move.",
2961 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2963 storage
=> get_standard_option
('pve-storage-id', {
2964 description
=> "Target storage.",
2965 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2969 description
=> "Target Format.",
2970 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2975 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2981 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2986 description
=> "Override I/O bandwidth limit (in KiB/s).",
2990 default => 'move limit from datacenter or storage config',
2996 description
=> "the task ID.",
3001 my $rpcenv = PVE
::RPCEnvironment
::get
();
3003 my $authuser = $rpcenv->get_user();
3005 my $node = extract_param
($param, 'node');
3007 my $vmid = extract_param
($param, 'vmid');
3009 my $digest = extract_param
($param, 'digest');
3011 my $disk = extract_param
($param, 'disk');
3013 my $storeid = extract_param
($param, 'storage');
3015 my $format = extract_param
($param, 'format');
3017 my $storecfg = PVE
::Storage
::config
();
3019 my $updatefn = sub {
3021 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3023 PVE
::QemuConfig-
>check_lock($conf);
3025 die "checksum missmatch (file change by other user?)\n"
3026 if $digest && $digest ne $conf->{digest
};
3028 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3030 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3032 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3034 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3037 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3038 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3042 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3043 (!$format || !$oldfmt || $oldfmt eq $format);
3045 # this only checks snapshots because $disk is passed!
3046 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3047 die "you can't move a disk with snapshots and delete the source\n"
3048 if $snapshotted && $param->{delete};
3050 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3052 my $running = PVE
::QemuServer
::check_running
($vmid);
3054 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3058 my $newvollist = [];
3064 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3066 warn "moving disk with snapshots, snapshots will not be moved!\n"
3069 my $bwlimit = extract_param
($param, 'bwlimit');
3070 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3072 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3073 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3075 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3077 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3079 # convert moved disk to base if part of template
3080 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3081 if PVE
::QemuConfig-
>is_template($conf);
3083 PVE
::QemuConfig-
>write_config($vmid, $conf);
3085 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3086 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3090 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3091 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3098 foreach my $volid (@$newvollist) {
3099 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3102 die "storage migration failed: $err";
3105 if ($param->{delete}) {
3107 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3108 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3114 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3117 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3120 __PACKAGE__-
>register_method({
3121 name
=> 'migrate_vm',
3122 path
=> '{vmid}/migrate',
3126 description
=> "Migrate virtual machine. Creates a new migration task.",
3128 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3131 additionalProperties
=> 0,
3133 node
=> get_standard_option
('pve-node'),
3134 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3135 target
=> get_standard_option
('pve-node', {
3136 description
=> "Target node.",
3137 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3141 description
=> "Use online/live migration.",
3146 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3151 enum
=> ['secure', 'insecure'],
3152 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3155 migration_network
=> {
3156 type
=> 'string', format
=> 'CIDR',
3157 description
=> "CIDR of the (sub) network that is used for migration.",
3160 "with-local-disks" => {
3162 description
=> "Enable live storage migration for local disk",
3165 targetstorage
=> get_standard_option
('pve-storage-id', {
3166 description
=> "Default target storage.",
3168 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3171 description
=> "Override I/O bandwidth limit (in KiB/s).",
3175 default => 'migrate limit from datacenter or storage config',
3181 description
=> "the task ID.",
3186 my $rpcenv = PVE
::RPCEnvironment
::get
();
3188 my $authuser = $rpcenv->get_user();
3190 my $target = extract_param
($param, 'target');
3192 my $localnode = PVE
::INotify
::nodename
();
3193 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3195 PVE
::Cluster
::check_cfs_quorum
();
3197 PVE
::Cluster
::check_node_exists
($target);
3199 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3201 my $vmid = extract_param
($param, 'vmid');
3203 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3204 if !$param->{online
} && $param->{targetstorage
};
3206 raise_param_exc
({ force
=> "Only root may use this option." })
3207 if $param->{force
} && $authuser ne 'root@pam';
3209 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3210 if $param->{migration_type
} && $authuser ne 'root@pam';
3212 # allow root only until better network permissions are available
3213 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3214 if $param->{migration_network
} && $authuser ne 'root@pam';
3217 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3219 # try to detect errors early
3221 PVE
::QemuConfig-
>check_lock($conf);
3223 if (PVE
::QemuServer
::check_running
($vmid)) {
3224 die "cant migrate running VM without --online\n"
3225 if !$param->{online
};
3228 my $storecfg = PVE
::Storage
::config
();
3230 if( $param->{targetstorage
}) {
3231 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3233 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3236 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3241 my $service = "vm:$vmid";
3243 my $cmd = ['ha-manager', 'migrate', $service, $target];
3245 print "Requesting HA migration for VM $vmid to node $target\n";
3247 PVE
::Tools
::run_command
($cmd);
3252 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3257 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3261 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3264 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3269 __PACKAGE__-
>register_method({
3271 path
=> '{vmid}/monitor',
3275 description
=> "Execute Qemu monitor commands.",
3277 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3281 additionalProperties
=> 0,
3283 node
=> get_standard_option
('pve-node'),
3284 vmid
=> get_standard_option
('pve-vmid'),
3287 description
=> "The monitor command.",
3291 returns
=> { type
=> 'string'},
3295 my $rpcenv = PVE
::RPCEnvironment
::get
();
3296 my $authuser = $rpcenv->get_user();
3299 my $command = shift;
3300 return $command =~ m/^\s*info(\s+|$)/
3301 || $command =~ m/^\s*help\s*$/;
3304 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3305 if !&$is_ro($param->{command
});
3307 my $vmid = $param->{vmid
};
3309 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3313 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3315 $res = "ERROR: $@" if $@;
3320 __PACKAGE__-
>register_method({
3321 name
=> 'resize_vm',
3322 path
=> '{vmid}/resize',
3326 description
=> "Extend volume size.",
3328 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3331 additionalProperties
=> 0,
3333 node
=> get_standard_option
('pve-node'),
3334 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3335 skiplock
=> get_standard_option
('skiplock'),
3338 description
=> "The disk you want to resize.",
3339 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3343 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3344 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.",
3348 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3354 returns
=> { type
=> 'null'},
3358 my $rpcenv = PVE
::RPCEnvironment
::get
();
3360 my $authuser = $rpcenv->get_user();
3362 my $node = extract_param
($param, 'node');
3364 my $vmid = extract_param
($param, 'vmid');
3366 my $digest = extract_param
($param, 'digest');
3368 my $disk = extract_param
($param, 'disk');
3370 my $sizestr = extract_param
($param, 'size');
3372 my $skiplock = extract_param
($param, 'skiplock');
3373 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3374 if $skiplock && $authuser ne 'root@pam';
3376 my $storecfg = PVE
::Storage
::config
();
3378 my $updatefn = sub {
3380 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3382 die "checksum missmatch (file change by other user?)\n"
3383 if $digest && $digest ne $conf->{digest
};
3384 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3386 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3388 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3390 my (undef, undef, undef, undef, undef, undef, $format) =
3391 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3393 die "can't resize volume: $disk if snapshot exists\n"
3394 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3396 my $volid = $drive->{file
};
3398 die "disk '$disk' has no associated volume\n" if !$volid;
3400 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3402 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3404 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3406 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3407 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3409 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3410 my ($ext, $newsize, $unit) = ($1, $2, $4);
3413 $newsize = $newsize * 1024;
3414 } elsif ($unit eq 'M') {
3415 $newsize = $newsize * 1024 * 1024;
3416 } elsif ($unit eq 'G') {
3417 $newsize = $newsize * 1024 * 1024 * 1024;
3418 } elsif ($unit eq 'T') {
3419 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3422 $newsize += $size if $ext;
3423 $newsize = int($newsize);
3425 die "shrinking disks is not supported\n" if $newsize < $size;
3427 return if $size == $newsize;
3429 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3431 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3433 $drive->{size
} = $newsize;
3434 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3436 PVE
::QemuConfig-
>write_config($vmid, $conf);
3439 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3443 __PACKAGE__-
>register_method({
3444 name
=> 'snapshot_list',
3445 path
=> '{vmid}/snapshot',
3447 description
=> "List all snapshots.",
3449 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3452 protected
=> 1, # qemu pid files are only readable by root
3454 additionalProperties
=> 0,
3456 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3457 node
=> get_standard_option
('pve-node'),
3466 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3470 description
=> "Snapshot includes RAM.",
3475 description
=> "Snapshot description.",
3479 description
=> "Snapshot creation time",
3481 renderer
=> 'timestamp',
3485 description
=> "Parent snapshot identifier.",
3491 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3496 my $vmid = $param->{vmid
};
3498 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3499 my $snaphash = $conf->{snapshots
} || {};
3503 foreach my $name (keys %$snaphash) {
3504 my $d = $snaphash->{$name};
3507 snaptime
=> $d->{snaptime
} || 0,
3508 vmstate
=> $d->{vmstate
} ?
1 : 0,
3509 description
=> $d->{description
} || '',
3511 $item->{parent
} = $d->{parent
} if $d->{parent
};
3512 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3516 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3519 digest
=> $conf->{digest
},
3520 running
=> $running,
3521 description
=> "You are here!",
3523 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3525 push @$res, $current;
3530 __PACKAGE__-
>register_method({
3532 path
=> '{vmid}/snapshot',
3536 description
=> "Snapshot a VM.",
3538 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3541 additionalProperties
=> 0,
3543 node
=> get_standard_option
('pve-node'),
3544 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3545 snapname
=> get_standard_option
('pve-snapshot-name'),
3549 description
=> "Save the vmstate",
3554 description
=> "A textual description or comment.",
3560 description
=> "the task ID.",
3565 my $rpcenv = PVE
::RPCEnvironment
::get
();
3567 my $authuser = $rpcenv->get_user();
3569 my $node = extract_param
($param, 'node');
3571 my $vmid = extract_param
($param, 'vmid');
3573 my $snapname = extract_param
($param, 'snapname');
3575 die "unable to use snapshot name 'current' (reserved name)\n"
3576 if $snapname eq 'current';
3579 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3580 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3581 $param->{description
});
3584 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3587 __PACKAGE__-
>register_method({
3588 name
=> 'snapshot_cmd_idx',
3589 path
=> '{vmid}/snapshot/{snapname}',
3596 additionalProperties
=> 0,
3598 vmid
=> get_standard_option
('pve-vmid'),
3599 node
=> get_standard_option
('pve-node'),
3600 snapname
=> get_standard_option
('pve-snapshot-name'),
3609 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3616 push @$res, { cmd
=> 'rollback' };
3617 push @$res, { cmd
=> 'config' };
3622 __PACKAGE__-
>register_method({
3623 name
=> 'update_snapshot_config',
3624 path
=> '{vmid}/snapshot/{snapname}/config',
3628 description
=> "Update snapshot metadata.",
3630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3633 additionalProperties
=> 0,
3635 node
=> get_standard_option
('pve-node'),
3636 vmid
=> get_standard_option
('pve-vmid'),
3637 snapname
=> get_standard_option
('pve-snapshot-name'),
3641 description
=> "A textual description or comment.",
3645 returns
=> { type
=> 'null' },
3649 my $rpcenv = PVE
::RPCEnvironment
::get
();
3651 my $authuser = $rpcenv->get_user();
3653 my $vmid = extract_param
($param, 'vmid');
3655 my $snapname = extract_param
($param, 'snapname');
3657 return undef if !defined($param->{description
});
3659 my $updatefn = sub {
3661 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3663 PVE
::QemuConfig-
>check_lock($conf);
3665 my $snap = $conf->{snapshots
}->{$snapname};
3667 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3669 $snap->{description
} = $param->{description
} if defined($param->{description
});
3671 PVE
::QemuConfig-
>write_config($vmid, $conf);
3674 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3679 __PACKAGE__-
>register_method({
3680 name
=> 'get_snapshot_config',
3681 path
=> '{vmid}/snapshot/{snapname}/config',
3684 description
=> "Get snapshot configuration",
3686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3689 additionalProperties
=> 0,
3691 node
=> get_standard_option
('pve-node'),
3692 vmid
=> get_standard_option
('pve-vmid'),
3693 snapname
=> get_standard_option
('pve-snapshot-name'),
3696 returns
=> { type
=> "object" },
3700 my $rpcenv = PVE
::RPCEnvironment
::get
();
3702 my $authuser = $rpcenv->get_user();
3704 my $vmid = extract_param
($param, 'vmid');
3706 my $snapname = extract_param
($param, 'snapname');
3708 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3710 my $snap = $conf->{snapshots
}->{$snapname};
3712 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3717 __PACKAGE__-
>register_method({
3719 path
=> '{vmid}/snapshot/{snapname}/rollback',
3723 description
=> "Rollback VM state to specified snapshot.",
3725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3728 additionalProperties
=> 0,
3730 node
=> get_standard_option
('pve-node'),
3731 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3732 snapname
=> get_standard_option
('pve-snapshot-name'),
3737 description
=> "the task ID.",
3742 my $rpcenv = PVE
::RPCEnvironment
::get
();
3744 my $authuser = $rpcenv->get_user();
3746 my $node = extract_param
($param, 'node');
3748 my $vmid = extract_param
($param, 'vmid');
3750 my $snapname = extract_param
($param, 'snapname');
3753 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3754 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3758 # hold migration lock, this makes sure that nobody create replication snapshots
3759 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3762 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3765 __PACKAGE__-
>register_method({
3766 name
=> 'delsnapshot',
3767 path
=> '{vmid}/snapshot/{snapname}',
3771 description
=> "Delete a VM snapshot.",
3773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3776 additionalProperties
=> 0,
3778 node
=> get_standard_option
('pve-node'),
3779 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3780 snapname
=> get_standard_option
('pve-snapshot-name'),
3784 description
=> "For removal from config file, even if removing disk snapshots fails.",
3790 description
=> "the task ID.",
3795 my $rpcenv = PVE
::RPCEnvironment
::get
();
3797 my $authuser = $rpcenv->get_user();
3799 my $node = extract_param
($param, 'node');
3801 my $vmid = extract_param
($param, 'vmid');
3803 my $snapname = extract_param
($param, 'snapname');
3806 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3807 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3810 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3813 __PACKAGE__-
>register_method({
3815 path
=> '{vmid}/template',
3819 description
=> "Create a Template.",
3821 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3822 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3825 additionalProperties
=> 0,
3827 node
=> get_standard_option
('pve-node'),
3828 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3832 description
=> "If you want to convert only 1 disk to base image.",
3833 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3838 returns
=> { type
=> 'null'},
3842 my $rpcenv = PVE
::RPCEnvironment
::get
();
3844 my $authuser = $rpcenv->get_user();
3846 my $node = extract_param
($param, 'node');
3848 my $vmid = extract_param
($param, 'vmid');
3850 my $disk = extract_param
($param, 'disk');
3852 my $updatefn = sub {
3854 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3856 PVE
::QemuConfig-
>check_lock($conf);
3858 die "unable to create template, because VM contains snapshots\n"
3859 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3861 die "you can't convert a template to a template\n"
3862 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3864 die "you can't convert a VM to template if VM is running\n"
3865 if PVE
::QemuServer
::check_running
($vmid);
3868 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3871 $conf->{template
} = 1;
3872 PVE
::QemuConfig-
>write_config($vmid, $conf);
3874 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3877 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);