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) = @_;
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']);
334 # catches usb\d+, hostpci\d+, args, lock, etc.
335 # new options will be checked here
336 die "only root can set '$opt' config\n";
343 __PACKAGE__-
>register_method({
347 description
=> "Virtual machine index (per node).",
349 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
353 protected
=> 1, # qemu pid files are only readable by root
355 additionalProperties
=> 0,
357 node
=> get_standard_option
('pve-node'),
361 description
=> "Determine the full status of active VMs.",
369 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
371 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
376 my $rpcenv = PVE
::RPCEnvironment
::get
();
377 my $authuser = $rpcenv->get_user();
379 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
382 foreach my $vmid (keys %$vmstatus) {
383 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
385 my $data = $vmstatus->{$vmid};
394 __PACKAGE__-
>register_method({
398 description
=> "Create or restore a virtual machine.",
400 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
401 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
402 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
403 user
=> 'all', # check inside
408 additionalProperties
=> 0,
409 properties
=> PVE
::QemuServer
::json_config_properties
(
411 node
=> get_standard_option
('pve-node'),
412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
414 description
=> "The backup file.",
418 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
420 storage
=> get_standard_option
('pve-storage-id', {
421 description
=> "Default storage.",
423 completion
=> \
&PVE
::QemuServer
::complete_storage
,
428 description
=> "Allow to overwrite existing VM.",
429 requires
=> 'archive',
434 description
=> "Assign a unique random ethernet address.",
435 requires
=> 'archive',
439 type
=> 'string', format
=> 'pve-poolid',
440 description
=> "Add the VM to the specified pool.",
443 description
=> "Override i/o bandwidth limit (in KiB/s).",
452 description
=> "Start VM after it was created successfully.",
462 my $rpcenv = PVE
::RPCEnvironment
::get
();
464 my $authuser = $rpcenv->get_user();
466 my $node = extract_param
($param, 'node');
468 my $vmid = extract_param
($param, 'vmid');
470 my $archive = extract_param
($param, 'archive');
471 my $is_restore = !!$archive;
473 my $storage = extract_param
($param, 'storage');
475 my $force = extract_param
($param, 'force');
477 my $unique = extract_param
($param, 'unique');
479 my $pool = extract_param
($param, 'pool');
481 my $bwlimit = extract_param
($param, 'bwlimit');
483 my $start_after_create = extract_param
($param, 'start');
485 my $filename = PVE
::QemuConfig-
>config_file($vmid);
487 my $storecfg = PVE
::Storage
::config
();
489 if (defined(my $ssh_keys = $param->{sshkeys
})) {
490 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
491 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
494 PVE
::Cluster
::check_cfs_quorum
();
496 if (defined($pool)) {
497 $rpcenv->check_pool_exist($pool);
500 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
501 if defined($storage);
503 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
505 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
507 } elsif ($archive && $force && (-f
$filename) &&
508 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
509 # OK: user has VM.Backup permissions, and want to restore an existing VM
515 &$resolve_cdrom_alias($param);
517 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
519 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
521 foreach my $opt (keys %$param) {
522 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
523 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
524 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
526 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
527 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
531 PVE
::QemuServer
::add_random_macs
($param);
533 my $keystr = join(' ', keys %$param);
534 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
536 if ($archive eq '-') {
537 die "pipe requires cli environment\n"
538 if $rpcenv->{type
} ne 'cli';
540 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
541 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
545 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
547 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
548 die "$emsg $@" if $@;
550 my $restorefn = sub {
551 my $conf = PVE
::QemuConfig-
>load_config($vmid);
553 PVE
::QemuConfig-
>check_protection($conf, $emsg);
555 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
556 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
559 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
563 bwlimit
=> $bwlimit, });
565 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
567 if ($start_after_create) {
568 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
573 # ensure no old replication state are exists
574 PVE
::ReplicationState
::delete_guest_states
($vmid);
576 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
580 # ensure no old replication state are exists
581 PVE
::ReplicationState
::delete_guest_states
($vmid);
589 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
593 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
595 if (!$conf->{bootdisk
}) {
596 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
597 $conf->{bootdisk
} = $firstdisk if $firstdisk;
600 # auto generate uuid if user did not specify smbios1 option
601 if (!$conf->{smbios1
}) {
602 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
605 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
606 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
609 PVE
::QemuConfig-
>write_config($vmid, $conf);
615 foreach my $volid (@$vollist) {
616 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
622 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
625 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
627 if ($start_after_create) {
628 print "Execute autostart\n";
629 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
634 my ($code, $worker_name);
636 $worker_name = 'qmrestore';
638 eval { $restorefn->() };
640 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
646 $worker_name = 'qmcreate';
648 eval { $createfn->() };
651 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
652 unlink($conffile) or die "failed to remove config file: $!\n";
660 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
663 __PACKAGE__-
>register_method({
668 description
=> "Directory index",
673 additionalProperties
=> 0,
675 node
=> get_standard_option
('pve-node'),
676 vmid
=> get_standard_option
('pve-vmid'),
684 subdir
=> { type
=> 'string' },
687 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
693 { subdir
=> 'config' },
694 { subdir
=> 'pending' },
695 { subdir
=> 'status' },
696 { subdir
=> 'unlink' },
697 { subdir
=> 'vncproxy' },
698 { subdir
=> 'termproxy' },
699 { subdir
=> 'migrate' },
700 { subdir
=> 'resize' },
701 { subdir
=> 'move' },
703 { subdir
=> 'rrddata' },
704 { subdir
=> 'monitor' },
705 { subdir
=> 'agent' },
706 { subdir
=> 'snapshot' },
707 { subdir
=> 'spiceproxy' },
708 { subdir
=> 'sendkey' },
709 { subdir
=> 'firewall' },
715 __PACKAGE__-
>register_method ({
716 subclass
=> "PVE::API2::Firewall::VM",
717 path
=> '{vmid}/firewall',
720 __PACKAGE__-
>register_method ({
721 subclass
=> "PVE::API2::Qemu::Agent",
722 path
=> '{vmid}/agent',
725 __PACKAGE__-
>register_method({
727 path
=> '{vmid}/rrd',
729 protected
=> 1, # fixme: can we avoid that?
731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
733 description
=> "Read VM RRD statistics (returns PNG)",
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
738 vmid
=> get_standard_option
('pve-vmid'),
740 description
=> "Specify the time frame you are interested in.",
742 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
745 description
=> "The list of datasources you want to display.",
746 type
=> 'string', format
=> 'pve-configid-list',
749 description
=> "The RRD consolidation function",
751 enum
=> [ 'AVERAGE', 'MAX' ],
759 filename
=> { type
=> 'string' },
765 return PVE
::Cluster
::create_rrd_graph
(
766 "pve2-vm/$param->{vmid}", $param->{timeframe
},
767 $param->{ds
}, $param->{cf
});
771 __PACKAGE__-
>register_method({
773 path
=> '{vmid}/rrddata',
775 protected
=> 1, # fixme: can we avoid that?
777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
779 description
=> "Read VM RRD statistics",
781 additionalProperties
=> 0,
783 node
=> get_standard_option
('pve-node'),
784 vmid
=> get_standard_option
('pve-vmid'),
786 description
=> "Specify the time frame you are interested in.",
788 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
791 description
=> "The RRD consolidation function",
793 enum
=> [ 'AVERAGE', 'MAX' ],
808 return PVE
::Cluster
::create_rrd_data
(
809 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
813 __PACKAGE__-
>register_method({
815 path
=> '{vmid}/config',
818 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
823 additionalProperties
=> 0,
825 node
=> get_standard_option
('pve-node'),
826 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
828 description
=> "Get current values (instead of pending values).",
833 snapshot
=> get_standard_option
('pve-snapshot-name', {
834 description
=> "Fetch config values from given snapshot.",
837 my ($cmd, $pname, $cur, $args) = @_;
838 PVE
::QemuConfig-
>snapshot_list($args->[0]);
844 description
=> "The current VM configuration.",
846 properties
=> PVE
::QemuServer
::json_config_properties
({
849 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
856 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
858 if (my $snapname = $param->{snapshot
}) {
859 my $snapshot = $conf->{snapshots
}->{$snapname};
860 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
862 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
867 delete $conf->{snapshots
};
869 if (!$param->{current
}) {
870 foreach my $opt (keys %{$conf->{pending
}}) {
871 next if $opt eq 'delete';
872 my $value = $conf->{pending
}->{$opt};
873 next if ref($value); # just to be sure
874 $conf->{$opt} = $value;
876 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
877 foreach my $opt (keys %$pending_delete_hash) {
878 delete $conf->{$opt} if $conf->{$opt};
882 delete $conf->{pending
};
884 # hide cloudinit password
885 if ($conf->{cipassword
}) {
886 $conf->{cipassword
} = '**********';
892 __PACKAGE__-
>register_method({
893 name
=> 'vm_pending',
894 path
=> '{vmid}/pending',
897 description
=> "Get virtual machine configuration, including pending changes.",
899 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
902 additionalProperties
=> 0,
904 node
=> get_standard_option
('pve-node'),
905 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
914 description
=> "Configuration option name.",
918 description
=> "Current value.",
923 description
=> "Pending value.",
928 description
=> "Indicates a pending delete request if present and not 0. " .
929 "The value 2 indicates a force-delete request.",
941 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
943 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
947 foreach my $opt (keys %$conf) {
948 next if ref($conf->{$opt});
949 my $item = { key
=> $opt };
950 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
951 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
952 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
954 # hide cloudinit password
955 if ($opt eq 'cipassword') {
956 $item->{value
} = '**********' if defined($item->{value
});
957 # the trailing space so that the pending string is different
958 $item->{pending
} = '********** ' if defined($item->{pending
});
963 foreach my $opt (keys %{$conf->{pending
}}) {
964 next if $opt eq 'delete';
965 next if ref($conf->{pending
}->{$opt}); # just to be sure
966 next if defined($conf->{$opt});
967 my $item = { key
=> $opt };
968 $item->{pending
} = $conf->{pending
}->{$opt};
970 # hide cloudinit password
971 if ($opt eq 'cipassword') {
972 $item->{pending
} = '**********' if defined($item->{pending
});
977 while (my ($opt, $force) = each %$pending_delete_hash) {
978 next if $conf->{pending
}->{$opt}; # just to be sure
979 next if $conf->{$opt};
980 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
987 # POST/PUT {vmid}/config implementation
989 # The original API used PUT (idempotent) an we assumed that all operations
990 # are fast. But it turned out that almost any configuration change can
991 # involve hot-plug actions, or disk alloc/free. Such actions can take long
992 # time to complete and have side effects (not idempotent).
994 # The new implementation uses POST and forks a worker process. We added
995 # a new option 'background_delay'. If specified we wait up to
996 # 'background_delay' second for the worker task to complete. It returns null
997 # if the task is finished within that time, else we return the UPID.
999 my $update_vm_api = sub {
1000 my ($param, $sync) = @_;
1002 my $rpcenv = PVE
::RPCEnvironment
::get
();
1004 my $authuser = $rpcenv->get_user();
1006 my $node = extract_param
($param, 'node');
1008 my $vmid = extract_param
($param, 'vmid');
1010 my $digest = extract_param
($param, 'digest');
1012 my $background_delay = extract_param
($param, 'background_delay');
1014 if (defined(my $cipassword = $param->{cipassword
})) {
1015 # Same logic as in cloud-init (but with the regex fixed...)
1016 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1017 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1020 my @paramarr = (); # used for log message
1021 foreach my $key (sort keys %$param) {
1022 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1023 push @paramarr, "-$key", $value;
1026 my $skiplock = extract_param
($param, 'skiplock');
1027 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1028 if $skiplock && $authuser ne 'root@pam';
1030 my $delete_str = extract_param
($param, 'delete');
1032 my $revert_str = extract_param
($param, 'revert');
1034 my $force = extract_param
($param, 'force');
1036 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1037 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1038 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1041 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1043 my $storecfg = PVE
::Storage
::config
();
1045 my $defaults = PVE
::QemuServer
::load_defaults
();
1047 &$resolve_cdrom_alias($param);
1049 # now try to verify all parameters
1052 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1053 if (!PVE
::QemuServer
::option_exists
($opt)) {
1054 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1057 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1058 "-revert $opt' at the same time" })
1059 if defined($param->{$opt});
1061 $revert->{$opt} = 1;
1065 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1066 $opt = 'ide2' if $opt eq 'cdrom';
1068 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1069 "-delete $opt' at the same time" })
1070 if defined($param->{$opt});
1072 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1073 "-revert $opt' at the same time" })
1076 if (!PVE
::QemuServer
::option_exists
($opt)) {
1077 raise_param_exc
({ delete => "unknown option '$opt'" });
1083 my $repl_conf = PVE
::ReplicationConfig-
>new();
1084 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1085 my $check_replication = sub {
1087 return if !$is_replicated;
1088 my $volid = $drive->{file
};
1089 return if !$volid || !($drive->{replicate
}//1);
1090 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1091 my ($storeid, $format);
1092 if ($volid =~ $NEW_DISK_RE) {
1094 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1096 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1097 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1099 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1100 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1101 return if $scfg->{shared
};
1102 die "cannot add non-replicatable volume to a replicated VM\n";
1105 foreach my $opt (keys %$param) {
1106 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1107 # cleanup drive path
1108 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1109 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1110 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1111 $check_replication->($drive);
1112 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1113 } elsif ($opt =~ m/^net(\d+)$/) {
1115 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1116 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1117 } elsif ($opt eq 'vmgenid') {
1118 if ($param->{$opt} eq '1') {
1119 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1121 } elsif ($opt eq 'hookscript') {
1122 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1123 raise_param_exc
({ $opt => $@ }) if $@;
1127 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1129 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1131 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1133 my $updatefn = sub {
1135 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1137 die "checksum missmatch (file change by other user?)\n"
1138 if $digest && $digest ne $conf->{digest
};
1140 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1142 foreach my $opt (keys %$revert) {
1143 if (defined($conf->{$opt})) {
1144 $param->{$opt} = $conf->{$opt};
1145 } elsif (defined($conf->{pending
}->{$opt})) {
1150 if ($param->{memory
} || defined($param->{balloon
})) {
1151 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1152 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1154 die "balloon value too large (must be smaller than assigned memory)\n"
1155 if $balloon && $balloon > $maxmem;
1158 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1162 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1164 # write updates to pending section
1166 my $modified = {}; # record what $option we modify
1168 foreach my $opt (@delete) {
1169 $modified->{$opt} = 1;
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1171 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1172 warn "cannot delete '$opt' - not set in current configuration!\n";
1173 $modified->{$opt} = 0;
1177 if ($opt =~ m/^unused/) {
1178 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1179 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1182 delete $conf->{$opt};
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1186 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1187 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1188 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1189 if defined($conf->{pending
}->{$opt});
1190 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1191 PVE
::QemuConfig-
>write_config($vmid, $conf);
1193 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1194 PVE
::QemuConfig-
>write_config($vmid, $conf);
1198 foreach my $opt (keys %$param) { # add/change
1199 $modified->{$opt} = 1;
1200 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1201 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1203 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1205 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1206 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1207 # FIXME: cloudinit: CDROM or Disk?
1208 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1211 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1213 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1214 if defined($conf->{pending
}->{$opt});
1216 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1218 $conf->{pending
}->{$opt} = $param->{$opt};
1220 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1221 PVE
::QemuConfig-
>write_config($vmid, $conf);
1224 # remove pending changes when nothing changed
1225 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1226 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1227 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1229 return if !scalar(keys %{$conf->{pending
}});
1231 my $running = PVE
::QemuServer
::check_running
($vmid);
1233 # apply pending changes
1235 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1239 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1240 raise_param_exc
($errors) if scalar(keys %$errors);
1242 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1252 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1254 if ($background_delay) {
1256 # Note: It would be better to do that in the Event based HTTPServer
1257 # to avoid blocking call to sleep.
1259 my $end_time = time() + $background_delay;
1261 my $task = PVE
::Tools
::upid_decode
($upid);
1264 while (time() < $end_time) {
1265 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1267 sleep(1); # this gets interrupted when child process ends
1271 my $status = PVE
::Tools
::upid_read_status
($upid);
1272 return undef if $status eq 'OK';
1281 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1284 my $vm_config_perm_list = [
1289 'VM.Config.Network',
1291 'VM.Config.Options',
1294 __PACKAGE__-
>register_method({
1295 name
=> 'update_vm_async',
1296 path
=> '{vmid}/config',
1300 description
=> "Set virtual machine options (asynchrounous API).",
1302 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1305 additionalProperties
=> 0,
1306 properties
=> PVE
::QemuServer
::json_config_properties
(
1308 node
=> get_standard_option
('pve-node'),
1309 vmid
=> get_standard_option
('pve-vmid'),
1310 skiplock
=> get_standard_option
('skiplock'),
1312 type
=> 'string', format
=> 'pve-configid-list',
1313 description
=> "A list of settings you want to delete.",
1317 type
=> 'string', format
=> 'pve-configid-list',
1318 description
=> "Revert a pending change.",
1323 description
=> $opt_force_description,
1325 requires
=> 'delete',
1329 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1333 background_delay
=> {
1335 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1346 code
=> $update_vm_api,
1349 __PACKAGE__-
>register_method({
1350 name
=> 'update_vm',
1351 path
=> '{vmid}/config',
1355 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1357 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1360 additionalProperties
=> 0,
1361 properties
=> PVE
::QemuServer
::json_config_properties
(
1363 node
=> get_standard_option
('pve-node'),
1364 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1365 skiplock
=> get_standard_option
('skiplock'),
1367 type
=> 'string', format
=> 'pve-configid-list',
1368 description
=> "A list of settings you want to delete.",
1372 type
=> 'string', format
=> 'pve-configid-list',
1373 description
=> "Revert a pending change.",
1378 description
=> $opt_force_description,
1380 requires
=> 'delete',
1384 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1390 returns
=> { type
=> 'null' },
1393 &$update_vm_api($param, 1);
1399 __PACKAGE__-
>register_method({
1400 name
=> 'destroy_vm',
1405 description
=> "Destroy the vm (also delete all used/owned volumes).",
1407 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1410 additionalProperties
=> 0,
1412 node
=> get_standard_option
('pve-node'),
1413 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1414 skiplock
=> get_standard_option
('skiplock'),
1423 my $rpcenv = PVE
::RPCEnvironment
::get
();
1425 my $authuser = $rpcenv->get_user();
1427 my $vmid = $param->{vmid
};
1429 my $skiplock = $param->{skiplock
};
1430 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1431 if $skiplock && $authuser ne 'root@pam';
1434 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1436 my $storecfg = PVE
::Storage
::config
();
1438 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1440 die "unable to remove VM $vmid - used in HA resources\n"
1441 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1443 # do not allow destroy if there are replication jobs
1444 my $repl_conf = PVE
::ReplicationConfig-
>new();
1445 $repl_conf->check_for_existing_jobs($vmid);
1447 # early tests (repeat after locking)
1448 die "VM $vmid is running - destroy failed\n"
1449 if PVE
::QemuServer
::check_running
($vmid);
1454 syslog
('info', "destroy VM $vmid: $upid\n");
1456 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1458 PVE
::AccessControl
::remove_vm_access
($vmid);
1460 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1463 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1466 __PACKAGE__-
>register_method({
1468 path
=> '{vmid}/unlink',
1472 description
=> "Unlink/delete disk images.",
1474 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1477 additionalProperties
=> 0,
1479 node
=> get_standard_option
('pve-node'),
1480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1482 type
=> 'string', format
=> 'pve-configid-list',
1483 description
=> "A list of disk IDs you want to delete.",
1487 description
=> $opt_force_description,
1492 returns
=> { type
=> 'null'},
1496 $param->{delete} = extract_param
($param, 'idlist');
1498 __PACKAGE__-
>update_vm($param);
1505 __PACKAGE__-
>register_method({
1507 path
=> '{vmid}/vncproxy',
1511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1513 description
=> "Creates a TCP VNC proxy connections.",
1515 additionalProperties
=> 0,
1517 node
=> get_standard_option
('pve-node'),
1518 vmid
=> get_standard_option
('pve-vmid'),
1522 description
=> "starts websockify instead of vncproxy",
1527 additionalProperties
=> 0,
1529 user
=> { type
=> 'string' },
1530 ticket
=> { type
=> 'string' },
1531 cert
=> { type
=> 'string' },
1532 port
=> { type
=> 'integer' },
1533 upid
=> { type
=> 'string' },
1539 my $rpcenv = PVE
::RPCEnvironment
::get
();
1541 my $authuser = $rpcenv->get_user();
1543 my $vmid = $param->{vmid
};
1544 my $node = $param->{node
};
1545 my $websocket = $param->{websocket
};
1547 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1548 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1550 my $authpath = "/vms/$vmid";
1552 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1554 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1560 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1561 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1562 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1563 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1564 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1566 $family = PVE
::Tools
::get_host_address_family
($node);
1569 my $port = PVE
::Tools
::next_vnc_port
($family);
1576 syslog
('info', "starting vnc proxy $upid\n");
1582 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1584 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1585 '-timeout', $timeout, '-authpath', $authpath,
1586 '-perm', 'Sys.Console'];
1588 if ($param->{websocket
}) {
1589 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1590 push @$cmd, '-notls', '-listen', 'localhost';
1593 push @$cmd, '-c', @$remcmd, @$termcmd;
1595 PVE
::Tools
::run_command
($cmd);
1599 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1601 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1603 my $sock = IO
::Socket
::IP-
>new(
1608 GetAddrInfoFlags
=> 0,
1609 ) or die "failed to create socket: $!\n";
1610 # Inside the worker we shouldn't have any previous alarms
1611 # running anyway...:
1613 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1615 accept(my $cli, $sock) or die "connection failed: $!\n";
1618 if (PVE
::Tools
::run_command
($cmd,
1619 output
=> '>&'.fileno($cli),
1620 input
=> '<&'.fileno($cli),
1623 die "Failed to run vncproxy.\n";
1630 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1632 PVE
::Tools
::wait_for_vnc_port
($port);
1643 __PACKAGE__-
>register_method({
1644 name
=> 'termproxy',
1645 path
=> '{vmid}/termproxy',
1649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1651 description
=> "Creates a TCP proxy connections.",
1653 additionalProperties
=> 0,
1655 node
=> get_standard_option
('pve-node'),
1656 vmid
=> get_standard_option
('pve-vmid'),
1660 enum
=> [qw(serial0 serial1 serial2 serial3)],
1661 description
=> "opens a serial terminal (defaults to display)",
1666 additionalProperties
=> 0,
1668 user
=> { type
=> 'string' },
1669 ticket
=> { type
=> 'string' },
1670 port
=> { type
=> 'integer' },
1671 upid
=> { type
=> 'string' },
1677 my $rpcenv = PVE
::RPCEnvironment
::get
();
1679 my $authuser = $rpcenv->get_user();
1681 my $vmid = $param->{vmid
};
1682 my $node = $param->{node
};
1683 my $serial = $param->{serial
};
1685 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1687 if (!defined($serial)) {
1688 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1689 $serial = $conf->{vga
};
1693 my $authpath = "/vms/$vmid";
1695 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1700 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1701 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1702 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1703 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1704 push @$remcmd, '--';
1706 $family = PVE
::Tools
::get_host_address_family
($node);
1709 my $port = PVE
::Tools
::next_vnc_port
($family);
1711 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1712 push @$termcmd, '-iface', $serial if $serial;
1717 syslog
('info', "starting qemu termproxy $upid\n");
1719 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1720 '--perm', 'VM.Console', '--'];
1721 push @$cmd, @$remcmd, @$termcmd;
1723 PVE
::Tools
::run_command
($cmd);
1726 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1728 PVE
::Tools
::wait_for_vnc_port
($port);
1738 __PACKAGE__-
>register_method({
1739 name
=> 'vncwebsocket',
1740 path
=> '{vmid}/vncwebsocket',
1743 description
=> "You also need to pass a valid ticket (vncticket).",
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1746 description
=> "Opens a weksocket for VNC traffic.",
1748 additionalProperties
=> 0,
1750 node
=> get_standard_option
('pve-node'),
1751 vmid
=> get_standard_option
('pve-vmid'),
1753 description
=> "Ticket from previous call to vncproxy.",
1758 description
=> "Port number returned by previous vncproxy call.",
1768 port
=> { type
=> 'string' },
1774 my $rpcenv = PVE
::RPCEnvironment
::get
();
1776 my $authuser = $rpcenv->get_user();
1778 my $vmid = $param->{vmid
};
1779 my $node = $param->{node
};
1781 my $authpath = "/vms/$vmid";
1783 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1785 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1787 # Note: VNC ports are acessible from outside, so we do not gain any
1788 # security if we verify that $param->{port} belongs to VM $vmid. This
1789 # check is done by verifying the VNC ticket (inside VNC protocol).
1791 my $port = $param->{port
};
1793 return { port
=> $port };
1796 __PACKAGE__-
>register_method({
1797 name
=> 'spiceproxy',
1798 path
=> '{vmid}/spiceproxy',
1803 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1805 description
=> "Returns a SPICE configuration to connect to the VM.",
1807 additionalProperties
=> 0,
1809 node
=> get_standard_option
('pve-node'),
1810 vmid
=> get_standard_option
('pve-vmid'),
1811 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1814 returns
=> get_standard_option
('remote-viewer-config'),
1818 my $rpcenv = PVE
::RPCEnvironment
::get
();
1820 my $authuser = $rpcenv->get_user();
1822 my $vmid = $param->{vmid
};
1823 my $node = $param->{node
};
1824 my $proxy = $param->{proxy
};
1826 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1827 my $title = "VM $vmid";
1828 $title .= " - ". $conf->{name
} if $conf->{name
};
1830 my $port = PVE
::QemuServer
::spice_port
($vmid);
1832 my ($ticket, undef, $remote_viewer_config) =
1833 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1835 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1836 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1838 return $remote_viewer_config;
1841 __PACKAGE__-
>register_method({
1843 path
=> '{vmid}/status',
1846 description
=> "Directory index",
1851 additionalProperties
=> 0,
1853 node
=> get_standard_option
('pve-node'),
1854 vmid
=> get_standard_option
('pve-vmid'),
1862 subdir
=> { type
=> 'string' },
1865 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1871 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1874 { subdir
=> 'current' },
1875 { subdir
=> 'start' },
1876 { subdir
=> 'stop' },
1882 __PACKAGE__-
>register_method({
1883 name
=> 'vm_status',
1884 path
=> '{vmid}/status/current',
1887 protected
=> 1, # qemu pid files are only readable by root
1888 description
=> "Get virtual machine status.",
1890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1893 additionalProperties
=> 0,
1895 node
=> get_standard_option
('pve-node'),
1896 vmid
=> get_standard_option
('pve-vmid'),
1902 %$PVE::QemuServer
::vmstatus_return_properties
,
1904 description
=> "HA manager service status.",
1908 description
=> "Qemu VGA configuration supports spice.",
1913 description
=> "Qemu GuestAgent enabled in config.",
1923 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1925 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1926 my $status = $vmstatus->{$param->{vmid
}};
1928 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1930 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1931 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1936 __PACKAGE__-
>register_method({
1938 path
=> '{vmid}/status/start',
1942 description
=> "Start virtual machine.",
1944 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1947 additionalProperties
=> 0,
1949 node
=> get_standard_option
('pve-node'),
1950 vmid
=> get_standard_option
('pve-vmid',
1951 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1952 skiplock
=> get_standard_option
('skiplock'),
1953 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1954 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1957 enum
=> ['secure', 'insecure'],
1958 description
=> "Migration traffic is encrypted using an SSH " .
1959 "tunnel by default. On secure, completely private networks " .
1960 "this can be disabled to increase performance.",
1963 migration_network
=> {
1964 type
=> 'string', format
=> 'CIDR',
1965 description
=> "CIDR of the (sub) network that is used for migration.",
1968 machine
=> get_standard_option
('pve-qm-machine'),
1970 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1982 my $rpcenv = PVE
::RPCEnvironment
::get
();
1984 my $authuser = $rpcenv->get_user();
1986 my $node = extract_param
($param, 'node');
1988 my $vmid = extract_param
($param, 'vmid');
1990 my $machine = extract_param
($param, 'machine');
1992 my $stateuri = extract_param
($param, 'stateuri');
1993 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1994 if $stateuri && $authuser ne 'root@pam';
1996 my $skiplock = extract_param
($param, 'skiplock');
1997 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1998 if $skiplock && $authuser ne 'root@pam';
2000 my $migratedfrom = extract_param
($param, 'migratedfrom');
2001 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2002 if $migratedfrom && $authuser ne 'root@pam';
2004 my $migration_type = extract_param
($param, 'migration_type');
2005 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2006 if $migration_type && $authuser ne 'root@pam';
2008 my $migration_network = extract_param
($param, 'migration_network');
2009 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2010 if $migration_network && $authuser ne 'root@pam';
2012 my $targetstorage = extract_param
($param, 'targetstorage');
2013 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2014 if $targetstorage && $authuser ne 'root@pam';
2016 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2017 if $targetstorage && !$migratedfrom;
2019 # read spice ticket from STDIN
2021 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2022 if (defined(my $line = <STDIN
>)) {
2024 $spice_ticket = $line;
2028 PVE
::Cluster
::check_cfs_quorum
();
2030 my $storecfg = PVE
::Storage
::config
();
2032 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2033 $rpcenv->{type
} ne 'ha') {
2038 my $service = "vm:$vmid";
2040 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2042 print "Requesting HA start for VM $vmid\n";
2044 PVE
::Tools
::run_command
($cmd);
2049 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2056 syslog
('info', "start VM $vmid: $upid\n");
2058 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2059 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2064 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2068 __PACKAGE__-
>register_method({
2070 path
=> '{vmid}/status/stop',
2074 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2075 "is akin to pulling the power plug of a running computer and may damage the VM data",
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid',
2084 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2085 skiplock
=> get_standard_option
('skiplock'),
2086 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2088 description
=> "Wait maximal timeout seconds.",
2094 description
=> "Do not deactivate storage volumes.",
2107 my $rpcenv = PVE
::RPCEnvironment
::get
();
2109 my $authuser = $rpcenv->get_user();
2111 my $node = extract_param
($param, 'node');
2113 my $vmid = extract_param
($param, 'vmid');
2115 my $skiplock = extract_param
($param, 'skiplock');
2116 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2117 if $skiplock && $authuser ne 'root@pam';
2119 my $keepActive = extract_param
($param, 'keepActive');
2120 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2121 if $keepActive && $authuser ne 'root@pam';
2123 my $migratedfrom = extract_param
($param, 'migratedfrom');
2124 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2125 if $migratedfrom && $authuser ne 'root@pam';
2128 my $storecfg = PVE
::Storage
::config
();
2130 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2135 my $service = "vm:$vmid";
2137 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2139 print "Requesting HA stop for VM $vmid\n";
2141 PVE
::Tools
::run_command
($cmd);
2146 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2152 syslog
('info', "stop VM $vmid: $upid\n");
2154 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2155 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2160 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2164 __PACKAGE__-
>register_method({
2166 path
=> '{vmid}/status/reset',
2170 description
=> "Reset virtual machine.",
2172 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2175 additionalProperties
=> 0,
2177 node
=> get_standard_option
('pve-node'),
2178 vmid
=> get_standard_option
('pve-vmid',
2179 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2180 skiplock
=> get_standard_option
('skiplock'),
2189 my $rpcenv = PVE
::RPCEnvironment
::get
();
2191 my $authuser = $rpcenv->get_user();
2193 my $node = extract_param
($param, 'node');
2195 my $vmid = extract_param
($param, 'vmid');
2197 my $skiplock = extract_param
($param, 'skiplock');
2198 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2199 if $skiplock && $authuser ne 'root@pam';
2201 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2206 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2211 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2214 __PACKAGE__-
>register_method({
2215 name
=> 'vm_shutdown',
2216 path
=> '{vmid}/status/shutdown',
2220 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2221 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2223 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2226 additionalProperties
=> 0,
2228 node
=> get_standard_option
('pve-node'),
2229 vmid
=> get_standard_option
('pve-vmid',
2230 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2231 skiplock
=> get_standard_option
('skiplock'),
2233 description
=> "Wait maximal timeout seconds.",
2239 description
=> "Make sure the VM stops.",
2245 description
=> "Do not deactivate storage volumes.",
2258 my $rpcenv = PVE
::RPCEnvironment
::get
();
2260 my $authuser = $rpcenv->get_user();
2262 my $node = extract_param
($param, 'node');
2264 my $vmid = extract_param
($param, 'vmid');
2266 my $skiplock = extract_param
($param, 'skiplock');
2267 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2268 if $skiplock && $authuser ne 'root@pam';
2270 my $keepActive = extract_param
($param, 'keepActive');
2271 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2272 if $keepActive && $authuser ne 'root@pam';
2274 my $storecfg = PVE
::Storage
::config
();
2278 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2279 # otherwise, we will infer a shutdown command, but run into the timeout,
2280 # then when the vm is resumed, it will instantly shutdown
2282 # checking the qmp status here to get feedback to the gui/cli/api
2283 # and the status query should not take too long
2286 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2290 if (!$err && $qmpstatus->{status
} eq "paused") {
2291 if ($param->{forceStop
}) {
2292 warn "VM is paused - stop instead of shutdown\n";
2295 die "VM is paused - cannot shutdown\n";
2299 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2300 ($rpcenv->{type
} ne 'ha')) {
2305 my $service = "vm:$vmid";
2307 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2309 print "Requesting HA stop for VM $vmid\n";
2311 PVE
::Tools
::run_command
($cmd);
2316 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2323 syslog
('info', "shutdown VM $vmid: $upid\n");
2325 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2326 $shutdown, $param->{forceStop
}, $keepActive);
2331 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2335 __PACKAGE__-
>register_method({
2336 name
=> 'vm_suspend',
2337 path
=> '{vmid}/status/suspend',
2341 description
=> "Suspend virtual machine.",
2343 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2346 additionalProperties
=> 0,
2348 node
=> get_standard_option
('pve-node'),
2349 vmid
=> get_standard_option
('pve-vmid',
2350 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2351 skiplock
=> get_standard_option
('skiplock'),
2356 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2358 statestorage
=> get_standard_option
('pve-storage-id', {
2359 description
=> "The storage for the VM state",
2360 requires
=> 'todisk',
2362 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2372 my $rpcenv = PVE
::RPCEnvironment
::get
();
2374 my $authuser = $rpcenv->get_user();
2376 my $node = extract_param
($param, 'node');
2378 my $vmid = extract_param
($param, 'vmid');
2380 my $todisk = extract_param
($param, 'todisk') // 0;
2382 my $statestorage = extract_param
($param, 'statestorage');
2384 my $skiplock = extract_param
($param, 'skiplock');
2385 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2386 if $skiplock && $authuser ne 'root@pam';
2388 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2390 die "Cannot suspend HA managed VM to disk\n"
2391 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2396 syslog
('info', "suspend VM $vmid: $upid\n");
2398 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2403 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2406 __PACKAGE__-
>register_method({
2407 name
=> 'vm_resume',
2408 path
=> '{vmid}/status/resume',
2412 description
=> "Resume virtual machine.",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid',
2421 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2422 skiplock
=> get_standard_option
('skiplock'),
2423 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2433 my $rpcenv = PVE
::RPCEnvironment
::get
();
2435 my $authuser = $rpcenv->get_user();
2437 my $node = extract_param
($param, 'node');
2439 my $vmid = extract_param
($param, 'vmid');
2441 my $skiplock = extract_param
($param, 'skiplock');
2442 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2443 if $skiplock && $authuser ne 'root@pam';
2445 my $nocheck = extract_param
($param, 'nocheck');
2447 my $to_disk_suspended;
2449 PVE
::QemuConfig-
>lock_config($vmid, sub {
2450 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2451 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2455 die "VM $vmid not running\n"
2456 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2461 syslog
('info', "resume VM $vmid: $upid\n");
2463 if (!$to_disk_suspended) {
2464 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2466 my $storecfg = PVE
::Storage
::config
();
2467 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2473 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2476 __PACKAGE__-
>register_method({
2477 name
=> 'vm_sendkey',
2478 path
=> '{vmid}/sendkey',
2482 description
=> "Send key event to virtual machine.",
2484 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2487 additionalProperties
=> 0,
2489 node
=> get_standard_option
('pve-node'),
2490 vmid
=> get_standard_option
('pve-vmid',
2491 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2492 skiplock
=> get_standard_option
('skiplock'),
2494 description
=> "The key (qemu monitor encoding).",
2499 returns
=> { type
=> 'null'},
2503 my $rpcenv = PVE
::RPCEnvironment
::get
();
2505 my $authuser = $rpcenv->get_user();
2507 my $node = extract_param
($param, 'node');
2509 my $vmid = extract_param
($param, 'vmid');
2511 my $skiplock = extract_param
($param, 'skiplock');
2512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2513 if $skiplock && $authuser ne 'root@pam';
2515 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2520 __PACKAGE__-
>register_method({
2521 name
=> 'vm_feature',
2522 path
=> '{vmid}/feature',
2526 description
=> "Check if feature for virtual machine is available.",
2528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2531 additionalProperties
=> 0,
2533 node
=> get_standard_option
('pve-node'),
2534 vmid
=> get_standard_option
('pve-vmid'),
2536 description
=> "Feature to check.",
2538 enum
=> [ 'snapshot', 'clone', 'copy' ],
2540 snapname
=> get_standard_option
('pve-snapshot-name', {
2548 hasFeature
=> { type
=> 'boolean' },
2551 items
=> { type
=> 'string' },
2558 my $node = extract_param
($param, 'node');
2560 my $vmid = extract_param
($param, 'vmid');
2562 my $snapname = extract_param
($param, 'snapname');
2564 my $feature = extract_param
($param, 'feature');
2566 my $running = PVE
::QemuServer
::check_running
($vmid);
2568 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2571 my $snap = $conf->{snapshots
}->{$snapname};
2572 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2575 my $storecfg = PVE
::Storage
::config
();
2577 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2578 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2581 hasFeature
=> $hasFeature,
2582 nodes
=> [ keys %$nodelist ],
2586 __PACKAGE__-
>register_method({
2588 path
=> '{vmid}/clone',
2592 description
=> "Create a copy of virtual machine/template.",
2594 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2595 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2596 "'Datastore.AllocateSpace' on any used storage.",
2599 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2601 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2602 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2607 additionalProperties
=> 0,
2609 node
=> get_standard_option
('pve-node'),
2610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2611 newid
=> get_standard_option
('pve-vmid', {
2612 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2613 description
=> 'VMID for the clone.' }),
2616 type
=> 'string', format
=> 'dns-name',
2617 description
=> "Set a name for the new VM.",
2622 description
=> "Description for the new VM.",
2626 type
=> 'string', format
=> 'pve-poolid',
2627 description
=> "Add the new VM to the specified pool.",
2629 snapname
=> get_standard_option
('pve-snapshot-name', {
2632 storage
=> get_standard_option
('pve-storage-id', {
2633 description
=> "Target storage for full clone.",
2637 description
=> "Target format for file storage. Only valid for full clone.",
2640 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2645 description
=> "Create a full copy of all disks. This is always done when " .
2646 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2648 target
=> get_standard_option
('pve-node', {
2649 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2660 my $rpcenv = PVE
::RPCEnvironment
::get
();
2662 my $authuser = $rpcenv->get_user();
2664 my $node = extract_param
($param, 'node');
2666 my $vmid = extract_param
($param, 'vmid');
2668 my $newid = extract_param
($param, 'newid');
2670 my $pool = extract_param
($param, 'pool');
2672 if (defined($pool)) {
2673 $rpcenv->check_pool_exist($pool);
2676 my $snapname = extract_param
($param, 'snapname');
2678 my $storage = extract_param
($param, 'storage');
2680 my $format = extract_param
($param, 'format');
2682 my $target = extract_param
($param, 'target');
2684 my $localnode = PVE
::INotify
::nodename
();
2686 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2688 PVE
::Cluster
::check_node_exists
($target) if $target;
2690 my $storecfg = PVE
::Storage
::config
();
2693 # check if storage is enabled on local node
2694 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2696 # check if storage is available on target node
2697 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2698 # clone only works if target storage is shared
2699 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2700 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2704 PVE
::Cluster
::check_cfs_quorum
();
2706 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2708 # exclusive lock if VM is running - else shared lock is enough;
2709 my $shared_lock = $running ?
0 : 1;
2713 # do all tests after lock
2714 # we also try to do all tests before we fork the worker
2716 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2718 PVE
::QemuConfig-
>check_lock($conf);
2720 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2722 die "unexpected state change\n" if $verify_running != $running;
2724 die "snapshot '$snapname' does not exist\n"
2725 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2727 my $full = extract_param
($param, 'full');
2728 if (!defined($full)) {
2729 $full = !PVE
::QemuConfig-
>is_template($conf);
2732 die "parameter 'storage' not allowed for linked clones\n"
2733 if defined($storage) && !$full;
2735 die "parameter 'format' not allowed for linked clones\n"
2736 if defined($format) && !$full;
2738 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2740 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2742 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2744 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2746 die "unable to create VM $newid: config file already exists\n"
2749 my $newconf = { lock => 'clone' };
2754 foreach my $opt (keys %$oldconf) {
2755 my $value = $oldconf->{$opt};
2757 # do not copy snapshot related info
2758 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2759 $opt eq 'vmstate' || $opt eq 'snapstate';
2761 # no need to copy unused images, because VMID(owner) changes anyways
2762 next if $opt =~ m/^unused\d+$/;
2764 # always change MAC! address
2765 if ($opt =~ m/^net(\d+)$/) {
2766 my $net = PVE
::QemuServer
::parse_net
($value);
2767 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2768 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2769 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2770 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2771 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2772 die "unable to parse drive options for '$opt'\n" if !$drive;
2773 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2774 $newconf->{$opt} = $value; # simply copy configuration
2776 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2777 die "Full clone feature is not supported for drive '$opt'\n"
2778 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2779 $fullclone->{$opt} = 1;
2781 # not full means clone instead of copy
2782 die "Linked clone feature is not supported for drive '$opt'\n"
2783 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2785 $drives->{$opt} = $drive;
2786 push @$vollist, $drive->{file
};
2789 # copy everything else
2790 $newconf->{$opt} = $value;
2794 # auto generate a new uuid
2795 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2796 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2797 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2799 # auto generate a new vmgenid if the option was set
2800 if ($newconf->{vmgenid
}) {
2801 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2804 delete $newconf->{template
};
2806 if ($param->{name
}) {
2807 $newconf->{name
} = $param->{name
};
2809 if ($oldconf->{name
}) {
2810 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2812 $newconf->{name
} = "Copy-of-VM-$vmid";
2816 if ($param->{description
}) {
2817 $newconf->{description
} = $param->{description
};
2820 # create empty/temp config - this fails if VM already exists on other node
2821 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2826 my $newvollist = [];
2833 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2835 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2837 my $total_jobs = scalar(keys %{$drives});
2840 foreach my $opt (keys %$drives) {
2841 my $drive = $drives->{$opt};
2842 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2844 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2845 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2846 $jobs, $skipcomplete, $oldconf->{agent
});
2848 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2850 PVE
::QemuConfig-
>write_config($newid, $newconf);
2854 delete $newconf->{lock};
2856 # do not write pending changes
2857 if (my @changes = keys %{$newconf->{pending
}}) {
2858 my $pending = join(',', @changes);
2859 warn "found pending changes for '$pending', discarding for clone\n";
2860 delete $newconf->{pending
};
2863 PVE
::QemuConfig-
>write_config($newid, $newconf);
2866 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2867 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2868 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2870 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2871 die "Failed to move config to node '$target' - rename failed: $!\n"
2872 if !rename($conffile, $newconffile);
2875 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2880 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2882 sleep 1; # some storage like rbd need to wait before release volume - really?
2884 foreach my $volid (@$newvollist) {
2885 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2888 die "clone failed: $err";
2894 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2896 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2899 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2900 # Aquire exclusive lock lock for $newid
2901 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2906 __PACKAGE__-
>register_method({
2907 name
=> 'move_vm_disk',
2908 path
=> '{vmid}/move_disk',
2912 description
=> "Move volume to different storage.",
2914 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2916 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2917 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2921 additionalProperties
=> 0,
2923 node
=> get_standard_option
('pve-node'),
2924 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2927 description
=> "The disk you want to move.",
2928 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2930 storage
=> get_standard_option
('pve-storage-id', {
2931 description
=> "Target storage.",
2932 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2936 description
=> "Target Format.",
2937 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2942 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2948 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2956 description
=> "the task ID.",
2961 my $rpcenv = PVE
::RPCEnvironment
::get
();
2963 my $authuser = $rpcenv->get_user();
2965 my $node = extract_param
($param, 'node');
2967 my $vmid = extract_param
($param, 'vmid');
2969 my $digest = extract_param
($param, 'digest');
2971 my $disk = extract_param
($param, 'disk');
2973 my $storeid = extract_param
($param, 'storage');
2975 my $format = extract_param
($param, 'format');
2977 my $storecfg = PVE
::Storage
::config
();
2979 my $updatefn = sub {
2981 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2983 PVE
::QemuConfig-
>check_lock($conf);
2985 die "checksum missmatch (file change by other user?)\n"
2986 if $digest && $digest ne $conf->{digest
};
2988 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2990 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2992 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2994 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2997 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2998 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3002 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3003 (!$format || !$oldfmt || $oldfmt eq $format);
3005 # this only checks snapshots because $disk is passed!
3006 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3007 die "you can't move a disk with snapshots and delete the source\n"
3008 if $snapshotted && $param->{delete};
3010 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3012 my $running = PVE
::QemuServer
::check_running
($vmid);
3014 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3018 my $newvollist = [];
3024 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3026 warn "moving disk with snapshots, snapshots will not be moved!\n"
3029 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3030 $vmid, $storeid, $format, 1, $newvollist);
3032 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3034 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3036 # convert moved disk to base if part of template
3037 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3038 if PVE
::QemuConfig-
>is_template($conf);
3040 PVE
::QemuConfig-
>write_config($vmid, $conf);
3042 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3043 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3047 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3048 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3055 foreach my $volid (@$newvollist) {
3056 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3059 die "storage migration failed: $err";
3062 if ($param->{delete}) {
3064 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3065 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3071 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3074 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3077 __PACKAGE__-
>register_method({
3078 name
=> 'migrate_vm',
3079 path
=> '{vmid}/migrate',
3083 description
=> "Migrate virtual machine. Creates a new migration task.",
3085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3088 additionalProperties
=> 0,
3090 node
=> get_standard_option
('pve-node'),
3091 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3092 target
=> get_standard_option
('pve-node', {
3093 description
=> "Target node.",
3094 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3098 description
=> "Use online/live migration.",
3103 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3108 enum
=> ['secure', 'insecure'],
3109 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3112 migration_network
=> {
3113 type
=> 'string', format
=> 'CIDR',
3114 description
=> "CIDR of the (sub) network that is used for migration.",
3117 "with-local-disks" => {
3119 description
=> "Enable live storage migration for local disk",
3122 targetstorage
=> get_standard_option
('pve-storage-id', {
3123 description
=> "Default target storage.",
3125 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3131 description
=> "the task ID.",
3136 my $rpcenv = PVE
::RPCEnvironment
::get
();
3138 my $authuser = $rpcenv->get_user();
3140 my $target = extract_param
($param, 'target');
3142 my $localnode = PVE
::INotify
::nodename
();
3143 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3145 PVE
::Cluster
::check_cfs_quorum
();
3147 PVE
::Cluster
::check_node_exists
($target);
3149 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3151 my $vmid = extract_param
($param, 'vmid');
3153 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3154 if !$param->{online
} && $param->{targetstorage
};
3156 raise_param_exc
({ force
=> "Only root may use this option." })
3157 if $param->{force
} && $authuser ne 'root@pam';
3159 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3160 if $param->{migration_type
} && $authuser ne 'root@pam';
3162 # allow root only until better network permissions are available
3163 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3164 if $param->{migration_network
} && $authuser ne 'root@pam';
3167 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3169 # try to detect errors early
3171 PVE
::QemuConfig-
>check_lock($conf);
3173 if (PVE
::QemuServer
::check_running
($vmid)) {
3174 die "cant migrate running VM without --online\n"
3175 if !$param->{online
};
3178 my $storecfg = PVE
::Storage
::config
();
3180 if( $param->{targetstorage
}) {
3181 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3183 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3186 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3191 my $service = "vm:$vmid";
3193 my $cmd = ['ha-manager', 'migrate', $service, $target];
3195 print "Requesting HA migration for VM $vmid to node $target\n";
3197 PVE
::Tools
::run_command
($cmd);
3202 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3207 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3211 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3214 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3219 __PACKAGE__-
>register_method({
3221 path
=> '{vmid}/monitor',
3225 description
=> "Execute Qemu monitor commands.",
3227 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3228 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3231 additionalProperties
=> 0,
3233 node
=> get_standard_option
('pve-node'),
3234 vmid
=> get_standard_option
('pve-vmid'),
3237 description
=> "The monitor command.",
3241 returns
=> { type
=> 'string'},
3245 my $rpcenv = PVE
::RPCEnvironment
::get
();
3246 my $authuser = $rpcenv->get_user();
3249 my $command = shift;
3250 return $command =~ m/^\s*info(\s+|$)/
3251 || $command =~ m/^\s*help\s*$/;
3254 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3255 if !&$is_ro($param->{command
});
3257 my $vmid = $param->{vmid
};
3259 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3263 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3265 $res = "ERROR: $@" if $@;
3270 __PACKAGE__-
>register_method({
3271 name
=> 'resize_vm',
3272 path
=> '{vmid}/resize',
3276 description
=> "Extend volume size.",
3278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3281 additionalProperties
=> 0,
3283 node
=> get_standard_option
('pve-node'),
3284 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3285 skiplock
=> get_standard_option
('skiplock'),
3288 description
=> "The disk you want to resize.",
3289 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3293 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3294 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.",
3298 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3304 returns
=> { type
=> 'null'},
3308 my $rpcenv = PVE
::RPCEnvironment
::get
();
3310 my $authuser = $rpcenv->get_user();
3312 my $node = extract_param
($param, 'node');
3314 my $vmid = extract_param
($param, 'vmid');
3316 my $digest = extract_param
($param, 'digest');
3318 my $disk = extract_param
($param, 'disk');
3320 my $sizestr = extract_param
($param, 'size');
3322 my $skiplock = extract_param
($param, 'skiplock');
3323 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3324 if $skiplock && $authuser ne 'root@pam';
3326 my $storecfg = PVE
::Storage
::config
();
3328 my $updatefn = sub {
3330 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3332 die "checksum missmatch (file change by other user?)\n"
3333 if $digest && $digest ne $conf->{digest
};
3334 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3336 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3338 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3340 my (undef, undef, undef, undef, undef, undef, $format) =
3341 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3343 die "can't resize volume: $disk if snapshot exists\n"
3344 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3346 my $volid = $drive->{file
};
3348 die "disk '$disk' has no associated volume\n" if !$volid;
3350 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3352 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3354 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3356 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3357 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3359 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3360 my ($ext, $newsize, $unit) = ($1, $2, $4);
3363 $newsize = $newsize * 1024;
3364 } elsif ($unit eq 'M') {
3365 $newsize = $newsize * 1024 * 1024;
3366 } elsif ($unit eq 'G') {
3367 $newsize = $newsize * 1024 * 1024 * 1024;
3368 } elsif ($unit eq 'T') {
3369 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3372 $newsize += $size if $ext;
3373 $newsize = int($newsize);
3375 die "shrinking disks is not supported\n" if $newsize < $size;
3377 return if $size == $newsize;
3379 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3381 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3383 $drive->{size
} = $newsize;
3384 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3386 PVE
::QemuConfig-
>write_config($vmid, $conf);
3389 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3393 __PACKAGE__-
>register_method({
3394 name
=> 'snapshot_list',
3395 path
=> '{vmid}/snapshot',
3397 description
=> "List all snapshots.",
3399 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3402 protected
=> 1, # qemu pid files are only readable by root
3404 additionalProperties
=> 0,
3406 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3407 node
=> get_standard_option
('pve-node'),
3416 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3420 description
=> "Snapshot includes RAM.",
3425 description
=> "Snapshot description.",
3429 description
=> "Snapshot creation time",
3431 renderer
=> 'timestamp',
3435 description
=> "Parent snapshot identifier.",
3441 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3446 my $vmid = $param->{vmid
};
3448 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3449 my $snaphash = $conf->{snapshots
} || {};
3453 foreach my $name (keys %$snaphash) {
3454 my $d = $snaphash->{$name};
3457 snaptime
=> $d->{snaptime
} || 0,
3458 vmstate
=> $d->{vmstate
} ?
1 : 0,
3459 description
=> $d->{description
} || '',
3461 $item->{parent
} = $d->{parent
} if $d->{parent
};
3462 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3466 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3469 digest
=> $conf->{digest
},
3470 running
=> $running,
3471 description
=> "You are here!",
3473 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3475 push @$res, $current;
3480 __PACKAGE__-
>register_method({
3482 path
=> '{vmid}/snapshot',
3486 description
=> "Snapshot a VM.",
3488 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3491 additionalProperties
=> 0,
3493 node
=> get_standard_option
('pve-node'),
3494 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3495 snapname
=> get_standard_option
('pve-snapshot-name'),
3499 description
=> "Save the vmstate",
3504 description
=> "A textual description or comment.",
3510 description
=> "the task ID.",
3515 my $rpcenv = PVE
::RPCEnvironment
::get
();
3517 my $authuser = $rpcenv->get_user();
3519 my $node = extract_param
($param, 'node');
3521 my $vmid = extract_param
($param, 'vmid');
3523 my $snapname = extract_param
($param, 'snapname');
3525 die "unable to use snapshot name 'current' (reserved name)\n"
3526 if $snapname eq 'current';
3529 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3530 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3531 $param->{description
});
3534 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3537 __PACKAGE__-
>register_method({
3538 name
=> 'snapshot_cmd_idx',
3539 path
=> '{vmid}/snapshot/{snapname}',
3546 additionalProperties
=> 0,
3548 vmid
=> get_standard_option
('pve-vmid'),
3549 node
=> get_standard_option
('pve-node'),
3550 snapname
=> get_standard_option
('pve-snapshot-name'),
3559 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3566 push @$res, { cmd
=> 'rollback' };
3567 push @$res, { cmd
=> 'config' };
3572 __PACKAGE__-
>register_method({
3573 name
=> 'update_snapshot_config',
3574 path
=> '{vmid}/snapshot/{snapname}/config',
3578 description
=> "Update snapshot metadata.",
3580 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3583 additionalProperties
=> 0,
3585 node
=> get_standard_option
('pve-node'),
3586 vmid
=> get_standard_option
('pve-vmid'),
3587 snapname
=> get_standard_option
('pve-snapshot-name'),
3591 description
=> "A textual description or comment.",
3595 returns
=> { type
=> 'null' },
3599 my $rpcenv = PVE
::RPCEnvironment
::get
();
3601 my $authuser = $rpcenv->get_user();
3603 my $vmid = extract_param
($param, 'vmid');
3605 my $snapname = extract_param
($param, 'snapname');
3607 return undef if !defined($param->{description
});
3609 my $updatefn = sub {
3611 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3613 PVE
::QemuConfig-
>check_lock($conf);
3615 my $snap = $conf->{snapshots
}->{$snapname};
3617 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3619 $snap->{description
} = $param->{description
} if defined($param->{description
});
3621 PVE
::QemuConfig-
>write_config($vmid, $conf);
3624 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3629 __PACKAGE__-
>register_method({
3630 name
=> 'get_snapshot_config',
3631 path
=> '{vmid}/snapshot/{snapname}/config',
3634 description
=> "Get snapshot configuration",
3636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3639 additionalProperties
=> 0,
3641 node
=> get_standard_option
('pve-node'),
3642 vmid
=> get_standard_option
('pve-vmid'),
3643 snapname
=> get_standard_option
('pve-snapshot-name'),
3646 returns
=> { type
=> "object" },
3650 my $rpcenv = PVE
::RPCEnvironment
::get
();
3652 my $authuser = $rpcenv->get_user();
3654 my $vmid = extract_param
($param, 'vmid');
3656 my $snapname = extract_param
($param, 'snapname');
3658 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3660 my $snap = $conf->{snapshots
}->{$snapname};
3662 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3667 __PACKAGE__-
>register_method({
3669 path
=> '{vmid}/snapshot/{snapname}/rollback',
3673 description
=> "Rollback VM state to specified snapshot.",
3675 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3678 additionalProperties
=> 0,
3680 node
=> get_standard_option
('pve-node'),
3681 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3682 snapname
=> get_standard_option
('pve-snapshot-name'),
3687 description
=> "the task ID.",
3692 my $rpcenv = PVE
::RPCEnvironment
::get
();
3694 my $authuser = $rpcenv->get_user();
3696 my $node = extract_param
($param, 'node');
3698 my $vmid = extract_param
($param, 'vmid');
3700 my $snapname = extract_param
($param, 'snapname');
3703 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3704 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3708 # hold migration lock, this makes sure that nobody create replication snapshots
3709 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3712 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3715 __PACKAGE__-
>register_method({
3716 name
=> 'delsnapshot',
3717 path
=> '{vmid}/snapshot/{snapname}',
3721 description
=> "Delete a VM snapshot.",
3723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3726 additionalProperties
=> 0,
3728 node
=> get_standard_option
('pve-node'),
3729 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3730 snapname
=> get_standard_option
('pve-snapshot-name'),
3734 description
=> "For removal from config file, even if removing disk snapshots fails.",
3740 description
=> "the task ID.",
3745 my $rpcenv = PVE
::RPCEnvironment
::get
();
3747 my $authuser = $rpcenv->get_user();
3749 my $node = extract_param
($param, 'node');
3751 my $vmid = extract_param
($param, 'vmid');
3753 my $snapname = extract_param
($param, 'snapname');
3756 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3757 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3760 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3763 __PACKAGE__-
>register_method({
3765 path
=> '{vmid}/template',
3769 description
=> "Create a Template.",
3771 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3772 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3775 additionalProperties
=> 0,
3777 node
=> get_standard_option
('pve-node'),
3778 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3782 description
=> "If you want to convert only 1 disk to base image.",
3783 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3788 returns
=> { type
=> 'null'},
3792 my $rpcenv = PVE
::RPCEnvironment
::get
();
3794 my $authuser = $rpcenv->get_user();
3796 my $node = extract_param
($param, 'node');
3798 my $vmid = extract_param
($param, 'vmid');
3800 my $disk = extract_param
($param, 'disk');
3802 my $updatefn = sub {
3804 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3806 PVE
::QemuConfig-
>check_lock($conf);
3808 die "unable to create template, because VM contains snapshots\n"
3809 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3811 die "you can't convert a template to a template\n"
3812 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3814 die "you can't convert a VM to template if VM is running\n"
3815 if PVE
::QemuServer
::check_running
($vmid);
3818 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3821 $conf->{template
} = 1;
3822 PVE
::QemuConfig-
>write_config($vmid, $conf);
3824 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3827 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);