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).",
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 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) if $start_after_create;
570 # ensure no old replication state are exists
571 PVE
::ReplicationState
::delete_guest_states
($vmid);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
577 # ensure no old replication state are exists
578 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
590 if (!$conf->{bootdisk
}) {
591 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
592 $conf->{bootdisk
} = $firstdisk if $firstdisk;
595 # auto generate uuid if user did not specify smbios1 option
596 if (!$conf->{smbios1
}) {
597 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
600 PVE
::QemuConfig-
>write_config($vmid, $conf);
606 foreach my $volid (@$vollist) {
607 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
613 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
616 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
618 if ($start_after_create) {
619 print "Execute autostart\n";
620 PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node});
624 my $worker_name = $is_restore ?
'qmrestore' : 'qmcreate';
625 my $code = $is_restore ?
$restorefn : $createfn;
627 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
630 __PACKAGE__-
>register_method({
635 description
=> "Directory index",
640 additionalProperties
=> 0,
642 node
=> get_standard_option
('pve-node'),
643 vmid
=> get_standard_option
('pve-vmid'),
651 subdir
=> { type
=> 'string' },
654 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
660 { subdir
=> 'config' },
661 { subdir
=> 'pending' },
662 { subdir
=> 'status' },
663 { subdir
=> 'unlink' },
664 { subdir
=> 'vncproxy' },
665 { subdir
=> 'termproxy' },
666 { subdir
=> 'migrate' },
667 { subdir
=> 'resize' },
668 { subdir
=> 'move' },
670 { subdir
=> 'rrddata' },
671 { subdir
=> 'monitor' },
672 { subdir
=> 'agent' },
673 { subdir
=> 'snapshot' },
674 { subdir
=> 'spiceproxy' },
675 { subdir
=> 'sendkey' },
676 { subdir
=> 'firewall' },
682 __PACKAGE__-
>register_method ({
683 subclass
=> "PVE::API2::Firewall::VM",
684 path
=> '{vmid}/firewall',
687 __PACKAGE__-
>register_method ({
688 subclass
=> "PVE::API2::Qemu::Agent",
689 path
=> '{vmid}/agent',
692 __PACKAGE__-
>register_method({
694 path
=> '{vmid}/rrd',
696 protected
=> 1, # fixme: can we avoid that?
698 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
700 description
=> "Read VM RRD statistics (returns PNG)",
702 additionalProperties
=> 0,
704 node
=> get_standard_option
('pve-node'),
705 vmid
=> get_standard_option
('pve-vmid'),
707 description
=> "Specify the time frame you are interested in.",
709 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
712 description
=> "The list of datasources you want to display.",
713 type
=> 'string', format
=> 'pve-configid-list',
716 description
=> "The RRD consolidation function",
718 enum
=> [ 'AVERAGE', 'MAX' ],
726 filename
=> { type
=> 'string' },
732 return PVE
::Cluster
::create_rrd_graph
(
733 "pve2-vm/$param->{vmid}", $param->{timeframe
},
734 $param->{ds
}, $param->{cf
});
738 __PACKAGE__-
>register_method({
740 path
=> '{vmid}/rrddata',
742 protected
=> 1, # fixme: can we avoid that?
744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
746 description
=> "Read VM RRD statistics",
748 additionalProperties
=> 0,
750 node
=> get_standard_option
('pve-node'),
751 vmid
=> get_standard_option
('pve-vmid'),
753 description
=> "Specify the time frame you are interested in.",
755 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
758 description
=> "The RRD consolidation function",
760 enum
=> [ 'AVERAGE', 'MAX' ],
775 return PVE
::Cluster
::create_rrd_data
(
776 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
780 __PACKAGE__-
>register_method({
782 path
=> '{vmid}/config',
785 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
790 additionalProperties
=> 0,
792 node
=> get_standard_option
('pve-node'),
793 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
795 description
=> "Get current values (instead of pending values).",
807 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
814 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
816 delete $conf->{snapshots
};
818 if (!$param->{current
}) {
819 foreach my $opt (keys %{$conf->{pending
}}) {
820 next if $opt eq 'delete';
821 my $value = $conf->{pending
}->{$opt};
822 next if ref($value); # just to be sure
823 $conf->{$opt} = $value;
825 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
826 foreach my $opt (keys %$pending_delete_hash) {
827 delete $conf->{$opt} if $conf->{$opt};
831 delete $conf->{pending
};
833 # hide cloudinit password
834 if ($conf->{cipassword
}) {
835 $conf->{cipassword
} = '**********';
841 __PACKAGE__-
>register_method({
842 name
=> 'vm_pending',
843 path
=> '{vmid}/pending',
846 description
=> "Get virtual machine configuration, including pending changes.",
848 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
851 additionalProperties
=> 0,
853 node
=> get_standard_option
('pve-node'),
854 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
863 description
=> "Configuration option name.",
867 description
=> "Current value.",
872 description
=> "Pending value.",
877 description
=> "Indicates a pending delete request if present and not 0. " .
878 "The value 2 indicates a force-delete request.",
890 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
892 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
896 foreach my $opt (keys %$conf) {
897 next if ref($conf->{$opt});
898 my $item = { key
=> $opt };
899 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
900 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
901 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
903 # hide cloudinit password
904 if ($opt eq 'cipassword') {
905 $item->{value
} = '**********' if defined($item->{value
});
906 # the trailing space so that the pending string is different
907 $item->{pending
} = '********** ' if defined($item->{pending
});
912 foreach my $opt (keys %{$conf->{pending
}}) {
913 next if $opt eq 'delete';
914 next if ref($conf->{pending
}->{$opt}); # just to be sure
915 next if defined($conf->{$opt});
916 my $item = { key
=> $opt };
917 $item->{pending
} = $conf->{pending
}->{$opt};
919 # hide cloudinit password
920 if ($opt eq 'cipassword') {
921 $item->{pending
} = '**********' if defined($item->{pending
});
926 while (my ($opt, $force) = each %$pending_delete_hash) {
927 next if $conf->{pending
}->{$opt}; # just to be sure
928 next if $conf->{$opt};
929 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
936 # POST/PUT {vmid}/config implementation
938 # The original API used PUT (idempotent) an we assumed that all operations
939 # are fast. But it turned out that almost any configuration change can
940 # involve hot-plug actions, or disk alloc/free. Such actions can take long
941 # time to complete and have side effects (not idempotent).
943 # The new implementation uses POST and forks a worker process. We added
944 # a new option 'background_delay'. If specified we wait up to
945 # 'background_delay' second for the worker task to complete. It returns null
946 # if the task is finished within that time, else we return the UPID.
948 my $update_vm_api = sub {
949 my ($param, $sync) = @_;
951 my $rpcenv = PVE
::RPCEnvironment
::get
();
953 my $authuser = $rpcenv->get_user();
955 my $node = extract_param
($param, 'node');
957 my $vmid = extract_param
($param, 'vmid');
959 my $digest = extract_param
($param, 'digest');
961 my $background_delay = extract_param
($param, 'background_delay');
963 if (defined(my $cipassword = $param->{cipassword
})) {
964 # Same logic as in cloud-init (but with the regex fixed...)
965 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
966 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
969 my @paramarr = (); # used for log message
970 foreach my $key (sort keys %$param) {
971 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
972 push @paramarr, "-$key", $value;
975 my $skiplock = extract_param
($param, 'skiplock');
976 raise_param_exc
({ skiplock
=> "Only root may use this option." })
977 if $skiplock && $authuser ne 'root@pam';
979 my $delete_str = extract_param
($param, 'delete');
981 my $revert_str = extract_param
($param, 'revert');
983 my $force = extract_param
($param, 'force');
985 if (defined(my $ssh_keys = $param->{sshkeys
})) {
986 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
987 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
990 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
992 my $storecfg = PVE
::Storage
::config
();
994 my $defaults = PVE
::QemuServer
::load_defaults
();
996 &$resolve_cdrom_alias($param);
998 # now try to verify all parameters
1001 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1002 if (!PVE
::QemuServer
::option_exists
($opt)) {
1003 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1006 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1007 "-revert $opt' at the same time" })
1008 if defined($param->{$opt});
1010 $revert->{$opt} = 1;
1014 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1015 $opt = 'ide2' if $opt eq 'cdrom';
1017 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1018 "-delete $opt' at the same time" })
1019 if defined($param->{$opt});
1021 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1022 "-revert $opt' at the same time" })
1025 if (!PVE
::QemuServer
::option_exists
($opt)) {
1026 raise_param_exc
({ delete => "unknown option '$opt'" });
1032 my $repl_conf = PVE
::ReplicationConfig-
>new();
1033 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1034 my $check_replication = sub {
1036 return if !$is_replicated;
1037 my $volid = $drive->{file
};
1038 return if !$volid || !($drive->{replicate
}//1);
1039 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1040 my ($storeid, $format);
1041 if ($volid =~ $NEW_DISK_RE) {
1043 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1045 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1046 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1048 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1049 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1050 return if $scfg->{shared
};
1051 die "cannot add non-replicatable volume to a replicated VM\n";
1054 foreach my $opt (keys %$param) {
1055 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1056 # cleanup drive path
1057 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1058 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1059 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1060 $check_replication->($drive);
1061 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1062 } elsif ($opt =~ m/^net(\d+)$/) {
1064 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1065 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1069 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1071 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1073 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1075 my $updatefn = sub {
1077 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1079 die "checksum missmatch (file change by other user?)\n"
1080 if $digest && $digest ne $conf->{digest
};
1082 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1084 foreach my $opt (keys %$revert) {
1085 if (defined($conf->{$opt})) {
1086 $param->{$opt} = $conf->{$opt};
1087 } elsif (defined($conf->{pending
}->{$opt})) {
1092 if ($param->{memory
} || defined($param->{balloon
})) {
1093 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1094 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1096 die "balloon value too large (must be smaller than assigned memory)\n"
1097 if $balloon && $balloon > $maxmem;
1100 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1104 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1106 # write updates to pending section
1108 my $modified = {}; # record what $option we modify
1110 foreach my $opt (@delete) {
1111 $modified->{$opt} = 1;
1112 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1113 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1114 warn "cannot delete '$opt' - not set in current configuration!\n";
1115 $modified->{$opt} = 0;
1119 if ($opt =~ m/^unused/) {
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1121 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1122 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1123 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1124 delete $conf->{$opt};
1125 PVE
::QemuConfig-
>write_config($vmid, $conf);
1127 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1128 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1129 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1130 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1131 if defined($conf->{pending
}->{$opt});
1132 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1133 PVE
::QemuConfig-
>write_config($vmid, $conf);
1135 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1136 PVE
::QemuConfig-
>write_config($vmid, $conf);
1140 foreach my $opt (keys %$param) { # add/change
1141 $modified->{$opt} = 1;
1142 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1143 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1145 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1146 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1147 # FIXME: cloudinit: CDROM or Disk?
1148 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1149 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1151 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1153 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1154 if defined($conf->{pending
}->{$opt});
1156 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1158 $conf->{pending
}->{$opt} = $param->{$opt};
1160 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1164 # remove pending changes when nothing changed
1165 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1167 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1169 return if !scalar(keys %{$conf->{pending
}});
1171 my $running = PVE
::QemuServer
::check_running
($vmid);
1173 # apply pending changes
1175 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1179 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1180 raise_param_exc
($errors) if scalar(keys %$errors);
1182 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1192 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1194 if ($background_delay) {
1196 # Note: It would be better to do that in the Event based HTTPServer
1197 # to avoid blocking call to sleep.
1199 my $end_time = time() + $background_delay;
1201 my $task = PVE
::Tools
::upid_decode
($upid);
1204 while (time() < $end_time) {
1205 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1207 sleep(1); # this gets interrupted when child process ends
1211 my $status = PVE
::Tools
::upid_read_status
($upid);
1212 return undef if $status eq 'OK';
1221 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1224 my $vm_config_perm_list = [
1229 'VM.Config.Network',
1231 'VM.Config.Options',
1234 __PACKAGE__-
>register_method({
1235 name
=> 'update_vm_async',
1236 path
=> '{vmid}/config',
1240 description
=> "Set virtual machine options (asynchrounous API).",
1242 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1245 additionalProperties
=> 0,
1246 properties
=> PVE
::QemuServer
::json_config_properties
(
1248 node
=> get_standard_option
('pve-node'),
1249 vmid
=> get_standard_option
('pve-vmid'),
1250 skiplock
=> get_standard_option
('skiplock'),
1252 type
=> 'string', format
=> 'pve-configid-list',
1253 description
=> "A list of settings you want to delete.",
1257 type
=> 'string', format
=> 'pve-configid-list',
1258 description
=> "Revert a pending change.",
1263 description
=> $opt_force_description,
1265 requires
=> 'delete',
1269 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1273 background_delay
=> {
1275 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1286 code
=> $update_vm_api,
1289 __PACKAGE__-
>register_method({
1290 name
=> 'update_vm',
1291 path
=> '{vmid}/config',
1295 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1297 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1300 additionalProperties
=> 0,
1301 properties
=> PVE
::QemuServer
::json_config_properties
(
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1305 skiplock
=> get_standard_option
('skiplock'),
1307 type
=> 'string', format
=> 'pve-configid-list',
1308 description
=> "A list of settings you want to delete.",
1312 type
=> 'string', format
=> 'pve-configid-list',
1313 description
=> "Revert a pending change.",
1318 description
=> $opt_force_description,
1320 requires
=> 'delete',
1324 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1330 returns
=> { type
=> 'null' },
1333 &$update_vm_api($param, 1);
1339 __PACKAGE__-
>register_method({
1340 name
=> 'destroy_vm',
1345 description
=> "Destroy the vm (also delete all used/owned volumes).",
1347 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1350 additionalProperties
=> 0,
1352 node
=> get_standard_option
('pve-node'),
1353 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1354 skiplock
=> get_standard_option
('skiplock'),
1363 my $rpcenv = PVE
::RPCEnvironment
::get
();
1365 my $authuser = $rpcenv->get_user();
1367 my $vmid = $param->{vmid
};
1369 my $skiplock = $param->{skiplock
};
1370 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1371 if $skiplock && $authuser ne 'root@pam';
1374 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1376 my $storecfg = PVE
::Storage
::config
();
1378 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1380 die "unable to remove VM $vmid - used in HA resources\n"
1381 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1383 # do not allow destroy if there are replication jobs
1384 my $repl_conf = PVE
::ReplicationConfig-
>new();
1385 $repl_conf->check_for_existing_jobs($vmid);
1387 # early tests (repeat after locking)
1388 die "VM $vmid is running - destroy failed\n"
1389 if PVE
::QemuServer
::check_running
($vmid);
1394 syslog
('info', "destroy VM $vmid: $upid\n");
1396 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1398 PVE
::AccessControl
::remove_vm_access
($vmid);
1400 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1403 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1406 __PACKAGE__-
>register_method({
1408 path
=> '{vmid}/unlink',
1412 description
=> "Unlink/delete disk images.",
1414 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1417 additionalProperties
=> 0,
1419 node
=> get_standard_option
('pve-node'),
1420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1422 type
=> 'string', format
=> 'pve-configid-list',
1423 description
=> "A list of disk IDs you want to delete.",
1427 description
=> $opt_force_description,
1432 returns
=> { type
=> 'null'},
1436 $param->{delete} = extract_param
($param, 'idlist');
1438 __PACKAGE__-
>update_vm($param);
1445 __PACKAGE__-
>register_method({
1447 path
=> '{vmid}/vncproxy',
1451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1453 description
=> "Creates a TCP VNC proxy connections.",
1455 additionalProperties
=> 0,
1457 node
=> get_standard_option
('pve-node'),
1458 vmid
=> get_standard_option
('pve-vmid'),
1462 description
=> "starts websockify instead of vncproxy",
1467 additionalProperties
=> 0,
1469 user
=> { type
=> 'string' },
1470 ticket
=> { type
=> 'string' },
1471 cert
=> { type
=> 'string' },
1472 port
=> { type
=> 'integer' },
1473 upid
=> { type
=> 'string' },
1479 my $rpcenv = PVE
::RPCEnvironment
::get
();
1481 my $authuser = $rpcenv->get_user();
1483 my $vmid = $param->{vmid
};
1484 my $node = $param->{node
};
1485 my $websocket = $param->{websocket
};
1487 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1489 my $authpath = "/vms/$vmid";
1491 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1493 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1496 my ($remip, $family);
1499 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1500 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1501 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1502 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1504 $family = PVE
::Tools
::get_host_address_family
($node);
1507 my $port = PVE
::Tools
::next_vnc_port
($family);
1514 syslog
('info', "starting vnc proxy $upid\n");
1518 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1521 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1523 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1524 '-timeout', $timeout, '-authpath', $authpath,
1525 '-perm', 'Sys.Console'];
1527 if ($param->{websocket
}) {
1528 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1529 push @$cmd, '-notls', '-listen', 'localhost';
1532 push @$cmd, '-c', @$remcmd, @$termcmd;
1534 PVE
::Tools
::run_command
($cmd);
1538 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1540 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1542 my $sock = IO
::Socket
::IP-
>new(
1547 GetAddrInfoFlags
=> 0,
1548 ) or die "failed to create socket: $!\n";
1549 # Inside the worker we shouldn't have any previous alarms
1550 # running anyway...:
1552 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1554 accept(my $cli, $sock) or die "connection failed: $!\n";
1557 if (PVE
::Tools
::run_command
($cmd,
1558 output
=> '>&'.fileno($cli),
1559 input
=> '<&'.fileno($cli),
1562 die "Failed to run vncproxy.\n";
1569 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1571 PVE
::Tools
::wait_for_vnc_port
($port);
1582 __PACKAGE__-
>register_method({
1583 name
=> 'termproxy',
1584 path
=> '{vmid}/termproxy',
1588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1590 description
=> "Creates a TCP proxy connections.",
1592 additionalProperties
=> 0,
1594 node
=> get_standard_option
('pve-node'),
1595 vmid
=> get_standard_option
('pve-vmid'),
1599 enum
=> [qw(serial0 serial1 serial2 serial3)],
1600 description
=> "opens a serial terminal (defaults to display)",
1605 additionalProperties
=> 0,
1607 user
=> { type
=> 'string' },
1608 ticket
=> { type
=> 'string' },
1609 port
=> { type
=> 'integer' },
1610 upid
=> { type
=> 'string' },
1616 my $rpcenv = PVE
::RPCEnvironment
::get
();
1618 my $authuser = $rpcenv->get_user();
1620 my $vmid = $param->{vmid
};
1621 my $node = $param->{node
};
1622 my $serial = $param->{serial
};
1624 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1626 if (!defined($serial)) {
1627 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1628 $serial = $conf->{vga
};
1632 my $authpath = "/vms/$vmid";
1634 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1636 my ($remip, $family);
1638 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1639 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1641 $family = PVE
::Tools
::get_host_address_family
($node);
1644 my $port = PVE
::Tools
::next_vnc_port
($family);
1646 my $remcmd = $remip ?
1647 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1649 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1650 push @$termcmd, '-iface', $serial if $serial;
1655 syslog
('info', "starting qemu termproxy $upid\n");
1657 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1658 '--perm', 'VM.Console', '--'];
1659 push @$cmd, @$remcmd, @$termcmd;
1661 PVE
::Tools
::run_command
($cmd);
1664 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1666 PVE
::Tools
::wait_for_vnc_port
($port);
1676 __PACKAGE__-
>register_method({
1677 name
=> 'vncwebsocket',
1678 path
=> '{vmid}/vncwebsocket',
1681 description
=> "You also need to pass a valid ticket (vncticket).",
1682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1684 description
=> "Opens a weksocket for VNC traffic.",
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid'),
1691 description
=> "Ticket from previous call to vncproxy.",
1696 description
=> "Port number returned by previous vncproxy call.",
1706 port
=> { type
=> 'string' },
1712 my $rpcenv = PVE
::RPCEnvironment
::get
();
1714 my $authuser = $rpcenv->get_user();
1716 my $vmid = $param->{vmid
};
1717 my $node = $param->{node
};
1719 my $authpath = "/vms/$vmid";
1721 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1723 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1725 # Note: VNC ports are acessible from outside, so we do not gain any
1726 # security if we verify that $param->{port} belongs to VM $vmid. This
1727 # check is done by verifying the VNC ticket (inside VNC protocol).
1729 my $port = $param->{port
};
1731 return { port
=> $port };
1734 __PACKAGE__-
>register_method({
1735 name
=> 'spiceproxy',
1736 path
=> '{vmid}/spiceproxy',
1741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1743 description
=> "Returns a SPICE configuration to connect to the VM.",
1745 additionalProperties
=> 0,
1747 node
=> get_standard_option
('pve-node'),
1748 vmid
=> get_standard_option
('pve-vmid'),
1749 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1752 returns
=> get_standard_option
('remote-viewer-config'),
1756 my $rpcenv = PVE
::RPCEnvironment
::get
();
1758 my $authuser = $rpcenv->get_user();
1760 my $vmid = $param->{vmid
};
1761 my $node = $param->{node
};
1762 my $proxy = $param->{proxy
};
1764 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1765 my $title = "VM $vmid";
1766 $title .= " - ". $conf->{name
} if $conf->{name
};
1768 my $port = PVE
::QemuServer
::spice_port
($vmid);
1770 my ($ticket, undef, $remote_viewer_config) =
1771 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1773 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1774 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1776 return $remote_viewer_config;
1779 __PACKAGE__-
>register_method({
1781 path
=> '{vmid}/status',
1784 description
=> "Directory index",
1789 additionalProperties
=> 0,
1791 node
=> get_standard_option
('pve-node'),
1792 vmid
=> get_standard_option
('pve-vmid'),
1800 subdir
=> { type
=> 'string' },
1803 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1809 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1812 { subdir
=> 'current' },
1813 { subdir
=> 'start' },
1814 { subdir
=> 'stop' },
1820 __PACKAGE__-
>register_method({
1821 name
=> 'vm_status',
1822 path
=> '{vmid}/status/current',
1825 protected
=> 1, # qemu pid files are only readable by root
1826 description
=> "Get virtual machine status.",
1828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1831 additionalProperties
=> 0,
1833 node
=> get_standard_option
('pve-node'),
1834 vmid
=> get_standard_option
('pve-vmid'),
1837 returns
=> { type
=> 'object' },
1842 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1844 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1845 my $status = $vmstatus->{$param->{vmid
}};
1847 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1849 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1851 $status->{agent
} = 1 if $conf->{agent
};
1856 __PACKAGE__-
>register_method({
1858 path
=> '{vmid}/status/start',
1862 description
=> "Start virtual machine.",
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1867 additionalProperties
=> 0,
1869 node
=> get_standard_option
('pve-node'),
1870 vmid
=> get_standard_option
('pve-vmid',
1871 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1872 skiplock
=> get_standard_option
('skiplock'),
1873 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1874 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1877 enum
=> ['secure', 'insecure'],
1878 description
=> "Migration traffic is encrypted using an SSH " .
1879 "tunnel by default. On secure, completely private networks " .
1880 "this can be disabled to increase performance.",
1883 migration_network
=> {
1884 type
=> 'string', format
=> 'CIDR',
1885 description
=> "CIDR of the (sub) network that is used for migration.",
1888 machine
=> get_standard_option
('pve-qm-machine'),
1890 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1902 my $rpcenv = PVE
::RPCEnvironment
::get
();
1904 my $authuser = $rpcenv->get_user();
1906 my $node = extract_param
($param, 'node');
1908 my $vmid = extract_param
($param, 'vmid');
1910 my $machine = extract_param
($param, 'machine');
1912 my $stateuri = extract_param
($param, 'stateuri');
1913 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1914 if $stateuri && $authuser ne 'root@pam';
1916 my $skiplock = extract_param
($param, 'skiplock');
1917 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1918 if $skiplock && $authuser ne 'root@pam';
1920 my $migratedfrom = extract_param
($param, 'migratedfrom');
1921 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1922 if $migratedfrom && $authuser ne 'root@pam';
1924 my $migration_type = extract_param
($param, 'migration_type');
1925 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1926 if $migration_type && $authuser ne 'root@pam';
1928 my $migration_network = extract_param
($param, 'migration_network');
1929 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1930 if $migration_network && $authuser ne 'root@pam';
1932 my $targetstorage = extract_param
($param, 'targetstorage');
1933 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1934 if $targetstorage && $authuser ne 'root@pam';
1936 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1937 if $targetstorage && !$migratedfrom;
1939 # read spice ticket from STDIN
1941 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1942 if (defined(my $line = <STDIN
>)) {
1944 $spice_ticket = $line;
1948 PVE
::Cluster
::check_cfs_quorum
();
1950 my $storecfg = PVE
::Storage
::config
();
1952 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1953 $rpcenv->{type
} ne 'ha') {
1958 my $service = "vm:$vmid";
1960 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1962 print "Requesting HA start for VM $vmid\n";
1964 PVE
::Tools
::run_command
($cmd);
1969 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1976 syslog
('info', "start VM $vmid: $upid\n");
1978 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1979 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1984 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1988 __PACKAGE__-
>register_method({
1990 path
=> '{vmid}/status/stop',
1994 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1995 "is akin to pulling the power plug of a running computer and may damage the VM data",
1997 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2000 additionalProperties
=> 0,
2002 node
=> get_standard_option
('pve-node'),
2003 vmid
=> get_standard_option
('pve-vmid',
2004 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2005 skiplock
=> get_standard_option
('skiplock'),
2006 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2008 description
=> "Wait maximal timeout seconds.",
2014 description
=> "Do not deactivate storage volumes.",
2027 my $rpcenv = PVE
::RPCEnvironment
::get
();
2029 my $authuser = $rpcenv->get_user();
2031 my $node = extract_param
($param, 'node');
2033 my $vmid = extract_param
($param, 'vmid');
2035 my $skiplock = extract_param
($param, 'skiplock');
2036 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2037 if $skiplock && $authuser ne 'root@pam';
2039 my $keepActive = extract_param
($param, 'keepActive');
2040 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2041 if $keepActive && $authuser ne 'root@pam';
2043 my $migratedfrom = extract_param
($param, 'migratedfrom');
2044 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2045 if $migratedfrom && $authuser ne 'root@pam';
2048 my $storecfg = PVE
::Storage
::config
();
2050 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2055 my $service = "vm:$vmid";
2057 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2059 print "Requesting HA stop for VM $vmid\n";
2061 PVE
::Tools
::run_command
($cmd);
2066 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2072 syslog
('info', "stop VM $vmid: $upid\n");
2074 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2075 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2080 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2084 __PACKAGE__-
>register_method({
2086 path
=> '{vmid}/status/reset',
2090 description
=> "Reset virtual machine.",
2092 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2095 additionalProperties
=> 0,
2097 node
=> get_standard_option
('pve-node'),
2098 vmid
=> get_standard_option
('pve-vmid',
2099 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2100 skiplock
=> get_standard_option
('skiplock'),
2109 my $rpcenv = PVE
::RPCEnvironment
::get
();
2111 my $authuser = $rpcenv->get_user();
2113 my $node = extract_param
($param, 'node');
2115 my $vmid = extract_param
($param, 'vmid');
2117 my $skiplock = extract_param
($param, 'skiplock');
2118 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2119 if $skiplock && $authuser ne 'root@pam';
2121 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2126 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2131 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2134 __PACKAGE__-
>register_method({
2135 name
=> 'vm_shutdown',
2136 path
=> '{vmid}/status/shutdown',
2140 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2141 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2143 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2146 additionalProperties
=> 0,
2148 node
=> get_standard_option
('pve-node'),
2149 vmid
=> get_standard_option
('pve-vmid',
2150 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2151 skiplock
=> get_standard_option
('skiplock'),
2153 description
=> "Wait maximal timeout seconds.",
2159 description
=> "Make sure the VM stops.",
2165 description
=> "Do not deactivate storage volumes.",
2178 my $rpcenv = PVE
::RPCEnvironment
::get
();
2180 my $authuser = $rpcenv->get_user();
2182 my $node = extract_param
($param, 'node');
2184 my $vmid = extract_param
($param, 'vmid');
2186 my $skiplock = extract_param
($param, 'skiplock');
2187 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2188 if $skiplock && $authuser ne 'root@pam';
2190 my $keepActive = extract_param
($param, 'keepActive');
2191 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2192 if $keepActive && $authuser ne 'root@pam';
2194 my $storecfg = PVE
::Storage
::config
();
2198 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2199 # otherwise, we will infer a shutdown command, but run into the timeout,
2200 # then when the vm is resumed, it will instantly shutdown
2202 # checking the qmp status here to get feedback to the gui/cli/api
2203 # and the status query should not take too long
2206 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2210 if (!$err && $qmpstatus->{status
} eq "paused") {
2211 if ($param->{forceStop
}) {
2212 warn "VM is paused - stop instead of shutdown\n";
2215 die "VM is paused - cannot shutdown\n";
2219 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2220 ($rpcenv->{type
} ne 'ha')) {
2225 my $service = "vm:$vmid";
2227 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2229 print "Requesting HA stop for VM $vmid\n";
2231 PVE
::Tools
::run_command
($cmd);
2236 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2243 syslog
('info', "shutdown VM $vmid: $upid\n");
2245 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2246 $shutdown, $param->{forceStop
}, $keepActive);
2251 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2255 __PACKAGE__-
>register_method({
2256 name
=> 'vm_suspend',
2257 path
=> '{vmid}/status/suspend',
2261 description
=> "Suspend virtual machine.",
2263 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2266 additionalProperties
=> 0,
2268 node
=> get_standard_option
('pve-node'),
2269 vmid
=> get_standard_option
('pve-vmid',
2270 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2271 skiplock
=> get_standard_option
('skiplock'),
2280 my $rpcenv = PVE
::RPCEnvironment
::get
();
2282 my $authuser = $rpcenv->get_user();
2284 my $node = extract_param
($param, 'node');
2286 my $vmid = extract_param
($param, 'vmid');
2288 my $skiplock = extract_param
($param, 'skiplock');
2289 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2290 if $skiplock && $authuser ne 'root@pam';
2292 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2297 syslog
('info', "suspend VM $vmid: $upid\n");
2299 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2304 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2307 __PACKAGE__-
>register_method({
2308 name
=> 'vm_resume',
2309 path
=> '{vmid}/status/resume',
2313 description
=> "Resume virtual machine.",
2315 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2318 additionalProperties
=> 0,
2320 node
=> get_standard_option
('pve-node'),
2321 vmid
=> get_standard_option
('pve-vmid',
2322 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2323 skiplock
=> get_standard_option
('skiplock'),
2324 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2334 my $rpcenv = PVE
::RPCEnvironment
::get
();
2336 my $authuser = $rpcenv->get_user();
2338 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $skiplock = extract_param
($param, 'skiplock');
2343 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2344 if $skiplock && $authuser ne 'root@pam';
2346 my $nocheck = extract_param
($param, 'nocheck');
2348 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2353 syslog
('info', "resume VM $vmid: $upid\n");
2355 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2360 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2363 __PACKAGE__-
>register_method({
2364 name
=> 'vm_sendkey',
2365 path
=> '{vmid}/sendkey',
2369 description
=> "Send key event to virtual machine.",
2371 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2374 additionalProperties
=> 0,
2376 node
=> get_standard_option
('pve-node'),
2377 vmid
=> get_standard_option
('pve-vmid',
2378 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2379 skiplock
=> get_standard_option
('skiplock'),
2381 description
=> "The key (qemu monitor encoding).",
2386 returns
=> { type
=> 'null'},
2390 my $rpcenv = PVE
::RPCEnvironment
::get
();
2392 my $authuser = $rpcenv->get_user();
2394 my $node = extract_param
($param, 'node');
2396 my $vmid = extract_param
($param, 'vmid');
2398 my $skiplock = extract_param
($param, 'skiplock');
2399 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2400 if $skiplock && $authuser ne 'root@pam';
2402 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2407 __PACKAGE__-
>register_method({
2408 name
=> 'vm_feature',
2409 path
=> '{vmid}/feature',
2413 description
=> "Check if feature for virtual machine is available.",
2415 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2418 additionalProperties
=> 0,
2420 node
=> get_standard_option
('pve-node'),
2421 vmid
=> get_standard_option
('pve-vmid'),
2423 description
=> "Feature to check.",
2425 enum
=> [ 'snapshot', 'clone', 'copy' ],
2427 snapname
=> get_standard_option
('pve-snapshot-name', {
2435 hasFeature
=> { type
=> 'boolean' },
2438 items
=> { type
=> 'string' },
2445 my $node = extract_param
($param, 'node');
2447 my $vmid = extract_param
($param, 'vmid');
2449 my $snapname = extract_param
($param, 'snapname');
2451 my $feature = extract_param
($param, 'feature');
2453 my $running = PVE
::QemuServer
::check_running
($vmid);
2455 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2458 my $snap = $conf->{snapshots
}->{$snapname};
2459 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2462 my $storecfg = PVE
::Storage
::config
();
2464 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2465 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2468 hasFeature
=> $hasFeature,
2469 nodes
=> [ keys %$nodelist ],
2473 __PACKAGE__-
>register_method({
2475 path
=> '{vmid}/clone',
2479 description
=> "Create a copy of virtual machine/template.",
2481 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2482 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2483 "'Datastore.AllocateSpace' on any used storage.",
2486 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2488 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2489 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2494 additionalProperties
=> 0,
2496 node
=> get_standard_option
('pve-node'),
2497 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2498 newid
=> get_standard_option
('pve-vmid', {
2499 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2500 description
=> 'VMID for the clone.' }),
2503 type
=> 'string', format
=> 'dns-name',
2504 description
=> "Set a name for the new VM.",
2509 description
=> "Description for the new VM.",
2513 type
=> 'string', format
=> 'pve-poolid',
2514 description
=> "Add the new VM to the specified pool.",
2516 snapname
=> get_standard_option
('pve-snapshot-name', {
2519 storage
=> get_standard_option
('pve-storage-id', {
2520 description
=> "Target storage for full clone.",
2524 description
=> "Target format for file storage. Only valid for full clone.",
2527 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2532 description
=> "Create a full copy of all disks. This is always done when " .
2533 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2535 target
=> get_standard_option
('pve-node', {
2536 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2547 my $rpcenv = PVE
::RPCEnvironment
::get
();
2549 my $authuser = $rpcenv->get_user();
2551 my $node = extract_param
($param, 'node');
2553 my $vmid = extract_param
($param, 'vmid');
2555 my $newid = extract_param
($param, 'newid');
2557 my $pool = extract_param
($param, 'pool');
2559 if (defined($pool)) {
2560 $rpcenv->check_pool_exist($pool);
2563 my $snapname = extract_param
($param, 'snapname');
2565 my $storage = extract_param
($param, 'storage');
2567 my $format = extract_param
($param, 'format');
2569 my $target = extract_param
($param, 'target');
2571 my $localnode = PVE
::INotify
::nodename
();
2573 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2575 PVE
::Cluster
::check_node_exists
($target) if $target;
2577 my $storecfg = PVE
::Storage
::config
();
2580 # check if storage is enabled on local node
2581 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2583 # check if storage is available on target node
2584 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2585 # clone only works if target storage is shared
2586 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2587 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2591 PVE
::Cluster
::check_cfs_quorum
();
2593 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2595 # exclusive lock if VM is running - else shared lock is enough;
2596 my $shared_lock = $running ?
0 : 1;
2600 # do all tests after lock
2601 # we also try to do all tests before we fork the worker
2603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2605 PVE
::QemuConfig-
>check_lock($conf);
2607 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2609 die "unexpected state change\n" if $verify_running != $running;
2611 die "snapshot '$snapname' does not exist\n"
2612 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2614 my $full = extract_param
($param, 'full');
2615 if (!defined($full)) {
2616 $full = !PVE
::QemuConfig-
>is_template($conf);
2619 die "parameter 'storage' not allowed for linked clones\n"
2620 if defined($storage) && !$full;
2622 die "parameter 'format' not allowed for linked clones\n"
2623 if defined($format) && !$full;
2625 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2627 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2629 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2631 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2633 die "unable to create VM $newid: config file already exists\n"
2636 my $newconf = { lock => 'clone' };
2641 foreach my $opt (keys %$oldconf) {
2642 my $value = $oldconf->{$opt};
2644 # do not copy snapshot related info
2645 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2646 $opt eq 'vmstate' || $opt eq 'snapstate';
2648 # no need to copy unused images, because VMID(owner) changes anyways
2649 next if $opt =~ m/^unused\d+$/;
2651 # always change MAC! address
2652 if ($opt =~ m/^net(\d+)$/) {
2653 my $net = PVE
::QemuServer
::parse_net
($value);
2654 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2655 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2656 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2657 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2658 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2659 die "unable to parse drive options for '$opt'\n" if !$drive;
2660 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2661 $newconf->{$opt} = $value; # simply copy configuration
2663 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2664 die "Full clone feature is not supported for drive '$opt'\n"
2665 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2666 $fullclone->{$opt} = 1;
2668 # not full means clone instead of copy
2669 die "Linked clone feature is not supported for drive '$opt'\n"
2670 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2672 $drives->{$opt} = $drive;
2673 push @$vollist, $drive->{file
};
2676 # copy everything else
2677 $newconf->{$opt} = $value;
2681 # auto generate a new uuid
2682 my ($uuid, $uuid_str);
2683 UUID
::generate
($uuid);
2684 UUID
::unparse
($uuid, $uuid_str);
2685 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2686 $smbios1->{uuid
} = $uuid_str;
2687 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2689 delete $newconf->{template
};
2691 if ($param->{name
}) {
2692 $newconf->{name
} = $param->{name
};
2694 if ($oldconf->{name
}) {
2695 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2697 $newconf->{name
} = "Copy-of-VM-$vmid";
2701 if ($param->{description
}) {
2702 $newconf->{description
} = $param->{description
};
2705 # create empty/temp config - this fails if VM already exists on other node
2706 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2711 my $newvollist = [];
2718 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2720 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2722 my $total_jobs = scalar(keys %{$drives});
2725 foreach my $opt (keys %$drives) {
2726 my $drive = $drives->{$opt};
2727 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2729 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2730 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2731 $jobs, $skipcomplete, $oldconf->{agent
});
2733 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2735 PVE
::QemuConfig-
>write_config($newid, $newconf);
2739 delete $newconf->{lock};
2741 # do not write pending changes
2742 if ($newconf->{pending
}) {
2743 warn "found pending changes, discarding for clone\n";
2744 delete $newconf->{pending
};
2747 PVE
::QemuConfig-
>write_config($newid, $newconf);
2750 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2751 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2752 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2754 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2755 die "Failed to move config to node '$target' - rename failed: $!\n"
2756 if !rename($conffile, $newconffile);
2759 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2764 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2766 sleep 1; # some storage like rbd need to wait before release volume - really?
2768 foreach my $volid (@$newvollist) {
2769 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2772 die "clone failed: $err";
2778 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2780 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2783 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2784 # Aquire exclusive lock lock for $newid
2785 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2790 __PACKAGE__-
>register_method({
2791 name
=> 'move_vm_disk',
2792 path
=> '{vmid}/move_disk',
2796 description
=> "Move volume to different storage.",
2798 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2800 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2801 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2805 additionalProperties
=> 0,
2807 node
=> get_standard_option
('pve-node'),
2808 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2811 description
=> "The disk you want to move.",
2812 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2814 storage
=> get_standard_option
('pve-storage-id', {
2815 description
=> "Target storage.",
2816 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2820 description
=> "Target Format.",
2821 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2826 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2832 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2840 description
=> "the task ID.",
2845 my $rpcenv = PVE
::RPCEnvironment
::get
();
2847 my $authuser = $rpcenv->get_user();
2849 my $node = extract_param
($param, 'node');
2851 my $vmid = extract_param
($param, 'vmid');
2853 my $digest = extract_param
($param, 'digest');
2855 my $disk = extract_param
($param, 'disk');
2857 my $storeid = extract_param
($param, 'storage');
2859 my $format = extract_param
($param, 'format');
2861 my $storecfg = PVE
::Storage
::config
();
2863 my $updatefn = sub {
2865 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2867 PVE
::QemuConfig-
>check_lock($conf);
2869 die "checksum missmatch (file change by other user?)\n"
2870 if $digest && $digest ne $conf->{digest
};
2872 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2874 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2876 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2878 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2881 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2882 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2886 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2887 (!$format || !$oldfmt || $oldfmt eq $format);
2889 # this only checks snapshots because $disk is passed!
2890 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2891 die "you can't move a disk with snapshots and delete the source\n"
2892 if $snapshotted && $param->{delete};
2894 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2896 my $running = PVE
::QemuServer
::check_running
($vmid);
2898 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2902 my $newvollist = [];
2908 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2910 warn "moving disk with snapshots, snapshots will not be moved!\n"
2913 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2914 $vmid, $storeid, $format, 1, $newvollist);
2916 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2918 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2920 # convert moved disk to base if part of template
2921 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2922 if PVE
::QemuConfig-
>is_template($conf);
2924 PVE
::QemuConfig-
>write_config($vmid, $conf);
2927 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2928 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2935 foreach my $volid (@$newvollist) {
2936 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2939 die "storage migration failed: $err";
2942 if ($param->{delete}) {
2944 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2945 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2951 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2954 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2957 __PACKAGE__-
>register_method({
2958 name
=> 'migrate_vm',
2959 path
=> '{vmid}/migrate',
2963 description
=> "Migrate virtual machine. Creates a new migration task.",
2965 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2968 additionalProperties
=> 0,
2970 node
=> get_standard_option
('pve-node'),
2971 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2972 target
=> get_standard_option
('pve-node', {
2973 description
=> "Target node.",
2974 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2978 description
=> "Use online/live migration.",
2983 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2988 enum
=> ['secure', 'insecure'],
2989 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2992 migration_network
=> {
2993 type
=> 'string', format
=> 'CIDR',
2994 description
=> "CIDR of the (sub) network that is used for migration.",
2997 "with-local-disks" => {
2999 description
=> "Enable live storage migration for local disk",
3002 targetstorage
=> get_standard_option
('pve-storage-id', {
3003 description
=> "Default target storage.",
3005 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3011 description
=> "the task ID.",
3016 my $rpcenv = PVE
::RPCEnvironment
::get
();
3018 my $authuser = $rpcenv->get_user();
3020 my $target = extract_param
($param, 'target');
3022 my $localnode = PVE
::INotify
::nodename
();
3023 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3025 PVE
::Cluster
::check_cfs_quorum
();
3027 PVE
::Cluster
::check_node_exists
($target);
3029 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3031 my $vmid = extract_param
($param, 'vmid');
3033 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3034 if !$param->{online
} && $param->{targetstorage
};
3036 raise_param_exc
({ force
=> "Only root may use this option." })
3037 if $param->{force
} && $authuser ne 'root@pam';
3039 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3040 if $param->{migration_type
} && $authuser ne 'root@pam';
3042 # allow root only until better network permissions are available
3043 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3044 if $param->{migration_network
} && $authuser ne 'root@pam';
3047 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3049 # try to detect errors early
3051 PVE
::QemuConfig-
>check_lock($conf);
3053 if (PVE
::QemuServer
::check_running
($vmid)) {
3054 die "cant migrate running VM without --online\n"
3055 if !$param->{online
};
3058 my $storecfg = PVE
::Storage
::config
();
3060 if( $param->{targetstorage
}) {
3061 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3063 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3066 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3071 my $service = "vm:$vmid";
3073 my $cmd = ['ha-manager', 'migrate', $service, $target];
3075 print "Requesting HA migration for VM $vmid to node $target\n";
3077 PVE
::Tools
::run_command
($cmd);
3082 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3087 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3091 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3094 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3099 __PACKAGE__-
>register_method({
3101 path
=> '{vmid}/monitor',
3105 description
=> "Execute Qemu monitor commands.",
3107 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3108 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3111 additionalProperties
=> 0,
3113 node
=> get_standard_option
('pve-node'),
3114 vmid
=> get_standard_option
('pve-vmid'),
3117 description
=> "The monitor command.",
3121 returns
=> { type
=> 'string'},
3125 my $rpcenv = PVE
::RPCEnvironment
::get
();
3126 my $authuser = $rpcenv->get_user();
3129 my $command = shift;
3130 return $command =~ m/^\s*info(\s+|$)/
3131 || $command =~ m/^\s*help\s*$/;
3134 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3135 if !&$is_ro($param->{command
});
3137 my $vmid = $param->{vmid
};
3139 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3143 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3145 $res = "ERROR: $@" if $@;
3150 __PACKAGE__-
>register_method({
3151 name
=> 'resize_vm',
3152 path
=> '{vmid}/resize',
3156 description
=> "Extend volume size.",
3158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3161 additionalProperties
=> 0,
3163 node
=> get_standard_option
('pve-node'),
3164 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3165 skiplock
=> get_standard_option
('skiplock'),
3168 description
=> "The disk you want to resize.",
3169 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3173 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3174 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.",
3178 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3184 returns
=> { type
=> 'null'},
3188 my $rpcenv = PVE
::RPCEnvironment
::get
();
3190 my $authuser = $rpcenv->get_user();
3192 my $node = extract_param
($param, 'node');
3194 my $vmid = extract_param
($param, 'vmid');
3196 my $digest = extract_param
($param, 'digest');
3198 my $disk = extract_param
($param, 'disk');
3200 my $sizestr = extract_param
($param, 'size');
3202 my $skiplock = extract_param
($param, 'skiplock');
3203 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3204 if $skiplock && $authuser ne 'root@pam';
3206 my $storecfg = PVE
::Storage
::config
();
3208 my $updatefn = sub {
3210 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3212 die "checksum missmatch (file change by other user?)\n"
3213 if $digest && $digest ne $conf->{digest
};
3214 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3216 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3218 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3220 my (undef, undef, undef, undef, undef, undef, $format) =
3221 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3223 die "can't resize volume: $disk if snapshot exists\n"
3224 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3226 my $volid = $drive->{file
};
3228 die "disk '$disk' has no associated volume\n" if !$volid;
3230 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3232 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3234 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3236 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3237 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3239 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3240 my ($ext, $newsize, $unit) = ($1, $2, $4);
3243 $newsize = $newsize * 1024;
3244 } elsif ($unit eq 'M') {
3245 $newsize = $newsize * 1024 * 1024;
3246 } elsif ($unit eq 'G') {
3247 $newsize = $newsize * 1024 * 1024 * 1024;
3248 } elsif ($unit eq 'T') {
3249 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3252 $newsize += $size if $ext;
3253 $newsize = int($newsize);
3255 die "shrinking disks is not supported\n" if $newsize < $size;
3257 return if $size == $newsize;
3259 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3261 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3263 $drive->{size
} = $newsize;
3264 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3266 PVE
::QemuConfig-
>write_config($vmid, $conf);
3269 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3273 __PACKAGE__-
>register_method({
3274 name
=> 'snapshot_list',
3275 path
=> '{vmid}/snapshot',
3277 description
=> "List all snapshots.",
3279 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3282 protected
=> 1, # qemu pid files are only readable by root
3284 additionalProperties
=> 0,
3286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3287 node
=> get_standard_option
('pve-node'),
3296 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3301 my $vmid = $param->{vmid
};
3303 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3304 my $snaphash = $conf->{snapshots
} || {};
3308 foreach my $name (keys %$snaphash) {
3309 my $d = $snaphash->{$name};
3312 snaptime
=> $d->{snaptime
} || 0,
3313 vmstate
=> $d->{vmstate
} ?
1 : 0,
3314 description
=> $d->{description
} || '',
3316 $item->{parent
} = $d->{parent
} if $d->{parent
};
3317 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3321 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3322 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3323 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3325 push @$res, $current;
3330 __PACKAGE__-
>register_method({
3332 path
=> '{vmid}/snapshot',
3336 description
=> "Snapshot a VM.",
3338 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3341 additionalProperties
=> 0,
3343 node
=> get_standard_option
('pve-node'),
3344 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3345 snapname
=> get_standard_option
('pve-snapshot-name'),
3349 description
=> "Save the vmstate",
3354 description
=> "A textual description or comment.",
3360 description
=> "the task ID.",
3365 my $rpcenv = PVE
::RPCEnvironment
::get
();
3367 my $authuser = $rpcenv->get_user();
3369 my $node = extract_param
($param, 'node');
3371 my $vmid = extract_param
($param, 'vmid');
3373 my $snapname = extract_param
($param, 'snapname');
3375 die "unable to use snapshot name 'current' (reserved name)\n"
3376 if $snapname eq 'current';
3379 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3380 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3381 $param->{description
});
3384 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3387 __PACKAGE__-
>register_method({
3388 name
=> 'snapshot_cmd_idx',
3389 path
=> '{vmid}/snapshot/{snapname}',
3396 additionalProperties
=> 0,
3398 vmid
=> get_standard_option
('pve-vmid'),
3399 node
=> get_standard_option
('pve-node'),
3400 snapname
=> get_standard_option
('pve-snapshot-name'),
3409 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3416 push @$res, { cmd
=> 'rollback' };
3417 push @$res, { cmd
=> 'config' };
3422 __PACKAGE__-
>register_method({
3423 name
=> 'update_snapshot_config',
3424 path
=> '{vmid}/snapshot/{snapname}/config',
3428 description
=> "Update snapshot metadata.",
3430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3433 additionalProperties
=> 0,
3435 node
=> get_standard_option
('pve-node'),
3436 vmid
=> get_standard_option
('pve-vmid'),
3437 snapname
=> get_standard_option
('pve-snapshot-name'),
3441 description
=> "A textual description or comment.",
3445 returns
=> { type
=> 'null' },
3449 my $rpcenv = PVE
::RPCEnvironment
::get
();
3451 my $authuser = $rpcenv->get_user();
3453 my $vmid = extract_param
($param, 'vmid');
3455 my $snapname = extract_param
($param, 'snapname');
3457 return undef if !defined($param->{description
});
3459 my $updatefn = sub {
3461 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3463 PVE
::QemuConfig-
>check_lock($conf);
3465 my $snap = $conf->{snapshots
}->{$snapname};
3467 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3469 $snap->{description
} = $param->{description
} if defined($param->{description
});
3471 PVE
::QemuConfig-
>write_config($vmid, $conf);
3474 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3479 __PACKAGE__-
>register_method({
3480 name
=> 'get_snapshot_config',
3481 path
=> '{vmid}/snapshot/{snapname}/config',
3484 description
=> "Get snapshot configuration",
3486 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3489 additionalProperties
=> 0,
3491 node
=> get_standard_option
('pve-node'),
3492 vmid
=> get_standard_option
('pve-vmid'),
3493 snapname
=> get_standard_option
('pve-snapshot-name'),
3496 returns
=> { type
=> "object" },
3500 my $rpcenv = PVE
::RPCEnvironment
::get
();
3502 my $authuser = $rpcenv->get_user();
3504 my $vmid = extract_param
($param, 'vmid');
3506 my $snapname = extract_param
($param, 'snapname');
3508 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3510 my $snap = $conf->{snapshots
}->{$snapname};
3512 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3517 __PACKAGE__-
>register_method({
3519 path
=> '{vmid}/snapshot/{snapname}/rollback',
3523 description
=> "Rollback VM state to specified snapshot.",
3525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3528 additionalProperties
=> 0,
3530 node
=> get_standard_option
('pve-node'),
3531 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3532 snapname
=> get_standard_option
('pve-snapshot-name'),
3537 description
=> "the task ID.",
3542 my $rpcenv = PVE
::RPCEnvironment
::get
();
3544 my $authuser = $rpcenv->get_user();
3546 my $node = extract_param
($param, 'node');
3548 my $vmid = extract_param
($param, 'vmid');
3550 my $snapname = extract_param
($param, 'snapname');
3553 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3554 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3558 # hold migration lock, this makes sure that nobody create replication snapshots
3559 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3562 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3565 __PACKAGE__-
>register_method({
3566 name
=> 'delsnapshot',
3567 path
=> '{vmid}/snapshot/{snapname}',
3571 description
=> "Delete a VM snapshot.",
3573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3576 additionalProperties
=> 0,
3578 node
=> get_standard_option
('pve-node'),
3579 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3580 snapname
=> get_standard_option
('pve-snapshot-name'),
3584 description
=> "For removal from config file, even if removing disk snapshots fails.",
3590 description
=> "the task ID.",
3595 my $rpcenv = PVE
::RPCEnvironment
::get
();
3597 my $authuser = $rpcenv->get_user();
3599 my $node = extract_param
($param, 'node');
3601 my $vmid = extract_param
($param, 'vmid');
3603 my $snapname = extract_param
($param, 'snapname');
3606 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3607 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3610 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3613 __PACKAGE__-
>register_method({
3615 path
=> '{vmid}/template',
3619 description
=> "Create a Template.",
3621 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3622 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3625 additionalProperties
=> 0,
3627 node
=> get_standard_option
('pve-node'),
3628 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3632 description
=> "If you want to convert only 1 disk to base image.",
3633 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3638 returns
=> { type
=> 'null'},
3642 my $rpcenv = PVE
::RPCEnvironment
::get
();
3644 my $authuser = $rpcenv->get_user();
3646 my $node = extract_param
($param, 'node');
3648 my $vmid = extract_param
($param, 'vmid');
3650 my $disk = extract_param
($param, 'disk');
3652 my $updatefn = sub {
3654 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3656 PVE
::QemuConfig-
>check_lock($conf);
3658 die "unable to create template, because VM contains snapshots\n"
3659 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3661 die "you can't convert a template to a template\n"
3662 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3664 die "you can't convert a VM to template if VM is running\n"
3665 if PVE
::QemuServer
::check_running
($vmid);
3668 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3671 $conf->{template
} = 1;
3672 PVE
::QemuConfig-
>write_config($vmid, $conf);
3674 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3677 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);