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, $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 # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
160 my $cloudinit_iso_size = 5; # 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);
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 = {
303 my $check_vm_modify_config_perm = sub {
304 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
306 return 1 if $authuser eq 'root@pam';
308 foreach my $opt (@$key_list) {
309 # disk checks need to be done somewhere else
310 next if PVE
::QemuServer
::is_valid_drivename
($opt);
311 next if $opt eq 'cdrom';
312 next if $opt =~ m/^unused\d+$/;
314 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
315 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
316 } elsif ($memoryoptions->{$opt}) {
317 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
318 } elsif ($hwtypeoptions->{$opt}) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
320 } elsif ($generaloptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
322 # special case for startup since it changes host behaviour
323 if ($opt eq 'startup') {
324 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
326 } elsif ($vmpoweroptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
328 } elsif ($diskoptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
330 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 # catches usb\d+, hostpci\d+, args, lock, etc.
334 # new options will be checked here
335 die "only root can set '$opt' config\n";
342 __PACKAGE__-
>register_method({
346 description
=> "Virtual machine index (per node).",
348 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
352 protected
=> 1, # qemu pid files are only readable by root
354 additionalProperties
=> 0,
356 node
=> get_standard_option
('pve-node'),
360 description
=> "Determine the full status of active VMs.",
370 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
375 my $rpcenv = PVE
::RPCEnvironment
::get
();
376 my $authuser = $rpcenv->get_user();
378 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
381 foreach my $vmid (keys %$vmstatus) {
382 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
384 my $data = $vmstatus->{$vmid};
385 $data->{vmid
} = int($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).",
456 my $rpcenv = PVE
::RPCEnvironment
::get
();
458 my $authuser = $rpcenv->get_user();
460 my $node = extract_param
($param, 'node');
462 my $vmid = extract_param
($param, 'vmid');
464 my $archive = extract_param
($param, 'archive');
466 my $storage = extract_param
($param, 'storage');
468 my $force = extract_param
($param, 'force');
470 my $unique = extract_param
($param, 'unique');
472 my $pool = extract_param
($param, 'pool');
474 my $bwlimit = extract_param
($param, 'bwlimit');
476 my $filename = PVE
::QemuConfig-
>config_file($vmid);
478 my $storecfg = PVE
::Storage
::config
();
480 if (defined(my $ssh_keys = $param->{sshkeys
})) {
481 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
482 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
485 PVE
::Cluster
::check_cfs_quorum
();
487 if (defined($pool)) {
488 $rpcenv->check_pool_exist($pool);
491 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
492 if defined($storage);
494 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
496 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
498 } elsif ($archive && $force && (-f
$filename) &&
499 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
500 # OK: user has VM.Backup permissions, and want to restore an existing VM
506 &$resolve_cdrom_alias($param);
508 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
510 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
512 foreach my $opt (keys %$param) {
513 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
514 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
515 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
517 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
518 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
522 PVE
::QemuServer
::add_random_macs
($param);
524 my $keystr = join(' ', keys %$param);
525 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
527 if ($archive eq '-') {
528 die "pipe requires cli environment\n"
529 if $rpcenv->{type
} ne 'cli';
531 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
532 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
536 my $restorefn = sub {
537 my $vmlist = PVE
::Cluster
::get_vmlist
();
538 if ($vmlist->{ids
}->{$vmid}) {
539 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
540 if ($current_node eq $node) {
541 my $conf = PVE
::QemuConfig-
>load_config($vmid);
543 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
545 die "unable to restore vm $vmid - config file already exists\n"
548 die "unable to restore vm $vmid - vm is running\n"
549 if PVE
::QemuServer
::check_running
($vmid);
551 die "unable to restore vm $vmid - vm is a template\n"
552 if PVE
::QemuConfig-
>is_template($conf);
555 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
560 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
564 bwlimit
=> $bwlimit, });
566 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
569 # ensure no old replication state are exists
570 PVE
::ReplicationState
::delete_guest_states
($vmid);
572 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
578 PVE
::Cluster
::check_vmid_unused
($vmid);
580 # ensure no old replication state are exists
581 PVE
::ReplicationState
::delete_guest_states
($vmid);
591 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
593 if (!$conf->{bootdisk
}) {
594 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
595 $conf->{bootdisk
} = $firstdisk if $firstdisk;
598 # auto generate uuid if user did not specify smbios1 option
599 if (!$conf->{smbios1
}) {
600 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
603 PVE
::QemuConfig-
>write_config($vmid, $conf);
609 foreach my $volid (@$vollist) {
610 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
613 die "create failed - $err";
616 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
619 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
622 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
625 __PACKAGE__-
>register_method({
630 description
=> "Directory index",
635 additionalProperties
=> 0,
637 node
=> get_standard_option
('pve-node'),
638 vmid
=> get_standard_option
('pve-vmid'),
646 subdir
=> { type
=> 'string' },
649 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
655 { subdir
=> 'config' },
656 { subdir
=> 'pending' },
657 { subdir
=> 'status' },
658 { subdir
=> 'unlink' },
659 { subdir
=> 'vncproxy' },
660 { subdir
=> 'termproxy' },
661 { subdir
=> 'migrate' },
662 { subdir
=> 'resize' },
663 { subdir
=> 'move' },
665 { subdir
=> 'rrddata' },
666 { subdir
=> 'monitor' },
667 { subdir
=> 'agent' },
668 { subdir
=> 'snapshot' },
669 { subdir
=> 'spiceproxy' },
670 { subdir
=> 'sendkey' },
671 { subdir
=> 'firewall' },
677 __PACKAGE__-
>register_method ({
678 subclass
=> "PVE::API2::Firewall::VM",
679 path
=> '{vmid}/firewall',
682 __PACKAGE__-
>register_method ({
683 subclass
=> "PVE::API2::Qemu::Agent",
684 path
=> '{vmid}/agent',
687 __PACKAGE__-
>register_method({
689 path
=> '{vmid}/rrd',
691 protected
=> 1, # fixme: can we avoid that?
693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
695 description
=> "Read VM RRD statistics (returns PNG)",
697 additionalProperties
=> 0,
699 node
=> get_standard_option
('pve-node'),
700 vmid
=> get_standard_option
('pve-vmid'),
702 description
=> "Specify the time frame you are interested in.",
704 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
707 description
=> "The list of datasources you want to display.",
708 type
=> 'string', format
=> 'pve-configid-list',
711 description
=> "The RRD consolidation function",
713 enum
=> [ 'AVERAGE', 'MAX' ],
721 filename
=> { type
=> 'string' },
727 return PVE
::Cluster
::create_rrd_graph
(
728 "pve2-vm/$param->{vmid}", $param->{timeframe
},
729 $param->{ds
}, $param->{cf
});
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/rrddata',
737 protected
=> 1, # fixme: can we avoid that?
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
741 description
=> "Read VM RRD statistics",
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid'),
748 description
=> "Specify the time frame you are interested in.",
750 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
753 description
=> "The RRD consolidation function",
755 enum
=> [ 'AVERAGE', 'MAX' ],
770 return PVE
::Cluster
::create_rrd_data
(
771 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
775 __PACKAGE__-
>register_method({
777 path
=> '{vmid}/config',
780 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
790 description
=> "Get current values (instead of pending values).",
802 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
809 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
811 delete $conf->{snapshots
};
813 if (!$param->{current
}) {
814 foreach my $opt (keys %{$conf->{pending
}}) {
815 next if $opt eq 'delete';
816 my $value = $conf->{pending
}->{$opt};
817 next if ref($value); # just to be sure
818 $conf->{$opt} = $value;
820 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
821 foreach my $opt (keys %$pending_delete_hash) {
822 delete $conf->{$opt} if $conf->{$opt};
826 delete $conf->{pending
};
828 # hide cloudinit password
829 if ($conf->{cipassword
}) {
830 $conf->{cipassword
} = '**********';
836 __PACKAGE__-
>register_method({
837 name
=> 'vm_pending',
838 path
=> '{vmid}/pending',
841 description
=> "Get virtual machine configuration, including pending changes.",
843 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
846 additionalProperties
=> 0,
848 node
=> get_standard_option
('pve-node'),
849 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
858 description
=> "Configuration option name.",
862 description
=> "Current value.",
867 description
=> "Pending value.",
872 description
=> "Indicates a pending delete request if present and not 0. " .
873 "The value 2 indicates a force-delete request.",
885 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
887 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
891 foreach my $opt (keys %$conf) {
892 next if ref($conf->{$opt});
893 my $item = { key
=> $opt };
894 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
895 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
896 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
898 # hide cloudinit password
899 if ($opt eq 'cipassword') {
900 $item->{value
} = '**********' if defined($item->{value
});
901 # the trailing space so that the pending string is different
902 $item->{pending
} = '********** ' if defined($item->{pending
});
907 foreach my $opt (keys %{$conf->{pending
}}) {
908 next if $opt eq 'delete';
909 next if ref($conf->{pending
}->{$opt}); # just to be sure
910 next if defined($conf->{$opt});
911 my $item = { key
=> $opt };
912 $item->{pending
} = $conf->{pending
}->{$opt};
914 # hide cloudinit password
915 if ($opt eq 'cipassword') {
916 $item->{pending
} = '**********' if defined($item->{pending
});
921 while (my ($opt, $force) = each %$pending_delete_hash) {
922 next if $conf->{pending
}->{$opt}; # just to be sure
923 next if $conf->{$opt};
924 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
931 # POST/PUT {vmid}/config implementation
933 # The original API used PUT (idempotent) an we assumed that all operations
934 # are fast. But it turned out that almost any configuration change can
935 # involve hot-plug actions, or disk alloc/free. Such actions can take long
936 # time to complete and have side effects (not idempotent).
938 # The new implementation uses POST and forks a worker process. We added
939 # a new option 'background_delay'. If specified we wait up to
940 # 'background_delay' second for the worker task to complete. It returns null
941 # if the task is finished within that time, else we return the UPID.
943 my $update_vm_api = sub {
944 my ($param, $sync) = @_;
946 my $rpcenv = PVE
::RPCEnvironment
::get
();
948 my $authuser = $rpcenv->get_user();
950 my $node = extract_param
($param, 'node');
952 my $vmid = extract_param
($param, 'vmid');
954 my $digest = extract_param
($param, 'digest');
956 my $background_delay = extract_param
($param, 'background_delay');
958 if (defined(my $cipassword = $param->{cipassword
})) {
959 # Same logic as in cloud-init (but with the regex fixed...)
960 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
961 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
964 my @paramarr = (); # used for log message
965 foreach my $key (sort keys %$param) {
966 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
967 push @paramarr, "-$key", $value;
970 my $skiplock = extract_param
($param, 'skiplock');
971 raise_param_exc
({ skiplock
=> "Only root may use this option." })
972 if $skiplock && $authuser ne 'root@pam';
974 my $delete_str = extract_param
($param, 'delete');
976 my $revert_str = extract_param
($param, 'revert');
978 my $force = extract_param
($param, 'force');
980 if (defined(my $ssh_keys = $param->{sshkeys
})) {
981 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
982 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
985 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
987 my $storecfg = PVE
::Storage
::config
();
989 my $defaults = PVE
::QemuServer
::load_defaults
();
991 &$resolve_cdrom_alias($param);
993 # now try to verify all parameters
996 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
997 if (!PVE
::QemuServer
::option_exists
($opt)) {
998 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1001 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1002 "-revert $opt' at the same time" })
1003 if defined($param->{$opt});
1005 $revert->{$opt} = 1;
1009 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1010 $opt = 'ide2' if $opt eq 'cdrom';
1012 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1013 "-delete $opt' at the same time" })
1014 if defined($param->{$opt});
1016 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1017 "-revert $opt' at the same time" })
1020 if (!PVE
::QemuServer
::option_exists
($opt)) {
1021 raise_param_exc
({ delete => "unknown option '$opt'" });
1027 my $repl_conf = PVE
::ReplicationConfig-
>new();
1028 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1029 my $check_replication = sub {
1031 return if !$is_replicated;
1032 my $volid = $drive->{file
};
1033 return if !$volid || !($drive->{replicate
}//1);
1034 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1035 my ($storeid, $format);
1036 if ($volid =~ $NEW_DISK_RE) {
1038 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1040 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1041 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1043 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1044 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1045 return if $scfg->{shared
};
1046 die "cannot add non-replicatable volume to a replicated VM\n";
1049 foreach my $opt (keys %$param) {
1050 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1051 # cleanup drive path
1052 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1053 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1054 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1055 $check_replication->($drive);
1056 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1057 } elsif ($opt =~ m/^net(\d+)$/) {
1059 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1060 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1064 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1066 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1068 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1070 my $updatefn = sub {
1072 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1074 die "checksum missmatch (file change by other user?)\n"
1075 if $digest && $digest ne $conf->{digest
};
1077 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1079 foreach my $opt (keys %$revert) {
1080 if (defined($conf->{$opt})) {
1081 $param->{$opt} = $conf->{$opt};
1082 } elsif (defined($conf->{pending
}->{$opt})) {
1087 if ($param->{memory
} || defined($param->{balloon
})) {
1088 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1089 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1091 die "balloon value too large (must be smaller than assigned memory)\n"
1092 if $balloon && $balloon > $maxmem;
1095 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1099 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1101 # write updates to pending section
1103 my $modified = {}; # record what $option we modify
1105 foreach my $opt (@delete) {
1106 $modified->{$opt} = 1;
1107 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1108 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1109 warn "cannot delete '$opt' - not set in current configuration!\n";
1110 $modified->{$opt} = 0;
1114 if ($opt =~ m/^unused/) {
1115 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1116 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1117 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1118 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1119 delete $conf->{$opt};
1120 PVE
::QemuConfig-
>write_config($vmid, $conf);
1122 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1123 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1124 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1125 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1126 if defined($conf->{pending
}->{$opt});
1127 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1128 PVE
::QemuConfig-
>write_config($vmid, $conf);
1130 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1131 PVE
::QemuConfig-
>write_config($vmid, $conf);
1135 foreach my $opt (keys %$param) { # add/change
1136 $modified->{$opt} = 1;
1137 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1138 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1140 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1141 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1142 # FIXME: cloudinit: CDROM or Disk?
1143 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1144 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1146 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1148 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1149 if defined($conf->{pending
}->{$opt});
1151 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1153 $conf->{pending
}->{$opt} = $param->{$opt};
1155 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1156 PVE
::QemuConfig-
>write_config($vmid, $conf);
1159 # remove pending changes when nothing changed
1160 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1161 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1162 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1164 return if !scalar(keys %{$conf->{pending
}});
1166 my $running = PVE
::QemuServer
::check_running
($vmid);
1168 # apply pending changes
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1174 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1175 raise_param_exc
($errors) if scalar(keys %$errors);
1177 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1187 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1189 if ($background_delay) {
1191 # Note: It would be better to do that in the Event based HTTPServer
1192 # to avoid blocking call to sleep.
1194 my $end_time = time() + $background_delay;
1196 my $task = PVE
::Tools
::upid_decode
($upid);
1199 while (time() < $end_time) {
1200 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1202 sleep(1); # this gets interrupted when child process ends
1206 my $status = PVE
::Tools
::upid_read_status
($upid);
1207 return undef if $status eq 'OK';
1216 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1219 my $vm_config_perm_list = [
1224 'VM.Config.Network',
1226 'VM.Config.Options',
1229 __PACKAGE__-
>register_method({
1230 name
=> 'update_vm_async',
1231 path
=> '{vmid}/config',
1235 description
=> "Set virtual machine options (asynchrounous API).",
1237 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1240 additionalProperties
=> 0,
1241 properties
=> PVE
::QemuServer
::json_config_properties
(
1243 node
=> get_standard_option
('pve-node'),
1244 vmid
=> get_standard_option
('pve-vmid'),
1245 skiplock
=> get_standard_option
('skiplock'),
1247 type
=> 'string', format
=> 'pve-configid-list',
1248 description
=> "A list of settings you want to delete.",
1252 type
=> 'string', format
=> 'pve-configid-list',
1253 description
=> "Revert a pending change.",
1258 description
=> $opt_force_description,
1260 requires
=> 'delete',
1264 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1268 background_delay
=> {
1270 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1281 code
=> $update_vm_api,
1284 __PACKAGE__-
>register_method({
1285 name
=> 'update_vm',
1286 path
=> '{vmid}/config',
1290 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1292 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1295 additionalProperties
=> 0,
1296 properties
=> PVE
::QemuServer
::json_config_properties
(
1298 node
=> get_standard_option
('pve-node'),
1299 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1300 skiplock
=> get_standard_option
('skiplock'),
1302 type
=> 'string', format
=> 'pve-configid-list',
1303 description
=> "A list of settings you want to delete.",
1307 type
=> 'string', format
=> 'pve-configid-list',
1308 description
=> "Revert a pending change.",
1313 description
=> $opt_force_description,
1315 requires
=> 'delete',
1319 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1325 returns
=> { type
=> 'null' },
1328 &$update_vm_api($param, 1);
1334 __PACKAGE__-
>register_method({
1335 name
=> 'destroy_vm',
1340 description
=> "Destroy the vm (also delete all used/owned volumes).",
1342 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1345 additionalProperties
=> 0,
1347 node
=> get_standard_option
('pve-node'),
1348 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1349 skiplock
=> get_standard_option
('skiplock'),
1358 my $rpcenv = PVE
::RPCEnvironment
::get
();
1360 my $authuser = $rpcenv->get_user();
1362 my $vmid = $param->{vmid
};
1364 my $skiplock = $param->{skiplock
};
1365 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1366 if $skiplock && $authuser ne 'root@pam';
1369 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1371 my $storecfg = PVE
::Storage
::config
();
1373 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1375 die "unable to remove VM $vmid - used in HA resources\n"
1376 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1378 # do not allow destroy if there are replication jobs
1379 my $repl_conf = PVE
::ReplicationConfig-
>new();
1380 $repl_conf->check_for_existing_jobs($vmid);
1382 # early tests (repeat after locking)
1383 die "VM $vmid is running - destroy failed\n"
1384 if PVE
::QemuServer
::check_running
($vmid);
1389 syslog
('info', "destroy VM $vmid: $upid\n");
1391 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1393 PVE
::AccessControl
::remove_vm_access
($vmid);
1395 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1398 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1401 __PACKAGE__-
>register_method({
1403 path
=> '{vmid}/unlink',
1407 description
=> "Unlink/delete disk images.",
1409 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1412 additionalProperties
=> 0,
1414 node
=> get_standard_option
('pve-node'),
1415 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1417 type
=> 'string', format
=> 'pve-configid-list',
1418 description
=> "A list of disk IDs you want to delete.",
1422 description
=> $opt_force_description,
1427 returns
=> { type
=> 'null'},
1431 $param->{delete} = extract_param
($param, 'idlist');
1433 __PACKAGE__-
>update_vm($param);
1440 __PACKAGE__-
>register_method({
1442 path
=> '{vmid}/vncproxy',
1446 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1448 description
=> "Creates a TCP VNC proxy connections.",
1450 additionalProperties
=> 0,
1452 node
=> get_standard_option
('pve-node'),
1453 vmid
=> get_standard_option
('pve-vmid'),
1457 description
=> "starts websockify instead of vncproxy",
1462 additionalProperties
=> 0,
1464 user
=> { type
=> 'string' },
1465 ticket
=> { type
=> 'string' },
1466 cert
=> { type
=> 'string' },
1467 port
=> { type
=> 'integer' },
1468 upid
=> { type
=> 'string' },
1474 my $rpcenv = PVE
::RPCEnvironment
::get
();
1476 my $authuser = $rpcenv->get_user();
1478 my $vmid = $param->{vmid
};
1479 my $node = $param->{node
};
1480 my $websocket = $param->{websocket
};
1482 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1484 my $authpath = "/vms/$vmid";
1486 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1488 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1491 my ($remip, $family);
1494 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1495 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1496 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1497 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1499 $family = PVE
::Tools
::get_host_address_family
($node);
1502 my $port = PVE
::Tools
::next_vnc_port
($family);
1509 syslog
('info', "starting vnc proxy $upid\n");
1513 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1516 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1518 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1519 '-timeout', $timeout, '-authpath', $authpath,
1520 '-perm', 'Sys.Console'];
1522 if ($param->{websocket
}) {
1523 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1524 push @$cmd, '-notls', '-listen', 'localhost';
1527 push @$cmd, '-c', @$remcmd, @$termcmd;
1529 PVE
::Tools
::run_command
($cmd);
1533 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1535 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1537 my $sock = IO
::Socket
::IP-
>new(
1542 GetAddrInfoFlags
=> 0,
1543 ) or die "failed to create socket: $!\n";
1544 # Inside the worker we shouldn't have any previous alarms
1545 # running anyway...:
1547 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1549 accept(my $cli, $sock) or die "connection failed: $!\n";
1552 if (PVE
::Tools
::run_command
($cmd,
1553 output
=> '>&'.fileno($cli),
1554 input
=> '<&'.fileno($cli),
1557 die "Failed to run vncproxy.\n";
1564 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1566 PVE
::Tools
::wait_for_vnc_port
($port);
1577 __PACKAGE__-
>register_method({
1578 name
=> 'termproxy',
1579 path
=> '{vmid}/termproxy',
1583 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1585 description
=> "Creates a TCP proxy connections.",
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1594 enum
=> [qw(serial0 serial1 serial2 serial3)],
1595 description
=> "opens a serial terminal (defaults to display)",
1600 additionalProperties
=> 0,
1602 user
=> { type
=> 'string' },
1603 ticket
=> { type
=> 'string' },
1604 port
=> { type
=> 'integer' },
1605 upid
=> { type
=> 'string' },
1611 my $rpcenv = PVE
::RPCEnvironment
::get
();
1613 my $authuser = $rpcenv->get_user();
1615 my $vmid = $param->{vmid
};
1616 my $node = $param->{node
};
1617 my $serial = $param->{serial
};
1619 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1621 if (!defined($serial)) {
1622 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1623 $serial = $conf->{vga
};
1627 my $authpath = "/vms/$vmid";
1629 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1631 my ($remip, $family);
1633 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1634 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1636 $family = PVE
::Tools
::get_host_address_family
($node);
1639 my $port = PVE
::Tools
::next_vnc_port
($family);
1641 my $remcmd = $remip ?
1642 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1644 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1645 push @$termcmd, '-iface', $serial if $serial;
1650 syslog
('info', "starting qemu termproxy $upid\n");
1652 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1653 '--perm', 'VM.Console', '--'];
1654 push @$cmd, @$remcmd, @$termcmd;
1656 PVE
::Tools
::run_command
($cmd);
1659 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1661 PVE
::Tools
::wait_for_vnc_port
($port);
1671 __PACKAGE__-
>register_method({
1672 name
=> 'vncwebsocket',
1673 path
=> '{vmid}/vncwebsocket',
1676 description
=> "You also need to pass a valid ticket (vncticket).",
1677 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1679 description
=> "Opens a weksocket for VNC traffic.",
1681 additionalProperties
=> 0,
1683 node
=> get_standard_option
('pve-node'),
1684 vmid
=> get_standard_option
('pve-vmid'),
1686 description
=> "Ticket from previous call to vncproxy.",
1691 description
=> "Port number returned by previous vncproxy call.",
1701 port
=> { type
=> 'string' },
1707 my $rpcenv = PVE
::RPCEnvironment
::get
();
1709 my $authuser = $rpcenv->get_user();
1711 my $vmid = $param->{vmid
};
1712 my $node = $param->{node
};
1714 my $authpath = "/vms/$vmid";
1716 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1718 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1720 # Note: VNC ports are acessible from outside, so we do not gain any
1721 # security if we verify that $param->{port} belongs to VM $vmid. This
1722 # check is done by verifying the VNC ticket (inside VNC protocol).
1724 my $port = $param->{port
};
1726 return { port
=> $port };
1729 __PACKAGE__-
>register_method({
1730 name
=> 'spiceproxy',
1731 path
=> '{vmid}/spiceproxy',
1736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1738 description
=> "Returns a SPICE configuration to connect to the VM.",
1740 additionalProperties
=> 0,
1742 node
=> get_standard_option
('pve-node'),
1743 vmid
=> get_standard_option
('pve-vmid'),
1744 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1747 returns
=> get_standard_option
('remote-viewer-config'),
1751 my $rpcenv = PVE
::RPCEnvironment
::get
();
1753 my $authuser = $rpcenv->get_user();
1755 my $vmid = $param->{vmid
};
1756 my $node = $param->{node
};
1757 my $proxy = $param->{proxy
};
1759 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1760 my $title = "VM $vmid";
1761 $title .= " - ". $conf->{name
} if $conf->{name
};
1763 my $port = PVE
::QemuServer
::spice_port
($vmid);
1765 my ($ticket, undef, $remote_viewer_config) =
1766 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1768 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1769 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1771 return $remote_viewer_config;
1774 __PACKAGE__-
>register_method({
1776 path
=> '{vmid}/status',
1779 description
=> "Directory index",
1784 additionalProperties
=> 0,
1786 node
=> get_standard_option
('pve-node'),
1787 vmid
=> get_standard_option
('pve-vmid'),
1795 subdir
=> { type
=> 'string' },
1798 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1804 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1807 { subdir
=> 'current' },
1808 { subdir
=> 'start' },
1809 { subdir
=> 'stop' },
1815 __PACKAGE__-
>register_method({
1816 name
=> 'vm_status',
1817 path
=> '{vmid}/status/current',
1820 protected
=> 1, # qemu pid files are only readable by root
1821 description
=> "Get virtual machine status.",
1823 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1826 additionalProperties
=> 0,
1828 node
=> get_standard_option
('pve-node'),
1829 vmid
=> get_standard_option
('pve-vmid'),
1832 returns
=> { type
=> 'object' },
1837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1839 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1840 my $status = $vmstatus->{$param->{vmid
}};
1842 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1844 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1846 $status->{agent
} = 1 if $conf->{agent
};
1851 __PACKAGE__-
>register_method({
1853 path
=> '{vmid}/status/start',
1857 description
=> "Start virtual machine.",
1859 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid',
1866 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1867 skiplock
=> get_standard_option
('skiplock'),
1868 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1869 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1872 enum
=> ['secure', 'insecure'],
1873 description
=> "Migration traffic is encrypted using an SSH " .
1874 "tunnel by default. On secure, completely private networks " .
1875 "this can be disabled to increase performance.",
1878 migration_network
=> {
1879 type
=> 'string', format
=> 'CIDR',
1880 description
=> "CIDR of the (sub) network that is used for migration.",
1883 machine
=> get_standard_option
('pve-qm-machine'),
1885 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1897 my $rpcenv = PVE
::RPCEnvironment
::get
();
1899 my $authuser = $rpcenv->get_user();
1901 my $node = extract_param
($param, 'node');
1903 my $vmid = extract_param
($param, 'vmid');
1905 my $machine = extract_param
($param, 'machine');
1907 my $stateuri = extract_param
($param, 'stateuri');
1908 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1909 if $stateuri && $authuser ne 'root@pam';
1911 my $skiplock = extract_param
($param, 'skiplock');
1912 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1913 if $skiplock && $authuser ne 'root@pam';
1915 my $migratedfrom = extract_param
($param, 'migratedfrom');
1916 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1917 if $migratedfrom && $authuser ne 'root@pam';
1919 my $migration_type = extract_param
($param, 'migration_type');
1920 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1921 if $migration_type && $authuser ne 'root@pam';
1923 my $migration_network = extract_param
($param, 'migration_network');
1924 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1925 if $migration_network && $authuser ne 'root@pam';
1927 my $targetstorage = extract_param
($param, 'targetstorage');
1928 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1929 if $targetstorage && $authuser ne 'root@pam';
1931 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1932 if $targetstorage && !$migratedfrom;
1934 # read spice ticket from STDIN
1936 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1937 if (defined(my $line = <STDIN
>)) {
1939 $spice_ticket = $line;
1943 PVE
::Cluster
::check_cfs_quorum
();
1945 my $storecfg = PVE
::Storage
::config
();
1947 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1948 $rpcenv->{type
} ne 'ha') {
1953 my $service = "vm:$vmid";
1955 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1957 print "Requesting HA start for VM $vmid\n";
1959 PVE
::Tools
::run_command
($cmd);
1964 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1971 syslog
('info', "start VM $vmid: $upid\n");
1973 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1974 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1979 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1983 __PACKAGE__-
>register_method({
1985 path
=> '{vmid}/status/stop',
1989 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1990 "is akin to pulling the power plug of a running computer and may damage the VM data",
1992 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1995 additionalProperties
=> 0,
1997 node
=> get_standard_option
('pve-node'),
1998 vmid
=> get_standard_option
('pve-vmid',
1999 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2000 skiplock
=> get_standard_option
('skiplock'),
2001 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2003 description
=> "Wait maximal timeout seconds.",
2009 description
=> "Do not deactivate storage volumes.",
2022 my $rpcenv = PVE
::RPCEnvironment
::get
();
2024 my $authuser = $rpcenv->get_user();
2026 my $node = extract_param
($param, 'node');
2028 my $vmid = extract_param
($param, 'vmid');
2030 my $skiplock = extract_param
($param, 'skiplock');
2031 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2032 if $skiplock && $authuser ne 'root@pam';
2034 my $keepActive = extract_param
($param, 'keepActive');
2035 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2036 if $keepActive && $authuser ne 'root@pam';
2038 my $migratedfrom = extract_param
($param, 'migratedfrom');
2039 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2040 if $migratedfrom && $authuser ne 'root@pam';
2043 my $storecfg = PVE
::Storage
::config
();
2045 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2050 my $service = "vm:$vmid";
2052 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2054 print "Requesting HA stop for VM $vmid\n";
2056 PVE
::Tools
::run_command
($cmd);
2061 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2067 syslog
('info', "stop VM $vmid: $upid\n");
2069 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2070 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2075 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2079 __PACKAGE__-
>register_method({
2081 path
=> '{vmid}/status/reset',
2085 description
=> "Reset virtual machine.",
2087 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2090 additionalProperties
=> 0,
2092 node
=> get_standard_option
('pve-node'),
2093 vmid
=> get_standard_option
('pve-vmid',
2094 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2095 skiplock
=> get_standard_option
('skiplock'),
2104 my $rpcenv = PVE
::RPCEnvironment
::get
();
2106 my $authuser = $rpcenv->get_user();
2108 my $node = extract_param
($param, 'node');
2110 my $vmid = extract_param
($param, 'vmid');
2112 my $skiplock = extract_param
($param, 'skiplock');
2113 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2114 if $skiplock && $authuser ne 'root@pam';
2116 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2121 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2126 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2129 __PACKAGE__-
>register_method({
2130 name
=> 'vm_shutdown',
2131 path
=> '{vmid}/status/shutdown',
2135 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2136 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2138 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2141 additionalProperties
=> 0,
2143 node
=> get_standard_option
('pve-node'),
2144 vmid
=> get_standard_option
('pve-vmid',
2145 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2146 skiplock
=> get_standard_option
('skiplock'),
2148 description
=> "Wait maximal timeout seconds.",
2154 description
=> "Make sure the VM stops.",
2160 description
=> "Do not deactivate storage volumes.",
2173 my $rpcenv = PVE
::RPCEnvironment
::get
();
2175 my $authuser = $rpcenv->get_user();
2177 my $node = extract_param
($param, 'node');
2179 my $vmid = extract_param
($param, 'vmid');
2181 my $skiplock = extract_param
($param, 'skiplock');
2182 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2183 if $skiplock && $authuser ne 'root@pam';
2185 my $keepActive = extract_param
($param, 'keepActive');
2186 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2187 if $keepActive && $authuser ne 'root@pam';
2189 my $storecfg = PVE
::Storage
::config
();
2193 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2194 # otherwise, we will infer a shutdown command, but run into the timeout,
2195 # then when the vm is resumed, it will instantly shutdown
2197 # checking the qmp status here to get feedback to the gui/cli/api
2198 # and the status query should not take too long
2201 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2205 if (!$err && $qmpstatus->{status
} eq "paused") {
2206 if ($param->{forceStop
}) {
2207 warn "VM is paused - stop instead of shutdown\n";
2210 die "VM is paused - cannot shutdown\n";
2214 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2215 ($rpcenv->{type
} ne 'ha')) {
2220 my $service = "vm:$vmid";
2222 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2224 print "Requesting HA stop for VM $vmid\n";
2226 PVE
::Tools
::run_command
($cmd);
2231 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2238 syslog
('info', "shutdown VM $vmid: $upid\n");
2240 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2241 $shutdown, $param->{forceStop
}, $keepActive);
2246 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2250 __PACKAGE__-
>register_method({
2251 name
=> 'vm_suspend',
2252 path
=> '{vmid}/status/suspend',
2256 description
=> "Suspend virtual machine.",
2258 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2261 additionalProperties
=> 0,
2263 node
=> get_standard_option
('pve-node'),
2264 vmid
=> get_standard_option
('pve-vmid',
2265 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2266 skiplock
=> get_standard_option
('skiplock'),
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 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2292 syslog
('info', "suspend VM $vmid: $upid\n");
2294 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2299 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2302 __PACKAGE__-
>register_method({
2303 name
=> 'vm_resume',
2304 path
=> '{vmid}/status/resume',
2308 description
=> "Resume virtual machine.",
2310 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid',
2317 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2318 skiplock
=> get_standard_option
('skiplock'),
2319 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2329 my $rpcenv = PVE
::RPCEnvironment
::get
();
2331 my $authuser = $rpcenv->get_user();
2333 my $node = extract_param
($param, 'node');
2335 my $vmid = extract_param
($param, 'vmid');
2337 my $skiplock = extract_param
($param, 'skiplock');
2338 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2339 if $skiplock && $authuser ne 'root@pam';
2341 my $nocheck = extract_param
($param, 'nocheck');
2343 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2348 syslog
('info', "resume VM $vmid: $upid\n");
2350 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2355 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2358 __PACKAGE__-
>register_method({
2359 name
=> 'vm_sendkey',
2360 path
=> '{vmid}/sendkey',
2364 description
=> "Send key event to virtual machine.",
2366 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2369 additionalProperties
=> 0,
2371 node
=> get_standard_option
('pve-node'),
2372 vmid
=> get_standard_option
('pve-vmid',
2373 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2374 skiplock
=> get_standard_option
('skiplock'),
2376 description
=> "The key (qemu monitor encoding).",
2381 returns
=> { type
=> 'null'},
2385 my $rpcenv = PVE
::RPCEnvironment
::get
();
2387 my $authuser = $rpcenv->get_user();
2389 my $node = extract_param
($param, 'node');
2391 my $vmid = extract_param
($param, 'vmid');
2393 my $skiplock = extract_param
($param, 'skiplock');
2394 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2395 if $skiplock && $authuser ne 'root@pam';
2397 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2402 __PACKAGE__-
>register_method({
2403 name
=> 'vm_feature',
2404 path
=> '{vmid}/feature',
2408 description
=> "Check if feature for virtual machine is available.",
2410 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2413 additionalProperties
=> 0,
2415 node
=> get_standard_option
('pve-node'),
2416 vmid
=> get_standard_option
('pve-vmid'),
2418 description
=> "Feature to check.",
2420 enum
=> [ 'snapshot', 'clone', 'copy' ],
2422 snapname
=> get_standard_option
('pve-snapshot-name', {
2430 hasFeature
=> { type
=> 'boolean' },
2433 items
=> { type
=> 'string' },
2440 my $node = extract_param
($param, 'node');
2442 my $vmid = extract_param
($param, 'vmid');
2444 my $snapname = extract_param
($param, 'snapname');
2446 my $feature = extract_param
($param, 'feature');
2448 my $running = PVE
::QemuServer
::check_running
($vmid);
2450 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2453 my $snap = $conf->{snapshots
}->{$snapname};
2454 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2457 my $storecfg = PVE
::Storage
::config
();
2459 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2460 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2463 hasFeature
=> $hasFeature,
2464 nodes
=> [ keys %$nodelist ],
2468 __PACKAGE__-
>register_method({
2470 path
=> '{vmid}/clone',
2474 description
=> "Create a copy of virtual machine/template.",
2476 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2477 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2478 "'Datastore.AllocateSpace' on any used storage.",
2481 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2483 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2484 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2489 additionalProperties
=> 0,
2491 node
=> get_standard_option
('pve-node'),
2492 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2493 newid
=> get_standard_option
('pve-vmid', {
2494 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2495 description
=> 'VMID for the clone.' }),
2498 type
=> 'string', format
=> 'dns-name',
2499 description
=> "Set a name for the new VM.",
2504 description
=> "Description for the new VM.",
2508 type
=> 'string', format
=> 'pve-poolid',
2509 description
=> "Add the new VM to the specified pool.",
2511 snapname
=> get_standard_option
('pve-snapshot-name', {
2514 storage
=> get_standard_option
('pve-storage-id', {
2515 description
=> "Target storage for full clone.",
2519 description
=> "Target format for file storage. Only valid for full clone.",
2522 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2527 description
=> "Create a full copy of all disks. This is always done when " .
2528 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2530 target
=> get_standard_option
('pve-node', {
2531 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2542 my $rpcenv = PVE
::RPCEnvironment
::get
();
2544 my $authuser = $rpcenv->get_user();
2546 my $node = extract_param
($param, 'node');
2548 my $vmid = extract_param
($param, 'vmid');
2550 my $newid = extract_param
($param, 'newid');
2552 my $pool = extract_param
($param, 'pool');
2554 if (defined($pool)) {
2555 $rpcenv->check_pool_exist($pool);
2558 my $snapname = extract_param
($param, 'snapname');
2560 my $storage = extract_param
($param, 'storage');
2562 my $format = extract_param
($param, 'format');
2564 my $target = extract_param
($param, 'target');
2566 my $localnode = PVE
::INotify
::nodename
();
2568 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2570 PVE
::Cluster
::check_node_exists
($target) if $target;
2572 my $storecfg = PVE
::Storage
::config
();
2575 # check if storage is enabled on local node
2576 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2578 # check if storage is available on target node
2579 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2580 # clone only works if target storage is shared
2581 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2582 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2586 PVE
::Cluster
::check_cfs_quorum
();
2588 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2590 # exclusive lock if VM is running - else shared lock is enough;
2591 my $shared_lock = $running ?
0 : 1;
2595 # do all tests after lock
2596 # we also try to do all tests before we fork the worker
2598 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2600 PVE
::QemuConfig-
>check_lock($conf);
2602 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2604 die "unexpected state change\n" if $verify_running != $running;
2606 die "snapshot '$snapname' does not exist\n"
2607 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2609 my $full = extract_param
($param, 'full');
2610 if (!defined($full)) {
2611 $full = !PVE
::QemuConfig-
>is_template($conf);
2614 die "parameter 'storage' not allowed for linked clones\n"
2615 if defined($storage) && !$full;
2617 die "parameter 'format' not allowed for linked clones\n"
2618 if defined($format) && !$full;
2620 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2622 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2624 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2626 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2628 die "unable to create VM $newid: config file already exists\n"
2631 my $newconf = { lock => 'clone' };
2636 foreach my $opt (keys %$oldconf) {
2637 my $value = $oldconf->{$opt};
2639 # do not copy snapshot related info
2640 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2641 $opt eq 'vmstate' || $opt eq 'snapstate';
2643 # no need to copy unused images, because VMID(owner) changes anyways
2644 next if $opt =~ m/^unused\d+$/;
2646 # always change MAC! address
2647 if ($opt =~ m/^net(\d+)$/) {
2648 my $net = PVE
::QemuServer
::parse_net
($value);
2649 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2650 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2651 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2652 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2653 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2654 die "unable to parse drive options for '$opt'\n" if !$drive;
2655 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2656 $newconf->{$opt} = $value; # simply copy configuration
2658 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2659 die "Full clone feature is not supported for drive '$opt'\n"
2660 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2661 $fullclone->{$opt} = 1;
2663 # not full means clone instead of copy
2664 die "Linked clone feature is not supported for drive '$opt'\n"
2665 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2667 $drives->{$opt} = $drive;
2668 push @$vollist, $drive->{file
};
2671 # copy everything else
2672 $newconf->{$opt} = $value;
2676 # auto generate a new uuid
2677 my ($uuid, $uuid_str);
2678 UUID
::generate
($uuid);
2679 UUID
::unparse
($uuid, $uuid_str);
2680 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2681 $smbios1->{uuid
} = $uuid_str;
2682 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2684 delete $newconf->{template
};
2686 if ($param->{name
}) {
2687 $newconf->{name
} = $param->{name
};
2689 if ($oldconf->{name
}) {
2690 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2692 $newconf->{name
} = "Copy-of-VM-$vmid";
2696 if ($param->{description
}) {
2697 $newconf->{description
} = $param->{description
};
2700 # create empty/temp config - this fails if VM already exists on other node
2701 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2706 my $newvollist = [];
2713 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2715 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2717 my $total_jobs = scalar(keys %{$drives});
2720 foreach my $opt (keys %$drives) {
2721 my $drive = $drives->{$opt};
2722 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2724 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2725 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2726 $jobs, $skipcomplete, $oldconf->{agent
});
2728 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2730 PVE
::QemuConfig-
>write_config($newid, $newconf);
2734 delete $newconf->{lock};
2736 # do not write pending changes
2737 if ($newconf->{pending
}) {
2738 warn "found pending changes, discarding for clone\n";
2739 delete $newconf->{pending
};
2742 PVE
::QemuConfig-
>write_config($newid, $newconf);
2745 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2746 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2747 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2749 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2750 die "Failed to move config to node '$target' - rename failed: $!\n"
2751 if !rename($conffile, $newconffile);
2754 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2759 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2761 sleep 1; # some storage like rbd need to wait before release volume - really?
2763 foreach my $volid (@$newvollist) {
2764 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2767 die "clone failed: $err";
2773 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2775 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2778 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2779 # Aquire exclusive lock lock for $newid
2780 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2785 __PACKAGE__-
>register_method({
2786 name
=> 'move_vm_disk',
2787 path
=> '{vmid}/move_disk',
2791 description
=> "Move volume to different storage.",
2793 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2795 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2796 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2800 additionalProperties
=> 0,
2802 node
=> get_standard_option
('pve-node'),
2803 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2806 description
=> "The disk you want to move.",
2807 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2809 storage
=> get_standard_option
('pve-storage-id', {
2810 description
=> "Target storage.",
2811 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2815 description
=> "Target Format.",
2816 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2821 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2827 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2835 description
=> "the task ID.",
2840 my $rpcenv = PVE
::RPCEnvironment
::get
();
2842 my $authuser = $rpcenv->get_user();
2844 my $node = extract_param
($param, 'node');
2846 my $vmid = extract_param
($param, 'vmid');
2848 my $digest = extract_param
($param, 'digest');
2850 my $disk = extract_param
($param, 'disk');
2852 my $storeid = extract_param
($param, 'storage');
2854 my $format = extract_param
($param, 'format');
2856 my $storecfg = PVE
::Storage
::config
();
2858 my $updatefn = sub {
2860 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2862 PVE
::QemuConfig-
>check_lock($conf);
2864 die "checksum missmatch (file change by other user?)\n"
2865 if $digest && $digest ne $conf->{digest
};
2867 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2869 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2871 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2873 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2876 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2877 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2881 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2882 (!$format || !$oldfmt || $oldfmt eq $format);
2884 # this only checks snapshots because $disk is passed!
2885 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2886 die "you can't move a disk with snapshots and delete the source\n"
2887 if $snapshotted && $param->{delete};
2889 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2891 my $running = PVE
::QemuServer
::check_running
($vmid);
2893 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2897 my $newvollist = [];
2903 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2905 warn "moving disk with snapshots, snapshots will not be moved!\n"
2908 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2909 $vmid, $storeid, $format, 1, $newvollist);
2911 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2913 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2915 # convert moved disk to base if part of template
2916 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2917 if PVE
::QemuConfig-
>is_template($conf);
2919 PVE
::QemuConfig-
>write_config($vmid, $conf);
2922 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2923 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2930 foreach my $volid (@$newvollist) {
2931 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2934 die "storage migration failed: $err";
2937 if ($param->{delete}) {
2939 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2940 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2946 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2949 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2952 __PACKAGE__-
>register_method({
2953 name
=> 'migrate_vm',
2954 path
=> '{vmid}/migrate',
2958 description
=> "Migrate virtual machine. Creates a new migration task.",
2960 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2963 additionalProperties
=> 0,
2965 node
=> get_standard_option
('pve-node'),
2966 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2967 target
=> get_standard_option
('pve-node', {
2968 description
=> "Target node.",
2969 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2973 description
=> "Use online/live migration.",
2978 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2983 enum
=> ['secure', 'insecure'],
2984 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2987 migration_network
=> {
2988 type
=> 'string', format
=> 'CIDR',
2989 description
=> "CIDR of the (sub) network that is used for migration.",
2992 "with-local-disks" => {
2994 description
=> "Enable live storage migration for local disk",
2997 targetstorage
=> get_standard_option
('pve-storage-id', {
2998 description
=> "Default target storage.",
3000 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3006 description
=> "the task ID.",
3011 my $rpcenv = PVE
::RPCEnvironment
::get
();
3013 my $authuser = $rpcenv->get_user();
3015 my $target = extract_param
($param, 'target');
3017 my $localnode = PVE
::INotify
::nodename
();
3018 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3020 PVE
::Cluster
::check_cfs_quorum
();
3022 PVE
::Cluster
::check_node_exists
($target);
3024 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3026 my $vmid = extract_param
($param, 'vmid');
3028 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3029 if !$param->{online
} && $param->{targetstorage
};
3031 raise_param_exc
({ force
=> "Only root may use this option." })
3032 if $param->{force
} && $authuser ne 'root@pam';
3034 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3035 if $param->{migration_type
} && $authuser ne 'root@pam';
3037 # allow root only until better network permissions are available
3038 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3039 if $param->{migration_network
} && $authuser ne 'root@pam';
3042 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3044 # try to detect errors early
3046 PVE
::QemuConfig-
>check_lock($conf);
3048 if (PVE
::QemuServer
::check_running
($vmid)) {
3049 die "cant migrate running VM without --online\n"
3050 if !$param->{online
};
3053 my $storecfg = PVE
::Storage
::config
();
3055 if( $param->{targetstorage
}) {
3056 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3058 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3061 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3066 my $service = "vm:$vmid";
3068 my $cmd = ['ha-manager', 'migrate', $service, $target];
3070 print "Requesting HA migration for VM $vmid to node $target\n";
3072 PVE
::Tools
::run_command
($cmd);
3077 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3082 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3086 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3089 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3094 __PACKAGE__-
>register_method({
3096 path
=> '{vmid}/monitor',
3100 description
=> "Execute Qemu monitor commands.",
3102 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3103 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3106 additionalProperties
=> 0,
3108 node
=> get_standard_option
('pve-node'),
3109 vmid
=> get_standard_option
('pve-vmid'),
3112 description
=> "The monitor command.",
3116 returns
=> { type
=> 'string'},
3120 my $rpcenv = PVE
::RPCEnvironment
::get
();
3121 my $authuser = $rpcenv->get_user();
3124 my $command = shift;
3125 return $command =~ m/^\s*info(\s+|$)/
3126 || $command =~ m/^\s*help\s*$/;
3129 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3130 if !&$is_ro($param->{command
});
3132 my $vmid = $param->{vmid
};
3134 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3138 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3140 $res = "ERROR: $@" if $@;
3145 __PACKAGE__-
>register_method({
3146 name
=> 'resize_vm',
3147 path
=> '{vmid}/resize',
3151 description
=> "Extend volume size.",
3153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3156 additionalProperties
=> 0,
3158 node
=> get_standard_option
('pve-node'),
3159 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3160 skiplock
=> get_standard_option
('skiplock'),
3163 description
=> "The disk you want to resize.",
3164 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3168 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3169 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.",
3173 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3179 returns
=> { type
=> 'null'},
3183 my $rpcenv = PVE
::RPCEnvironment
::get
();
3185 my $authuser = $rpcenv->get_user();
3187 my $node = extract_param
($param, 'node');
3189 my $vmid = extract_param
($param, 'vmid');
3191 my $digest = extract_param
($param, 'digest');
3193 my $disk = extract_param
($param, 'disk');
3195 my $sizestr = extract_param
($param, 'size');
3197 my $skiplock = extract_param
($param, 'skiplock');
3198 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3199 if $skiplock && $authuser ne 'root@pam';
3201 my $storecfg = PVE
::Storage
::config
();
3203 my $updatefn = sub {
3205 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3207 die "checksum missmatch (file change by other user?)\n"
3208 if $digest && $digest ne $conf->{digest
};
3209 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3211 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3213 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3215 my (undef, undef, undef, undef, undef, undef, $format) =
3216 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3218 die "can't resize volume: $disk if snapshot exists\n"
3219 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3221 my $volid = $drive->{file
};
3223 die "disk '$disk' has no associated volume\n" if !$volid;
3225 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3227 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3229 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3231 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3232 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3234 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3235 my ($ext, $newsize, $unit) = ($1, $2, $4);
3238 $newsize = $newsize * 1024;
3239 } elsif ($unit eq 'M') {
3240 $newsize = $newsize * 1024 * 1024;
3241 } elsif ($unit eq 'G') {
3242 $newsize = $newsize * 1024 * 1024 * 1024;
3243 } elsif ($unit eq 'T') {
3244 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3247 $newsize += $size if $ext;
3248 $newsize = int($newsize);
3250 die "shrinking disks is not supported\n" if $newsize < $size;
3252 return if $size == $newsize;
3254 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3256 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3258 $drive->{size
} = $newsize;
3259 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3261 PVE
::QemuConfig-
>write_config($vmid, $conf);
3264 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3268 __PACKAGE__-
>register_method({
3269 name
=> 'snapshot_list',
3270 path
=> '{vmid}/snapshot',
3272 description
=> "List all snapshots.",
3274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3277 protected
=> 1, # qemu pid files are only readable by root
3279 additionalProperties
=> 0,
3281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3282 node
=> get_standard_option
('pve-node'),
3291 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3296 my $vmid = $param->{vmid
};
3298 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3299 my $snaphash = $conf->{snapshots
} || {};
3303 foreach my $name (keys %$snaphash) {
3304 my $d = $snaphash->{$name};
3307 snaptime
=> $d->{snaptime
} || 0,
3308 vmstate
=> $d->{vmstate
} ?
1 : 0,
3309 description
=> $d->{description
} || '',
3311 $item->{parent
} = $d->{parent
} if $d->{parent
};
3312 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3316 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3317 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3318 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3320 push @$res, $current;
3325 __PACKAGE__-
>register_method({
3327 path
=> '{vmid}/snapshot',
3331 description
=> "Snapshot a VM.",
3333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3336 additionalProperties
=> 0,
3338 node
=> get_standard_option
('pve-node'),
3339 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3340 snapname
=> get_standard_option
('pve-snapshot-name'),
3344 description
=> "Save the vmstate",
3349 description
=> "A textual description or comment.",
3355 description
=> "the task ID.",
3360 my $rpcenv = PVE
::RPCEnvironment
::get
();
3362 my $authuser = $rpcenv->get_user();
3364 my $node = extract_param
($param, 'node');
3366 my $vmid = extract_param
($param, 'vmid');
3368 my $snapname = extract_param
($param, 'snapname');
3370 die "unable to use snapshot name 'current' (reserved name)\n"
3371 if $snapname eq 'current';
3374 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3375 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3376 $param->{description
});
3379 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3382 __PACKAGE__-
>register_method({
3383 name
=> 'snapshot_cmd_idx',
3384 path
=> '{vmid}/snapshot/{snapname}',
3391 additionalProperties
=> 0,
3393 vmid
=> get_standard_option
('pve-vmid'),
3394 node
=> get_standard_option
('pve-node'),
3395 snapname
=> get_standard_option
('pve-snapshot-name'),
3404 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3411 push @$res, { cmd
=> 'rollback' };
3412 push @$res, { cmd
=> 'config' };
3417 __PACKAGE__-
>register_method({
3418 name
=> 'update_snapshot_config',
3419 path
=> '{vmid}/snapshot/{snapname}/config',
3423 description
=> "Update snapshot metadata.",
3425 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3428 additionalProperties
=> 0,
3430 node
=> get_standard_option
('pve-node'),
3431 vmid
=> get_standard_option
('pve-vmid'),
3432 snapname
=> get_standard_option
('pve-snapshot-name'),
3436 description
=> "A textual description or comment.",
3440 returns
=> { type
=> 'null' },
3444 my $rpcenv = PVE
::RPCEnvironment
::get
();
3446 my $authuser = $rpcenv->get_user();
3448 my $vmid = extract_param
($param, 'vmid');
3450 my $snapname = extract_param
($param, 'snapname');
3452 return undef if !defined($param->{description
});
3454 my $updatefn = sub {
3456 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3458 PVE
::QemuConfig-
>check_lock($conf);
3460 my $snap = $conf->{snapshots
}->{$snapname};
3462 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3464 $snap->{description
} = $param->{description
} if defined($param->{description
});
3466 PVE
::QemuConfig-
>write_config($vmid, $conf);
3469 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3474 __PACKAGE__-
>register_method({
3475 name
=> 'get_snapshot_config',
3476 path
=> '{vmid}/snapshot/{snapname}/config',
3479 description
=> "Get snapshot configuration",
3481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3484 additionalProperties
=> 0,
3486 node
=> get_standard_option
('pve-node'),
3487 vmid
=> get_standard_option
('pve-vmid'),
3488 snapname
=> get_standard_option
('pve-snapshot-name'),
3491 returns
=> { type
=> "object" },
3495 my $rpcenv = PVE
::RPCEnvironment
::get
();
3497 my $authuser = $rpcenv->get_user();
3499 my $vmid = extract_param
($param, 'vmid');
3501 my $snapname = extract_param
($param, 'snapname');
3503 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3505 my $snap = $conf->{snapshots
}->{$snapname};
3507 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3512 __PACKAGE__-
>register_method({
3514 path
=> '{vmid}/snapshot/{snapname}/rollback',
3518 description
=> "Rollback VM state to specified snapshot.",
3520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3523 additionalProperties
=> 0,
3525 node
=> get_standard_option
('pve-node'),
3526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3527 snapname
=> get_standard_option
('pve-snapshot-name'),
3532 description
=> "the task ID.",
3537 my $rpcenv = PVE
::RPCEnvironment
::get
();
3539 my $authuser = $rpcenv->get_user();
3541 my $node = extract_param
($param, 'node');
3543 my $vmid = extract_param
($param, 'vmid');
3545 my $snapname = extract_param
($param, 'snapname');
3548 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3549 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3553 # hold migration lock, this makes sure that nobody create replication snapshots
3554 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3557 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3560 __PACKAGE__-
>register_method({
3561 name
=> 'delsnapshot',
3562 path
=> '{vmid}/snapshot/{snapname}',
3566 description
=> "Delete a VM snapshot.",
3568 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3571 additionalProperties
=> 0,
3573 node
=> get_standard_option
('pve-node'),
3574 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3575 snapname
=> get_standard_option
('pve-snapshot-name'),
3579 description
=> "For removal from config file, even if removing disk snapshots fails.",
3585 description
=> "the task ID.",
3590 my $rpcenv = PVE
::RPCEnvironment
::get
();
3592 my $authuser = $rpcenv->get_user();
3594 my $node = extract_param
($param, 'node');
3596 my $vmid = extract_param
($param, 'vmid');
3598 my $snapname = extract_param
($param, 'snapname');
3601 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3602 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3605 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3608 __PACKAGE__-
>register_method({
3610 path
=> '{vmid}/template',
3614 description
=> "Create a Template.",
3616 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3617 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3620 additionalProperties
=> 0,
3622 node
=> get_standard_option
('pve-node'),
3623 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3627 description
=> "If you want to convert only 1 disk to base image.",
3628 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3633 returns
=> { type
=> 'null'},
3637 my $rpcenv = PVE
::RPCEnvironment
::get
();
3639 my $authuser = $rpcenv->get_user();
3641 my $node = extract_param
($param, 'node');
3643 my $vmid = extract_param
($param, 'vmid');
3645 my $disk = extract_param
($param, 'disk');
3647 my $updatefn = sub {
3649 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3651 PVE
::QemuConfig-
>check_lock($conf);
3653 die "unable to create template, because VM contains snapshots\n"
3654 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3656 die "you can't convert a template to a template\n"
3657 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3659 die "you can't convert a VM to template if VM is running\n"
3660 if PVE
::QemuServer
::check_running
($vmid);
3663 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3666 $conf->{template
} = 1;
3667 PVE
::QemuConfig-
>write_config($vmid, $conf);
3669 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3672 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);