1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 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.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
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
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_modify_config_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (@$key_list) {
338 # some checks (e.g., disk, serial port, usb) need to be done somewhere
339 # else, as there the permission can be value dependend
340 next if PVE
::QemuServer
::is_valid_drivename
($opt);
341 next if $opt eq 'cdrom';
342 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
345 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
347 } elsif ($memoryoptions->{$opt}) {
348 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
349 } elsif ($hwtypeoptions->{$opt}) {
350 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
351 } elsif ($generaloptions->{$opt}) {
352 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
353 # special case for startup since it changes host behaviour
354 if ($opt eq 'startup') {
355 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
357 } elsif ($vmpoweroptions->{$opt}) {
358 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
359 } elsif ($diskoptions->{$opt}) {
360 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
361 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
362 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
363 } elsif ($opt eq 'vmstate') {
364 # the user needs Disk and PowerMgmt privileges to change the vmstate
365 # also needs privileges on the storage, that will be checked later
366 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
368 # catches hostpci\d+, args, lock, etc.
369 # new options will be checked here
370 die "only root can set '$opt' config\n";
377 __PACKAGE__-
>register_method({
381 description
=> "Virtual machine index (per node).",
383 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
387 protected
=> 1, # qemu pid files are only readable by root
389 additionalProperties
=> 0,
391 node
=> get_standard_option
('pve-node'),
395 description
=> "Determine the full status of active VMs.",
403 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
405 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
410 my $rpcenv = PVE
::RPCEnvironment
::get
();
411 my $authuser = $rpcenv->get_user();
413 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
416 foreach my $vmid (keys %$vmstatus) {
417 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
419 my $data = $vmstatus->{$vmid};
426 my $parse_restore_archive = sub {
427 my ($storecfg, $archive) = @_;
429 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
431 if (defined($archive_storeid)) {
432 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
433 if ($scfg->{type
} eq 'pbs') {
440 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
448 __PACKAGE__-
>register_method({
452 description
=> "Create or restore a virtual machine.",
454 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
455 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
456 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
457 user
=> 'all', # check inside
462 additionalProperties
=> 0,
463 properties
=> PVE
::QemuServer
::json_config_properties
(
465 node
=> get_standard_option
('pve-node'),
466 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
468 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
472 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
474 storage
=> get_standard_option
('pve-storage-id', {
475 description
=> "Default storage.",
477 completion
=> \
&PVE
::QemuServer
::complete_storage
,
482 description
=> "Allow to overwrite existing VM.",
483 requires
=> 'archive',
488 description
=> "Assign a unique random ethernet address.",
489 requires
=> 'archive',
493 type
=> 'string', format
=> 'pve-poolid',
494 description
=> "Add the VM to the specified pool.",
497 description
=> "Override I/O bandwidth limit (in KiB/s).",
501 default => 'restore limit from datacenter or storage config',
507 description
=> "Start VM after it was created successfully.",
517 my $rpcenv = PVE
::RPCEnvironment
::get
();
518 my $authuser = $rpcenv->get_user();
520 my $node = extract_param
($param, 'node');
521 my $vmid = extract_param
($param, 'vmid');
523 my $archive = extract_param
($param, 'archive');
524 my $is_restore = !!$archive;
526 my $bwlimit = extract_param
($param, 'bwlimit');
527 my $force = extract_param
($param, 'force');
528 my $pool = extract_param
($param, 'pool');
529 my $start_after_create = extract_param
($param, 'start');
530 my $storage = extract_param
($param, 'storage');
531 my $unique = extract_param
($param, 'unique');
533 if (defined(my $ssh_keys = $param->{sshkeys
})) {
534 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
535 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
538 PVE
::Cluster
::check_cfs_quorum
();
540 my $filename = PVE
::QemuConfig-
>config_file($vmid);
541 my $storecfg = PVE
::Storage
::config
();
543 if (defined($pool)) {
544 $rpcenv->check_pool_exist($pool);
547 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
548 if defined($storage);
550 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
552 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
554 } elsif ($archive && $force && (-f
$filename) &&
555 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
556 # OK: user has VM.Backup permissions, and want to restore an existing VM
562 &$resolve_cdrom_alias($param);
564 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
566 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
568 &$check_cpu_model_access($rpcenv, $authuser, $param);
570 foreach my $opt (keys %$param) {
571 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
572 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
573 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
575 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
576 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
580 PVE
::QemuServer
::add_random_macs
($param);
582 my $keystr = join(' ', keys %$param);
583 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
585 if ($archive eq '-') {
586 die "pipe requires cli environment\n"
587 if $rpcenv->{type
} ne 'cli';
588 $archive = { type
=> 'pipe' };
590 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
592 $archive = $parse_restore_archive->($storecfg, $archive);
596 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
598 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
599 die "$emsg $@" if $@;
601 my $restorefn = sub {
602 my $conf = PVE
::QemuConfig-
>load_config($vmid);
604 PVE
::QemuConfig-
>check_protection($conf, $emsg);
606 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
609 my $restore_options = {
615 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
616 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
617 } elsif ($archive->{type
} eq 'pbs') {
618 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
620 die "unknown backup archive type\n";
622 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
623 # Convert restored VM to template if backup was VM template
624 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
625 warn "Convert to template.\n";
626 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
630 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
633 # ensure no old replication state are exists
634 PVE
::ReplicationState
::delete_guest_states
($vmid);
636 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
638 if ($start_after_create) {
639 print "Execute autostart\n";
640 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
646 # ensure no old replication state are exists
647 PVE
::ReplicationState
::delete_guest_states
($vmid);
651 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
655 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
657 if (!$conf->{bootdisk
}) {
658 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
659 $conf->{bootdisk
} = $firstdisk if $firstdisk;
662 # auto generate uuid if user did not specify smbios1 option
663 if (!$conf->{smbios1
}) {
664 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
667 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
668 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
671 PVE
::QemuConfig-
>write_config($vmid, $conf);
677 foreach my $volid (@$vollist) {
678 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
684 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
687 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
689 if ($start_after_create) {
690 print "Execute autostart\n";
691 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
696 my ($code, $worker_name);
698 $worker_name = 'qmrestore';
700 eval { $restorefn->() };
702 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
708 $worker_name = 'qmcreate';
710 eval { $createfn->() };
713 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
714 unlink($conffile) or die "failed to remove config file: $!\n";
722 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
725 __PACKAGE__-
>register_method({
730 description
=> "Directory index",
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
738 vmid
=> get_standard_option
('pve-vmid'),
746 subdir
=> { type
=> 'string' },
749 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
755 { subdir
=> 'config' },
756 { subdir
=> 'pending' },
757 { subdir
=> 'status' },
758 { subdir
=> 'unlink' },
759 { subdir
=> 'vncproxy' },
760 { subdir
=> 'termproxy' },
761 { subdir
=> 'migrate' },
762 { subdir
=> 'resize' },
763 { subdir
=> 'move' },
765 { subdir
=> 'rrddata' },
766 { subdir
=> 'monitor' },
767 { subdir
=> 'agent' },
768 { subdir
=> 'snapshot' },
769 { subdir
=> 'spiceproxy' },
770 { subdir
=> 'sendkey' },
771 { subdir
=> 'firewall' },
777 __PACKAGE__-
>register_method ({
778 subclass
=> "PVE::API2::Firewall::VM",
779 path
=> '{vmid}/firewall',
782 __PACKAGE__-
>register_method ({
783 subclass
=> "PVE::API2::Qemu::Agent",
784 path
=> '{vmid}/agent',
787 __PACKAGE__-
>register_method({
789 path
=> '{vmid}/rrd',
791 protected
=> 1, # fixme: can we avoid that?
793 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
795 description
=> "Read VM RRD statistics (returns PNG)",
797 additionalProperties
=> 0,
799 node
=> get_standard_option
('pve-node'),
800 vmid
=> get_standard_option
('pve-vmid'),
802 description
=> "Specify the time frame you are interested in.",
804 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
807 description
=> "The list of datasources you want to display.",
808 type
=> 'string', format
=> 'pve-configid-list',
811 description
=> "The RRD consolidation function",
813 enum
=> [ 'AVERAGE', 'MAX' ],
821 filename
=> { type
=> 'string' },
827 return PVE
::RRD
::create_rrd_graph
(
828 "pve2-vm/$param->{vmid}", $param->{timeframe
},
829 $param->{ds
}, $param->{cf
});
833 __PACKAGE__-
>register_method({
835 path
=> '{vmid}/rrddata',
837 protected
=> 1, # fixme: can we avoid that?
839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
841 description
=> "Read VM RRD statistics",
843 additionalProperties
=> 0,
845 node
=> get_standard_option
('pve-node'),
846 vmid
=> get_standard_option
('pve-vmid'),
848 description
=> "Specify the time frame you are interested in.",
850 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
853 description
=> "The RRD consolidation function",
855 enum
=> [ 'AVERAGE', 'MAX' ],
870 return PVE
::RRD
::create_rrd_data
(
871 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
875 __PACKAGE__-
>register_method({
877 path
=> '{vmid}/config',
880 description
=> "Get the virtual machine configuration with pending configuration " .
881 "changes applied. Set the 'current' parameter to get the current configuration instead.",
883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
886 additionalProperties
=> 0,
888 node
=> get_standard_option
('pve-node'),
889 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
891 description
=> "Get current values (instead of pending values).",
896 snapshot
=> get_standard_option
('pve-snapshot-name', {
897 description
=> "Fetch config values from given snapshot.",
900 my ($cmd, $pname, $cur, $args) = @_;
901 PVE
::QemuConfig-
>snapshot_list($args->[0]);
907 description
=> "The VM configuration.",
909 properties
=> PVE
::QemuServer
::json_config_properties
({
912 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
919 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
920 current
=> "cannot use 'snapshot' parameter with 'current'"})
921 if ($param->{snapshot
} && $param->{current
});
924 if ($param->{snapshot
}) {
925 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
927 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
929 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
934 __PACKAGE__-
>register_method({
935 name
=> 'vm_pending',
936 path
=> '{vmid}/pending',
939 description
=> "Get the virtual machine configuration with both current and pending values.",
941 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
944 additionalProperties
=> 0,
946 node
=> get_standard_option
('pve-node'),
947 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
956 description
=> "Configuration option name.",
960 description
=> "Current value.",
965 description
=> "Pending value.",
970 description
=> "Indicates a pending delete request if present and not 0. " .
971 "The value 2 indicates a force-delete request.",
983 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
985 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
987 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
988 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
990 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
993 # POST/PUT {vmid}/config implementation
995 # The original API used PUT (idempotent) an we assumed that all operations
996 # are fast. But it turned out that almost any configuration change can
997 # involve hot-plug actions, or disk alloc/free. Such actions can take long
998 # time to complete and have side effects (not idempotent).
1000 # The new implementation uses POST and forks a worker process. We added
1001 # a new option 'background_delay'. If specified we wait up to
1002 # 'background_delay' second for the worker task to complete. It returns null
1003 # if the task is finished within that time, else we return the UPID.
1005 my $update_vm_api = sub {
1006 my ($param, $sync) = @_;
1008 my $rpcenv = PVE
::RPCEnvironment
::get
();
1010 my $authuser = $rpcenv->get_user();
1012 my $node = extract_param
($param, 'node');
1014 my $vmid = extract_param
($param, 'vmid');
1016 my $digest = extract_param
($param, 'digest');
1018 my $background_delay = extract_param
($param, 'background_delay');
1020 if (defined(my $cipassword = $param->{cipassword
})) {
1021 # Same logic as in cloud-init (but with the regex fixed...)
1022 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1023 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1026 my @paramarr = (); # used for log message
1027 foreach my $key (sort keys %$param) {
1028 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1029 push @paramarr, "-$key", $value;
1032 my $skiplock = extract_param
($param, 'skiplock');
1033 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1034 if $skiplock && $authuser ne 'root@pam';
1036 my $delete_str = extract_param
($param, 'delete');
1038 my $revert_str = extract_param
($param, 'revert');
1040 my $force = extract_param
($param, 'force');
1042 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1043 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1044 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1047 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1049 my $storecfg = PVE
::Storage
::config
();
1051 my $defaults = PVE
::QemuServer
::load_defaults
();
1053 &$resolve_cdrom_alias($param);
1055 # now try to verify all parameters
1058 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1059 if (!PVE
::QemuServer
::option_exists
($opt)) {
1060 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1063 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1064 "-revert $opt' at the same time" })
1065 if defined($param->{$opt});
1067 $revert->{$opt} = 1;
1071 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1072 $opt = 'ide2' if $opt eq 'cdrom';
1074 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1075 "-delete $opt' at the same time" })
1076 if defined($param->{$opt});
1078 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1079 "-revert $opt' at the same time" })
1082 if (!PVE
::QemuServer
::option_exists
($opt)) {
1083 raise_param_exc
({ delete => "unknown option '$opt'" });
1089 my $repl_conf = PVE
::ReplicationConfig-
>new();
1090 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1091 my $check_replication = sub {
1093 return if !$is_replicated;
1094 my $volid = $drive->{file
};
1095 return if !$volid || !($drive->{replicate
}//1);
1096 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1098 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1099 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1100 if !defined($storeid);
1102 return if defined($volname) && $volname eq 'cloudinit';
1105 if ($volid =~ $NEW_DISK_RE) {
1107 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1109 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1111 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1113 return if $scfg->{shared
};
1114 die "cannot add non-replicatable volume to a replicated VM\n";
1117 foreach my $opt (keys %$param) {
1118 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1119 # cleanup drive path
1120 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1121 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1123 $check_replication->($drive);
1124 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1125 } elsif ($opt =~ m/^net(\d+)$/) {
1127 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1128 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1129 } elsif ($opt eq 'vmgenid') {
1130 if ($param->{$opt} eq '1') {
1131 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1133 } elsif ($opt eq 'hookscript') {
1134 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1135 raise_param_exc
({ $opt => $@ }) if $@;
1139 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1143 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1145 my $updatefn = sub {
1147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1149 die "checksum missmatch (file change by other user?)\n"
1150 if $digest && $digest ne $conf->{digest
};
1152 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1154 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1155 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1156 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1157 delete $conf->{lock}; # for check lock check, not written out
1158 push @delete, 'lock'; # this is the real deal to write it out
1160 push @delete, 'runningmachine' if $conf->{runningmachine
};
1161 push @delete, 'runningcpu' if $conf->{runningcpu
};
1164 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1166 foreach my $opt (keys %$revert) {
1167 if (defined($conf->{$opt})) {
1168 $param->{$opt} = $conf->{$opt};
1169 } elsif (defined($conf->{pending
}->{$opt})) {
1174 if ($param->{memory
} || defined($param->{balloon
})) {
1175 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1176 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1178 die "balloon value too large (must be smaller than assigned memory)\n"
1179 if $balloon && $balloon > $maxmem;
1182 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1186 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1188 # write updates to pending section
1190 my $modified = {}; # record what $option we modify
1192 foreach my $opt (@delete) {
1193 $modified->{$opt} = 1;
1194 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1196 # value of what we want to delete, independent if pending or not
1197 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1198 if (!defined($val)) {
1199 warn "cannot delete '$opt' - not set in current configuration!\n";
1200 $modified->{$opt} = 0;
1203 my $is_pending_val = defined($conf->{pending
}->{$opt});
1204 delete $conf->{pending
}->{$opt};
1206 if ($opt =~ m/^unused/) {
1207 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1208 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1210 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1211 delete $conf->{$opt};
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 } elsif ($opt eq 'vmstate') {
1215 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1216 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1217 delete $conf->{$opt};
1218 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1221 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1222 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1223 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1225 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1226 PVE
::QemuConfig-
>write_config($vmid, $conf);
1227 } elsif ($opt =~ m/^serial\d+$/) {
1228 if ($val eq 'socket') {
1229 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1230 } elsif ($authuser ne 'root@pam') {
1231 die "only root can delete '$opt' config for real devices\n";
1233 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1234 PVE
::QemuConfig-
>write_config($vmid, $conf);
1235 } elsif ($opt =~ m/^usb\d+$/) {
1236 if ($val =~ m/spice/) {
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1238 } elsif ($authuser ne 'root@pam') {
1239 die "only root can delete '$opt' config for real devices\n";
1241 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1242 PVE
::QemuConfig-
>write_config($vmid, $conf);
1244 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1245 PVE
::QemuConfig-
>write_config($vmid, $conf);
1249 foreach my $opt (keys %$param) { # add/change
1250 $modified->{$opt} = 1;
1251 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1252 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1254 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1256 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1257 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1258 # FIXME: cloudinit: CDROM or Disk?
1259 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1260 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1262 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1264 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1265 if defined($conf->{pending
}->{$opt});
1267 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1268 } elsif ($opt =~ m/^serial\d+/) {
1269 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1270 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1271 } elsif ($authuser ne 'root@pam') {
1272 die "only root can modify '$opt' config for real devices\n";
1274 $conf->{pending
}->{$opt} = $param->{$opt};
1275 } elsif ($opt =~ m/^usb\d+/) {
1276 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1277 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1278 } elsif ($authuser ne 'root@pam') {
1279 die "only root can modify '$opt' config for real devices\n";
1281 $conf->{pending
}->{$opt} = $param->{$opt};
1283 $conf->{pending
}->{$opt} = $param->{$opt};
1285 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1286 PVE
::QemuConfig-
>write_config($vmid, $conf);
1289 # remove pending changes when nothing changed
1290 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1291 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1292 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1294 return if !scalar(keys %{$conf->{pending
}});
1296 my $running = PVE
::QemuServer
::check_running
($vmid);
1298 # apply pending changes
1300 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1304 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1306 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1308 raise_param_exc
($errors) if scalar(keys %$errors);
1317 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1319 if ($background_delay) {
1321 # Note: It would be better to do that in the Event based HTTPServer
1322 # to avoid blocking call to sleep.
1324 my $end_time = time() + $background_delay;
1326 my $task = PVE
::Tools
::upid_decode
($upid);
1329 while (time() < $end_time) {
1330 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1332 sleep(1); # this gets interrupted when child process ends
1336 my $status = PVE
::Tools
::upid_read_status
($upid);
1337 return undef if $status eq 'OK';
1346 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1349 my $vm_config_perm_list = [
1354 'VM.Config.Network',
1356 'VM.Config.Options',
1359 __PACKAGE__-
>register_method({
1360 name
=> 'update_vm_async',
1361 path
=> '{vmid}/config',
1365 description
=> "Set virtual machine options (asynchrounous API).",
1367 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1370 additionalProperties
=> 0,
1371 properties
=> PVE
::QemuServer
::json_config_properties
(
1373 node
=> get_standard_option
('pve-node'),
1374 vmid
=> get_standard_option
('pve-vmid'),
1375 skiplock
=> get_standard_option
('skiplock'),
1377 type
=> 'string', format
=> 'pve-configid-list',
1378 description
=> "A list of settings you want to delete.",
1382 type
=> 'string', format
=> 'pve-configid-list',
1383 description
=> "Revert a pending change.",
1388 description
=> $opt_force_description,
1390 requires
=> 'delete',
1394 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1398 background_delay
=> {
1400 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1411 code
=> $update_vm_api,
1414 __PACKAGE__-
>register_method({
1415 name
=> 'update_vm',
1416 path
=> '{vmid}/config',
1420 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1422 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1425 additionalProperties
=> 0,
1426 properties
=> PVE
::QemuServer
::json_config_properties
(
1428 node
=> get_standard_option
('pve-node'),
1429 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1430 skiplock
=> get_standard_option
('skiplock'),
1432 type
=> 'string', format
=> 'pve-configid-list',
1433 description
=> "A list of settings you want to delete.",
1437 type
=> 'string', format
=> 'pve-configid-list',
1438 description
=> "Revert a pending change.",
1443 description
=> $opt_force_description,
1445 requires
=> 'delete',
1449 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1455 returns
=> { type
=> 'null' },
1458 &$update_vm_api($param, 1);
1463 __PACKAGE__-
>register_method({
1464 name
=> 'destroy_vm',
1469 description
=> "Destroy the vm (also delete all used/owned volumes).",
1471 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1478 skiplock
=> get_standard_option
('skiplock'),
1481 description
=> "Remove vmid from backup cron jobs.",
1492 my $rpcenv = PVE
::RPCEnvironment
::get
();
1493 my $authuser = $rpcenv->get_user();
1494 my $vmid = $param->{vmid
};
1496 my $skiplock = $param->{skiplock
};
1497 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1498 if $skiplock && $authuser ne 'root@pam';
1500 my $early_checks = sub {
1502 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1503 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1505 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1507 if (!$param->{purge
}) {
1508 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1510 # don't allow destroy if with replication jobs but no purge param
1511 my $repl_conf = PVE
::ReplicationConfig-
>new();
1512 $repl_conf->check_for_existing_jobs($vmid);
1515 die "VM $vmid is running - destroy failed\n"
1516 if PVE
::QemuServer
::check_running
($vmid);
1526 my $storecfg = PVE
::Storage
::config
();
1528 syslog
('info', "destroy VM $vmid: $upid\n");
1529 PVE
::QemuConfig-
>lock_config($vmid, sub {
1530 # repeat, config might have changed
1531 my $ha_managed = $early_checks->();
1533 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1535 PVE
::AccessControl
::remove_vm_access
($vmid);
1536 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1537 if ($param->{purge
}) {
1538 print "purging VM $vmid from related configurations..\n";
1539 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1540 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1543 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1544 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1548 # only now remove the zombie config, else we can have reuse race
1549 PVE
::QemuConfig-
>destroy_config($vmid);
1553 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1556 __PACKAGE__-
>register_method({
1558 path
=> '{vmid}/unlink',
1562 description
=> "Unlink/delete disk images.",
1564 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1567 additionalProperties
=> 0,
1569 node
=> get_standard_option
('pve-node'),
1570 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1572 type
=> 'string', format
=> 'pve-configid-list',
1573 description
=> "A list of disk IDs you want to delete.",
1577 description
=> $opt_force_description,
1582 returns
=> { type
=> 'null'},
1586 $param->{delete} = extract_param
($param, 'idlist');
1588 __PACKAGE__-
>update_vm($param);
1593 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1594 my $gen_rand_chars = sub {
1597 die "invalid length $length" if $length < 1;
1599 my $min = ord('!'); # first printable ascii
1601 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1602 die "failed to generate random bytes!\n"
1605 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1612 __PACKAGE__-
>register_method({
1614 path
=> '{vmid}/vncproxy',
1618 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1620 description
=> "Creates a TCP VNC proxy connections.",
1622 additionalProperties
=> 0,
1624 node
=> get_standard_option
('pve-node'),
1625 vmid
=> get_standard_option
('pve-vmid'),
1629 description
=> "starts websockify instead of vncproxy",
1631 'generate-password' => {
1635 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1640 additionalProperties
=> 0,
1642 user
=> { type
=> 'string' },
1643 ticket
=> { type
=> 'string' },
1646 description
=> "Returned if requested with 'generate-password' param."
1647 ." Consists of printable ASCII characters ('!' .. '~').",
1650 cert
=> { type
=> 'string' },
1651 port
=> { type
=> 'integer' },
1652 upid
=> { type
=> 'string' },
1658 my $rpcenv = PVE
::RPCEnvironment
::get
();
1660 my $authuser = $rpcenv->get_user();
1662 my $vmid = $param->{vmid
};
1663 my $node = $param->{node
};
1664 my $websocket = $param->{websocket
};
1666 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1670 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1671 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1674 my $authpath = "/vms/$vmid";
1676 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1677 my $password = $ticket;
1678 if ($param->{'generate-password'}) {
1679 $password = $gen_rand_chars->(8);
1682 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1688 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1689 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1690 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1691 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1692 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1694 $family = PVE
::Tools
::get_host_address_family
($node);
1697 my $port = PVE
::Tools
::next_vnc_port
($family);
1704 syslog
('info', "starting vnc proxy $upid\n");
1708 if (defined($serial)) {
1710 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1712 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1713 '-timeout', $timeout, '-authpath', $authpath,
1714 '-perm', 'Sys.Console'];
1716 if ($param->{websocket
}) {
1717 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1718 push @$cmd, '-notls', '-listen', 'localhost';
1721 push @$cmd, '-c', @$remcmd, @$termcmd;
1723 PVE
::Tools
::run_command
($cmd);
1727 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1729 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1731 my $sock = IO
::Socket
::IP-
>new(
1736 GetAddrInfoFlags
=> 0,
1737 ) or die "failed to create socket: $!\n";
1738 # Inside the worker we shouldn't have any previous alarms
1739 # running anyway...:
1741 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1743 accept(my $cli, $sock) or die "connection failed: $!\n";
1746 if (PVE
::Tools
::run_command
($cmd,
1747 output
=> '>&'.fileno($cli),
1748 input
=> '<&'.fileno($cli),
1751 die "Failed to run vncproxy.\n";
1758 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1760 PVE
::Tools
::wait_for_vnc_port
($port);
1769 $res->{password
} = $password if $param->{'generate-password'};
1774 __PACKAGE__-
>register_method({
1775 name
=> 'termproxy',
1776 path
=> '{vmid}/termproxy',
1780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1782 description
=> "Creates a TCP proxy connections.",
1784 additionalProperties
=> 0,
1786 node
=> get_standard_option
('pve-node'),
1787 vmid
=> get_standard_option
('pve-vmid'),
1791 enum
=> [qw(serial0 serial1 serial2 serial3)],
1792 description
=> "opens a serial terminal (defaults to display)",
1797 additionalProperties
=> 0,
1799 user
=> { type
=> 'string' },
1800 ticket
=> { type
=> 'string' },
1801 port
=> { type
=> 'integer' },
1802 upid
=> { type
=> 'string' },
1808 my $rpcenv = PVE
::RPCEnvironment
::get
();
1810 my $authuser = $rpcenv->get_user();
1812 my $vmid = $param->{vmid
};
1813 my $node = $param->{node
};
1814 my $serial = $param->{serial
};
1816 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1818 if (!defined($serial)) {
1820 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1821 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1825 my $authpath = "/vms/$vmid";
1827 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1832 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1833 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1834 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1835 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1836 push @$remcmd, '--';
1838 $family = PVE
::Tools
::get_host_address_family
($node);
1841 my $port = PVE
::Tools
::next_vnc_port
($family);
1843 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1844 push @$termcmd, '-iface', $serial if $serial;
1849 syslog
('info', "starting qemu termproxy $upid\n");
1851 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1852 '--perm', 'VM.Console', '--'];
1853 push @$cmd, @$remcmd, @$termcmd;
1855 PVE
::Tools
::run_command
($cmd);
1858 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1860 PVE
::Tools
::wait_for_vnc_port
($port);
1870 __PACKAGE__-
>register_method({
1871 name
=> 'vncwebsocket',
1872 path
=> '{vmid}/vncwebsocket',
1875 description
=> "You also need to pass a valid ticket (vncticket).",
1876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1878 description
=> "Opens a weksocket for VNC traffic.",
1880 additionalProperties
=> 0,
1882 node
=> get_standard_option
('pve-node'),
1883 vmid
=> get_standard_option
('pve-vmid'),
1885 description
=> "Ticket from previous call to vncproxy.",
1890 description
=> "Port number returned by previous vncproxy call.",
1900 port
=> { type
=> 'string' },
1906 my $rpcenv = PVE
::RPCEnvironment
::get
();
1908 my $authuser = $rpcenv->get_user();
1910 my $vmid = $param->{vmid
};
1911 my $node = $param->{node
};
1913 my $authpath = "/vms/$vmid";
1915 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1917 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1919 # Note: VNC ports are acessible from outside, so we do not gain any
1920 # security if we verify that $param->{port} belongs to VM $vmid. This
1921 # check is done by verifying the VNC ticket (inside VNC protocol).
1923 my $port = $param->{port
};
1925 return { port
=> $port };
1928 __PACKAGE__-
>register_method({
1929 name
=> 'spiceproxy',
1930 path
=> '{vmid}/spiceproxy',
1935 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1937 description
=> "Returns a SPICE configuration to connect to the VM.",
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1943 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1946 returns
=> get_standard_option
('remote-viewer-config'),
1950 my $rpcenv = PVE
::RPCEnvironment
::get
();
1952 my $authuser = $rpcenv->get_user();
1954 my $vmid = $param->{vmid
};
1955 my $node = $param->{node
};
1956 my $proxy = $param->{proxy
};
1958 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1959 my $title = "VM $vmid";
1960 $title .= " - ". $conf->{name
} if $conf->{name
};
1962 my $port = PVE
::QemuServer
::spice_port
($vmid);
1964 my ($ticket, undef, $remote_viewer_config) =
1965 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1967 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1968 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1970 return $remote_viewer_config;
1973 __PACKAGE__-
>register_method({
1975 path
=> '{vmid}/status',
1978 description
=> "Directory index",
1983 additionalProperties
=> 0,
1985 node
=> get_standard_option
('pve-node'),
1986 vmid
=> get_standard_option
('pve-vmid'),
1994 subdir
=> { type
=> 'string' },
1997 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2003 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2006 { subdir
=> 'current' },
2007 { subdir
=> 'start' },
2008 { subdir
=> 'stop' },
2009 { subdir
=> 'reset' },
2010 { subdir
=> 'shutdown' },
2011 { subdir
=> 'suspend' },
2012 { subdir
=> 'reboot' },
2018 __PACKAGE__-
>register_method({
2019 name
=> 'vm_status',
2020 path
=> '{vmid}/status/current',
2023 protected
=> 1, # qemu pid files are only readable by root
2024 description
=> "Get virtual machine status.",
2026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2029 additionalProperties
=> 0,
2031 node
=> get_standard_option
('pve-node'),
2032 vmid
=> get_standard_option
('pve-vmid'),
2038 %$PVE::QemuServer
::vmstatus_return_properties
,
2040 description
=> "HA manager service status.",
2044 description
=> "Qemu VGA configuration supports spice.",
2049 description
=> "Qemu GuestAgent enabled in config.",
2059 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2061 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2062 my $status = $vmstatus->{$param->{vmid
}};
2064 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2066 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2067 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2072 __PACKAGE__-
>register_method({
2074 path
=> '{vmid}/status/start',
2078 description
=> "Start virtual machine.",
2080 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2083 additionalProperties
=> 0,
2085 node
=> get_standard_option
('pve-node'),
2086 vmid
=> get_standard_option
('pve-vmid',
2087 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2088 skiplock
=> get_standard_option
('skiplock'),
2089 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2090 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2093 enum
=> ['secure', 'insecure'],
2094 description
=> "Migration traffic is encrypted using an SSH " .
2095 "tunnel by default. On secure, completely private networks " .
2096 "this can be disabled to increase performance.",
2099 migration_network
=> {
2100 type
=> 'string', format
=> 'CIDR',
2101 description
=> "CIDR of the (sub) network that is used for migration.",
2104 machine
=> get_standard_option
('pve-qemu-machine'),
2106 description
=> "Override QEMU's -cpu argument with the given string.",
2110 targetstorage
=> get_standard_option
('pve-targetstorage'),
2112 description
=> "Wait maximal timeout seconds.",
2115 default => 'max(30, vm memory in GiB)',
2126 my $rpcenv = PVE
::RPCEnvironment
::get
();
2127 my $authuser = $rpcenv->get_user();
2129 my $node = extract_param
($param, 'node');
2130 my $vmid = extract_param
($param, 'vmid');
2131 my $timeout = extract_param
($param, 'timeout');
2133 my $machine = extract_param
($param, 'machine');
2134 my $force_cpu = extract_param
($param, 'force-cpu');
2136 my $get_root_param = sub {
2137 my $value = extract_param
($param, $_[0]);
2138 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2139 if $value && $authuser ne 'root@pam';
2143 my $stateuri = $get_root_param->('stateuri');
2144 my $skiplock = $get_root_param->('skiplock');
2145 my $migratedfrom = $get_root_param->('migratedfrom');
2146 my $migration_type = $get_root_param->('migration_type');
2147 my $migration_network = $get_root_param->('migration_network');
2148 my $targetstorage = $get_root_param->('targetstorage');
2152 if ($targetstorage) {
2153 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2155 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2156 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2160 # read spice ticket from STDIN
2162 my $nbd_protocol_version = 0;
2163 my $replicated_volumes = {};
2164 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2165 while (defined(my $line = <STDIN
>)) {
2167 if ($line =~ m/^spice_ticket: (.+)$/) {
2169 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2170 $nbd_protocol_version = $1;
2171 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2172 $replicated_volumes->{$1} = 1;
2174 # fallback for old source node
2175 $spice_ticket = $line;
2180 PVE
::Cluster
::check_cfs_quorum
();
2182 my $storecfg = PVE
::Storage
::config
();
2184 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2188 print "Requesting HA start for VM $vmid\n";
2190 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2191 PVE
::Tools
::run_command
($cmd);
2195 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2202 syslog
('info', "start VM $vmid: $upid\n");
2204 my $migrate_opts = {
2205 migratedfrom
=> $migratedfrom,
2206 spice_ticket
=> $spice_ticket,
2207 network
=> $migration_network,
2208 type
=> $migration_type,
2209 storagemap
=> $storagemap,
2210 nbd_proto_version
=> $nbd_protocol_version,
2211 replicated_volumes
=> $replicated_volumes,
2215 statefile
=> $stateuri,
2216 skiplock
=> $skiplock,
2217 forcemachine
=> $machine,
2218 timeout
=> $timeout,
2219 forcecpu
=> $force_cpu,
2222 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2226 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2230 __PACKAGE__-
>register_method({
2232 path
=> '{vmid}/status/stop',
2236 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2237 "is akin to pulling the power plug of a running computer and may damage the VM data",
2239 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2242 additionalProperties
=> 0,
2244 node
=> get_standard_option
('pve-node'),
2245 vmid
=> get_standard_option
('pve-vmid',
2246 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2247 skiplock
=> get_standard_option
('skiplock'),
2248 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2250 description
=> "Wait maximal timeout seconds.",
2256 description
=> "Do not deactivate storage volumes.",
2269 my $rpcenv = PVE
::RPCEnvironment
::get
();
2270 my $authuser = $rpcenv->get_user();
2272 my $node = extract_param
($param, 'node');
2273 my $vmid = extract_param
($param, 'vmid');
2275 my $skiplock = extract_param
($param, 'skiplock');
2276 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2277 if $skiplock && $authuser ne 'root@pam';
2279 my $keepActive = extract_param
($param, 'keepActive');
2280 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2281 if $keepActive && $authuser ne 'root@pam';
2283 my $migratedfrom = extract_param
($param, 'migratedfrom');
2284 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2285 if $migratedfrom && $authuser ne 'root@pam';
2288 my $storecfg = PVE
::Storage
::config
();
2290 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2295 print "Requesting HA stop for VM $vmid\n";
2297 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2298 PVE
::Tools
::run_command
($cmd);
2302 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2308 syslog
('info', "stop VM $vmid: $upid\n");
2310 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2311 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2315 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2319 __PACKAGE__-
>register_method({
2321 path
=> '{vmid}/status/reset',
2325 description
=> "Reset virtual machine.",
2327 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2330 additionalProperties
=> 0,
2332 node
=> get_standard_option
('pve-node'),
2333 vmid
=> get_standard_option
('pve-vmid',
2334 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2335 skiplock
=> get_standard_option
('skiplock'),
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $skiplock = extract_param
($param, 'skiplock');
2353 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2354 if $skiplock && $authuser ne 'root@pam';
2356 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2361 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2366 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2369 __PACKAGE__-
>register_method({
2370 name
=> 'vm_shutdown',
2371 path
=> '{vmid}/status/shutdown',
2375 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2376 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2378 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2381 additionalProperties
=> 0,
2383 node
=> get_standard_option
('pve-node'),
2384 vmid
=> get_standard_option
('pve-vmid',
2385 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2386 skiplock
=> get_standard_option
('skiplock'),
2388 description
=> "Wait maximal timeout seconds.",
2394 description
=> "Make sure the VM stops.",
2400 description
=> "Do not deactivate storage volumes.",
2413 my $rpcenv = PVE
::RPCEnvironment
::get
();
2414 my $authuser = $rpcenv->get_user();
2416 my $node = extract_param
($param, 'node');
2417 my $vmid = extract_param
($param, 'vmid');
2419 my $skiplock = extract_param
($param, 'skiplock');
2420 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2421 if $skiplock && $authuser ne 'root@pam';
2423 my $keepActive = extract_param
($param, 'keepActive');
2424 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2425 if $keepActive && $authuser ne 'root@pam';
2427 my $storecfg = PVE
::Storage
::config
();
2431 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2432 # otherwise, we will infer a shutdown command, but run into the timeout,
2433 # then when the vm is resumed, it will instantly shutdown
2435 # checking the qmp status here to get feedback to the gui/cli/api
2436 # and the status query should not take too long
2437 my $qmpstatus = eval {
2438 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2439 mon_cmd
($vmid, "query-status");
2443 if (!$err && $qmpstatus->{status
} eq "paused") {
2444 if ($param->{forceStop
}) {
2445 warn "VM is paused - stop instead of shutdown\n";
2448 die "VM is paused - cannot shutdown\n";
2452 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2454 my $timeout = $param->{timeout
} // 60;
2458 print "Requesting HA stop for VM $vmid\n";
2460 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2461 PVE
::Tools
::run_command
($cmd);
2465 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2472 syslog
('info', "shutdown VM $vmid: $upid\n");
2474 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2475 $shutdown, $param->{forceStop
}, $keepActive);
2479 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2483 __PACKAGE__-
>register_method({
2484 name
=> 'vm_reboot',
2485 path
=> '{vmid}/status/reboot',
2489 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2491 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2494 additionalProperties
=> 0,
2496 node
=> get_standard_option
('pve-node'),
2497 vmid
=> get_standard_option
('pve-vmid',
2498 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2500 description
=> "Wait maximal timeout seconds for the shutdown.",
2513 my $rpcenv = PVE
::RPCEnvironment
::get
();
2514 my $authuser = $rpcenv->get_user();
2516 my $node = extract_param
($param, 'node');
2517 my $vmid = extract_param
($param, 'vmid');
2519 my $qmpstatus = eval {
2520 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2521 mon_cmd
($vmid, "query-status");
2525 if (!$err && $qmpstatus->{status
} eq "paused") {
2526 die "VM is paused - cannot shutdown\n";
2529 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2534 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2535 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2539 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2542 __PACKAGE__-
>register_method({
2543 name
=> 'vm_suspend',
2544 path
=> '{vmid}/status/suspend',
2548 description
=> "Suspend virtual machine.",
2550 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2551 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2552 " on the storage for the vmstate.",
2553 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2556 additionalProperties
=> 0,
2558 node
=> get_standard_option
('pve-node'),
2559 vmid
=> get_standard_option
('pve-vmid',
2560 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2561 skiplock
=> get_standard_option
('skiplock'),
2566 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2568 statestorage
=> get_standard_option
('pve-storage-id', {
2569 description
=> "The storage for the VM state",
2570 requires
=> 'todisk',
2572 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2582 my $rpcenv = PVE
::RPCEnvironment
::get
();
2583 my $authuser = $rpcenv->get_user();
2585 my $node = extract_param
($param, 'node');
2586 my $vmid = extract_param
($param, 'vmid');
2588 my $todisk = extract_param
($param, 'todisk') // 0;
2590 my $statestorage = extract_param
($param, 'statestorage');
2592 my $skiplock = extract_param
($param, 'skiplock');
2593 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2594 if $skiplock && $authuser ne 'root@pam';
2596 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2598 die "Cannot suspend HA managed VM to disk\n"
2599 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2601 # early check for storage permission, for better user feedback
2603 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2605 if (!$statestorage) {
2606 # get statestorage from config if none is given
2607 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2608 my $storecfg = PVE
::Storage
::config
();
2609 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2612 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2618 syslog
('info', "suspend VM $vmid: $upid\n");
2620 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2625 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2626 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2629 __PACKAGE__-
>register_method({
2630 name
=> 'vm_resume',
2631 path
=> '{vmid}/status/resume',
2635 description
=> "Resume virtual machine.",
2637 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2640 additionalProperties
=> 0,
2642 node
=> get_standard_option
('pve-node'),
2643 vmid
=> get_standard_option
('pve-vmid',
2644 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2645 skiplock
=> get_standard_option
('skiplock'),
2646 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2656 my $rpcenv = PVE
::RPCEnvironment
::get
();
2658 my $authuser = $rpcenv->get_user();
2660 my $node = extract_param
($param, 'node');
2662 my $vmid = extract_param
($param, 'vmid');
2664 my $skiplock = extract_param
($param, 'skiplock');
2665 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2666 if $skiplock && $authuser ne 'root@pam';
2668 my $nocheck = extract_param
($param, 'nocheck');
2669 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2670 if $nocheck && $authuser ne 'root@pam';
2672 my $to_disk_suspended;
2674 PVE
::QemuConfig-
>lock_config($vmid, sub {
2675 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2676 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2680 die "VM $vmid not running\n"
2681 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2686 syslog
('info', "resume VM $vmid: $upid\n");
2688 if (!$to_disk_suspended) {
2689 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2691 my $storecfg = PVE
::Storage
::config
();
2692 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2698 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2701 __PACKAGE__-
>register_method({
2702 name
=> 'vm_sendkey',
2703 path
=> '{vmid}/sendkey',
2707 description
=> "Send key event to virtual machine.",
2709 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2712 additionalProperties
=> 0,
2714 node
=> get_standard_option
('pve-node'),
2715 vmid
=> get_standard_option
('pve-vmid',
2716 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2717 skiplock
=> get_standard_option
('skiplock'),
2719 description
=> "The key (qemu monitor encoding).",
2724 returns
=> { type
=> 'null'},
2728 my $rpcenv = PVE
::RPCEnvironment
::get
();
2730 my $authuser = $rpcenv->get_user();
2732 my $node = extract_param
($param, 'node');
2734 my $vmid = extract_param
($param, 'vmid');
2736 my $skiplock = extract_param
($param, 'skiplock');
2737 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2738 if $skiplock && $authuser ne 'root@pam';
2740 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2745 __PACKAGE__-
>register_method({
2746 name
=> 'vm_feature',
2747 path
=> '{vmid}/feature',
2751 description
=> "Check if feature for virtual machine is available.",
2753 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2756 additionalProperties
=> 0,
2758 node
=> get_standard_option
('pve-node'),
2759 vmid
=> get_standard_option
('pve-vmid'),
2761 description
=> "Feature to check.",
2763 enum
=> [ 'snapshot', 'clone', 'copy' ],
2765 snapname
=> get_standard_option
('pve-snapshot-name', {
2773 hasFeature
=> { type
=> 'boolean' },
2776 items
=> { type
=> 'string' },
2783 my $node = extract_param
($param, 'node');
2785 my $vmid = extract_param
($param, 'vmid');
2787 my $snapname = extract_param
($param, 'snapname');
2789 my $feature = extract_param
($param, 'feature');
2791 my $running = PVE
::QemuServer
::check_running
($vmid);
2793 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2796 my $snap = $conf->{snapshots
}->{$snapname};
2797 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2800 my $storecfg = PVE
::Storage
::config
();
2802 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2803 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2806 hasFeature
=> $hasFeature,
2807 nodes
=> [ keys %$nodelist ],
2811 __PACKAGE__-
>register_method({
2813 path
=> '{vmid}/clone',
2817 description
=> "Create a copy of virtual machine/template.",
2819 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2820 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2821 "'Datastore.AllocateSpace' on any used storage.",
2824 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2826 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2827 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2832 additionalProperties
=> 0,
2834 node
=> get_standard_option
('pve-node'),
2835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2836 newid
=> get_standard_option
('pve-vmid', {
2837 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2838 description
=> 'VMID for the clone.' }),
2841 type
=> 'string', format
=> 'dns-name',
2842 description
=> "Set a name for the new VM.",
2847 description
=> "Description for the new VM.",
2851 type
=> 'string', format
=> 'pve-poolid',
2852 description
=> "Add the new VM to the specified pool.",
2854 snapname
=> get_standard_option
('pve-snapshot-name', {
2857 storage
=> get_standard_option
('pve-storage-id', {
2858 description
=> "Target storage for full clone.",
2862 description
=> "Target format for file storage. Only valid for full clone.",
2865 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2870 description
=> "Create a full copy of all disks. This is always done when " .
2871 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2873 target
=> get_standard_option
('pve-node', {
2874 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2878 description
=> "Override I/O bandwidth limit (in KiB/s).",
2882 default => 'clone limit from datacenter or storage config',
2892 my $rpcenv = PVE
::RPCEnvironment
::get
();
2893 my $authuser = $rpcenv->get_user();
2895 my $node = extract_param
($param, 'node');
2896 my $vmid = extract_param
($param, 'vmid');
2897 my $newid = extract_param
($param, 'newid');
2898 my $pool = extract_param
($param, 'pool');
2899 $rpcenv->check_pool_exist($pool) if defined($pool);
2901 my $snapname = extract_param
($param, 'snapname');
2902 my $storage = extract_param
($param, 'storage');
2903 my $format = extract_param
($param, 'format');
2904 my $target = extract_param
($param, 'target');
2906 my $localnode = PVE
::INotify
::nodename
();
2908 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2912 PVE
::Cluster
::check_node_exists
($target) if $target;
2914 my $storecfg = PVE
::Storage
::config
();
2917 # check if storage is enabled on local node
2918 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2920 # check if storage is available on target node
2921 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2922 # clone only works if target storage is shared
2923 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2924 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2928 PVE
::Cluster
::check_cfs_quorum
();
2930 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2933 # do all tests after lock but before forking worker - if possible
2935 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2936 PVE
::QemuConfig-
>check_lock($conf);
2938 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2939 die "unexpected state change\n" if $verify_running != $running;
2941 die "snapshot '$snapname' does not exist\n"
2942 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2944 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2946 die "parameter 'storage' not allowed for linked clones\n"
2947 if defined($storage) && !$full;
2949 die "parameter 'format' not allowed for linked clones\n"
2950 if defined($format) && !$full;
2952 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2954 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2956 die "can't clone VM to node '$target' (VM uses local storage)\n"
2957 if $target && !$sharedvm;
2959 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2960 die "unable to create VM $newid: config file already exists\n"
2963 my $newconf = { lock => 'clone' };
2968 foreach my $opt (keys %$oldconf) {
2969 my $value = $oldconf->{$opt};
2971 # do not copy snapshot related info
2972 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2973 $opt eq 'vmstate' || $opt eq 'snapstate';
2975 # no need to copy unused images, because VMID(owner) changes anyways
2976 next if $opt =~ m/^unused\d+$/;
2978 # always change MAC! address
2979 if ($opt =~ m/^net(\d+)$/) {
2980 my $net = PVE
::QemuServer
::parse_net
($value);
2981 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2982 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2983 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2984 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2985 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2986 die "unable to parse drive options for '$opt'\n" if !$drive;
2987 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2988 $newconf->{$opt} = $value; # simply copy configuration
2990 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2991 die "Full clone feature is not supported for drive '$opt'\n"
2992 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2993 $fullclone->{$opt} = 1;
2995 # not full means clone instead of copy
2996 die "Linked clone feature is not supported for drive '$opt'\n"
2997 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2999 $drives->{$opt} = $drive;
3000 push @$vollist, $drive->{file
};
3003 # copy everything else
3004 $newconf->{$opt} = $value;
3008 # auto generate a new uuid
3009 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3010 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3011 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3012 # auto generate a new vmgenid only if the option was set for template
3013 if ($newconf->{vmgenid
}) {
3014 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3017 delete $newconf->{template
};
3019 if ($param->{name
}) {
3020 $newconf->{name
} = $param->{name
};
3022 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3025 if ($param->{description
}) {
3026 $newconf->{description
} = $param->{description
};
3029 # create empty/temp config - this fails if VM already exists on other node
3030 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3031 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3036 my $newvollist = [];
3043 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3045 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3047 my $bwlimit = extract_param
($param, 'bwlimit');
3049 my $total_jobs = scalar(keys %{$drives});
3052 foreach my $opt (keys %$drives) {
3053 my $drive = $drives->{$opt};
3054 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3055 my $completion = $skipcomplete ?
'skip' : 'complete';
3057 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3058 my $storage_list = [ $src_sid ];
3059 push @$storage_list, $storage if defined($storage);
3060 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3062 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3063 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3064 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3066 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3068 PVE
::QemuConfig-
>write_config($newid, $newconf);
3072 delete $newconf->{lock};
3074 # do not write pending changes
3075 if (my @changes = keys %{$newconf->{pending
}}) {
3076 my $pending = join(',', @changes);
3077 warn "found pending changes for '$pending', discarding for clone\n";
3078 delete $newconf->{pending
};
3081 PVE
::QemuConfig-
>write_config($newid, $newconf);
3084 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3085 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3086 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3088 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3089 die "Failed to move config to node '$target' - rename failed: $!\n"
3090 if !rename($conffile, $newconffile);
3093 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3096 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3097 sleep 1; # some storage like rbd need to wait before release volume - really?
3099 foreach my $volid (@$newvollist) {
3100 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3104 PVE
::Firewall
::remove_vmfw_conf
($newid);
3106 unlink $conffile; # avoid races -> last thing before die
3108 die "clone failed: $err";
3114 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3116 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3119 # Aquire exclusive lock lock for $newid
3120 my $lock_target_vm = sub {
3121 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3124 # exclusive lock if VM is running - else shared lock is enough;
3126 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3128 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3132 __PACKAGE__-
>register_method({
3133 name
=> 'move_vm_disk',
3134 path
=> '{vmid}/move_disk',
3138 description
=> "Move volume to different storage.",
3140 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3142 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3143 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3147 additionalProperties
=> 0,
3149 node
=> get_standard_option
('pve-node'),
3150 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3153 description
=> "The disk you want to move.",
3154 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3156 storage
=> get_standard_option
('pve-storage-id', {
3157 description
=> "Target storage.",
3158 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3162 description
=> "Target Format.",
3163 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3168 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3174 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3179 description
=> "Override I/O bandwidth limit (in KiB/s).",
3183 default => 'move limit from datacenter or storage config',
3189 description
=> "the task ID.",
3194 my $rpcenv = PVE
::RPCEnvironment
::get
();
3195 my $authuser = $rpcenv->get_user();
3197 my $node = extract_param
($param, 'node');
3198 my $vmid = extract_param
($param, 'vmid');
3199 my $digest = extract_param
($param, 'digest');
3200 my $disk = extract_param
($param, 'disk');
3201 my $storeid = extract_param
($param, 'storage');
3202 my $format = extract_param
($param, 'format');
3204 my $storecfg = PVE
::Storage
::config
();
3206 my $updatefn = sub {
3207 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3208 PVE
::QemuConfig-
>check_lock($conf);
3210 die "VM config checksum missmatch (file change by other user?)\n"
3211 if $digest && $digest ne $conf->{digest
};
3213 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3215 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3217 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3218 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3220 my $old_volid = $drive->{file
};
3222 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3223 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3227 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3228 (!$format || !$oldfmt || $oldfmt eq $format);
3230 # this only checks snapshots because $disk is passed!
3231 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3232 die "you can't move a disk with snapshots and delete the source\n"
3233 if $snapshotted && $param->{delete};
3235 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3237 my $running = PVE
::QemuServer
::check_running
($vmid);
3239 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3242 my $newvollist = [];
3248 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3250 warn "moving disk with snapshots, snapshots will not be moved!\n"
3253 my $bwlimit = extract_param
($param, 'bwlimit');
3254 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3256 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3257 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3259 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3261 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3263 # convert moved disk to base if part of template
3264 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3265 if PVE
::QemuConfig-
>is_template($conf);
3267 PVE
::QemuConfig-
>write_config($vmid, $conf);
3269 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3270 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3271 eval { mon_cmd
($vmid, "guest-fstrim") };
3275 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3276 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3282 foreach my $volid (@$newvollist) {
3283 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3286 die "storage migration failed: $err";
3289 if ($param->{delete}) {
3291 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3292 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3298 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3301 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3304 my $check_vm_disks_local = sub {
3305 my ($storecfg, $vmconf, $vmid) = @_;
3307 my $local_disks = {};
3309 # add some more information to the disks e.g. cdrom
3310 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3311 my ($volid, $attr) = @_;
3313 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3315 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3316 return if $scfg->{shared
};
3318 # The shared attr here is just a special case where the vdisk
3319 # is marked as shared manually
3320 return if $attr->{shared
};
3321 return if $attr->{cdrom
} and $volid eq "none";
3323 if (exists $local_disks->{$volid}) {
3324 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3326 $local_disks->{$volid} = $attr;
3327 # ensure volid is present in case it's needed
3328 $local_disks->{$volid}->{volid
} = $volid;
3332 return $local_disks;
3335 __PACKAGE__-
>register_method({
3336 name
=> 'migrate_vm_precondition',
3337 path
=> '{vmid}/migrate',
3341 description
=> "Get preconditions for migration.",
3343 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3346 additionalProperties
=> 0,
3348 node
=> get_standard_option
('pve-node'),
3349 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3350 target
=> get_standard_option
('pve-node', {
3351 description
=> "Target node.",
3352 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3360 running
=> { type
=> 'boolean' },
3364 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3366 not_allowed_nodes
=> {
3369 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3373 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3375 local_resources
=> {
3377 description
=> "List local resources e.g. pci, usb"
3384 my $rpcenv = PVE
::RPCEnvironment
::get
();
3386 my $authuser = $rpcenv->get_user();
3388 PVE
::Cluster
::check_cfs_quorum
();
3392 my $vmid = extract_param
($param, 'vmid');
3393 my $target = extract_param
($param, 'target');
3394 my $localnode = PVE
::INotify
::nodename
();
3398 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3399 my $storecfg = PVE
::Storage
::config
();
3402 # try to detect errors early
3403 PVE
::QemuConfig-
>check_lock($vmconf);
3405 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3407 # if vm is not running, return target nodes where local storage is available
3408 # for offline migration
3409 if (!$res->{running
}) {
3410 $res->{allowed_nodes
} = [];
3411 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3412 delete $checked_nodes->{$localnode};
3414 foreach my $node (keys %$checked_nodes) {
3415 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3416 push @{$res->{allowed_nodes
}}, $node;
3420 $res->{not_allowed_nodes
} = $checked_nodes;
3424 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3425 $res->{local_disks
} = [ values %$local_disks ];;
3427 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3429 $res->{local_resources
} = $local_resources;
3436 __PACKAGE__-
>register_method({
3437 name
=> 'migrate_vm',
3438 path
=> '{vmid}/migrate',
3442 description
=> "Migrate virtual machine. Creates a new migration task.",
3444 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3447 additionalProperties
=> 0,
3449 node
=> get_standard_option
('pve-node'),
3450 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3451 target
=> get_standard_option
('pve-node', {
3452 description
=> "Target node.",
3453 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3457 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3462 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3467 enum
=> ['secure', 'insecure'],
3468 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3471 migration_network
=> {
3472 type
=> 'string', format
=> 'CIDR',
3473 description
=> "CIDR of the (sub) network that is used for migration.",
3476 "with-local-disks" => {
3478 description
=> "Enable live storage migration for local disk",
3481 targetstorage
=> get_standard_option
('pve-targetstorage', {
3482 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3485 description
=> "Override I/O bandwidth limit (in KiB/s).",
3489 default => 'migrate limit from datacenter or storage config',
3495 description
=> "the task ID.",
3500 my $rpcenv = PVE
::RPCEnvironment
::get
();
3501 my $authuser = $rpcenv->get_user();
3503 my $target = extract_param
($param, 'target');
3505 my $localnode = PVE
::INotify
::nodename
();
3506 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3508 PVE
::Cluster
::check_cfs_quorum
();
3510 PVE
::Cluster
::check_node_exists
($target);
3512 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3514 my $vmid = extract_param
($param, 'vmid');
3516 raise_param_exc
({ force
=> "Only root may use this option." })
3517 if $param->{force
} && $authuser ne 'root@pam';
3519 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3520 if $param->{migration_type
} && $authuser ne 'root@pam';
3522 # allow root only until better network permissions are available
3523 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3524 if $param->{migration_network
} && $authuser ne 'root@pam';
3527 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3529 # try to detect errors early
3531 PVE
::QemuConfig-
>check_lock($conf);
3533 if (PVE
::QemuServer
::check_running
($vmid)) {
3534 die "can't migrate running VM without --online\n" if !$param->{online
};
3536 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3537 $param->{online
} = 0;
3540 my $storecfg = PVE
::Storage
::config
();
3542 if (my $targetstorage = $param->{targetstorage
}) {
3543 my $check_storage = sub {
3544 my ($target_sid) = @_;
3545 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3546 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3547 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3548 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3549 if !$scfg->{content
}->{images
};
3552 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3553 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3556 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3557 if !defined($storagemap->{identity
});
3559 foreach my $source (values %{$storagemap->{entries
}}) {
3560 $check_storage->($source);
3563 $check_storage->($storagemap->{default})
3564 if $storagemap->{default};
3566 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3567 if $storagemap->{identity
};
3569 $param->{storagemap
} = $storagemap;
3571 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3574 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3579 print "Requesting HA migration for VM $vmid to node $target\n";
3581 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3582 PVE
::Tools
::run_command
($cmd);
3586 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3591 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3595 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3598 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3603 __PACKAGE__-
>register_method({
3605 path
=> '{vmid}/monitor',
3609 description
=> "Execute Qemu monitor commands.",
3611 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3612 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3615 additionalProperties
=> 0,
3617 node
=> get_standard_option
('pve-node'),
3618 vmid
=> get_standard_option
('pve-vmid'),
3621 description
=> "The monitor command.",
3625 returns
=> { type
=> 'string'},
3629 my $rpcenv = PVE
::RPCEnvironment
::get
();
3630 my $authuser = $rpcenv->get_user();
3633 my $command = shift;
3634 return $command =~ m/^\s*info(\s+|$)/
3635 || $command =~ m/^\s*help\s*$/;
3638 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3639 if !&$is_ro($param->{command
});
3641 my $vmid = $param->{vmid
};
3643 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3647 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3649 $res = "ERROR: $@" if $@;
3654 __PACKAGE__-
>register_method({
3655 name
=> 'resize_vm',
3656 path
=> '{vmid}/resize',
3660 description
=> "Extend volume size.",
3662 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3665 additionalProperties
=> 0,
3667 node
=> get_standard_option
('pve-node'),
3668 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3669 skiplock
=> get_standard_option
('skiplock'),
3672 description
=> "The disk you want to resize.",
3673 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3677 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3678 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.",
3682 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3688 returns
=> { type
=> 'null'},
3692 my $rpcenv = PVE
::RPCEnvironment
::get
();
3694 my $authuser = $rpcenv->get_user();
3696 my $node = extract_param
($param, 'node');
3698 my $vmid = extract_param
($param, 'vmid');
3700 my $digest = extract_param
($param, 'digest');
3702 my $disk = extract_param
($param, 'disk');
3704 my $sizestr = extract_param
($param, 'size');
3706 my $skiplock = extract_param
($param, 'skiplock');
3707 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3708 if $skiplock && $authuser ne 'root@pam';
3710 my $storecfg = PVE
::Storage
::config
();
3712 my $updatefn = sub {
3714 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3716 die "checksum missmatch (file change by other user?)\n"
3717 if $digest && $digest ne $conf->{digest
};
3718 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3720 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3722 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3724 my (undef, undef, undef, undef, undef, undef, $format) =
3725 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3727 die "can't resize volume: $disk if snapshot exists\n"
3728 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3730 my $volid = $drive->{file
};
3732 die "disk '$disk' has no associated volume\n" if !$volid;
3734 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3736 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3738 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3740 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3741 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3743 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3745 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3746 my ($ext, $newsize, $unit) = ($1, $2, $4);
3749 $newsize = $newsize * 1024;
3750 } elsif ($unit eq 'M') {
3751 $newsize = $newsize * 1024 * 1024;
3752 } elsif ($unit eq 'G') {
3753 $newsize = $newsize * 1024 * 1024 * 1024;
3754 } elsif ($unit eq 'T') {
3755 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3758 $newsize += $size if $ext;
3759 $newsize = int($newsize);
3761 die "shrinking disks is not supported\n" if $newsize < $size;
3763 return if $size == $newsize;
3765 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3767 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3769 $drive->{size
} = $newsize;
3770 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3772 PVE
::QemuConfig-
>write_config($vmid, $conf);
3775 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3779 __PACKAGE__-
>register_method({
3780 name
=> 'snapshot_list',
3781 path
=> '{vmid}/snapshot',
3783 description
=> "List all snapshots.",
3785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3788 protected
=> 1, # qemu pid files are only readable by root
3790 additionalProperties
=> 0,
3792 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3793 node
=> get_standard_option
('pve-node'),
3802 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3806 description
=> "Snapshot includes RAM.",
3811 description
=> "Snapshot description.",
3815 description
=> "Snapshot creation time",
3817 renderer
=> 'timestamp',
3821 description
=> "Parent snapshot identifier.",
3827 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3832 my $vmid = $param->{vmid
};
3834 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3835 my $snaphash = $conf->{snapshots
} || {};
3839 foreach my $name (keys %$snaphash) {
3840 my $d = $snaphash->{$name};
3843 snaptime
=> $d->{snaptime
} || 0,
3844 vmstate
=> $d->{vmstate
} ?
1 : 0,
3845 description
=> $d->{description
} || '',
3847 $item->{parent
} = $d->{parent
} if $d->{parent
};
3848 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3852 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3855 digest
=> $conf->{digest
},
3856 running
=> $running,
3857 description
=> "You are here!",
3859 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3861 push @$res, $current;
3866 __PACKAGE__-
>register_method({
3868 path
=> '{vmid}/snapshot',
3872 description
=> "Snapshot a VM.",
3874 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3877 additionalProperties
=> 0,
3879 node
=> get_standard_option
('pve-node'),
3880 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3881 snapname
=> get_standard_option
('pve-snapshot-name'),
3885 description
=> "Save the vmstate",
3890 description
=> "A textual description or comment.",
3896 description
=> "the task ID.",
3901 my $rpcenv = PVE
::RPCEnvironment
::get
();
3903 my $authuser = $rpcenv->get_user();
3905 my $node = extract_param
($param, 'node');
3907 my $vmid = extract_param
($param, 'vmid');
3909 my $snapname = extract_param
($param, 'snapname');
3911 die "unable to use snapshot name 'current' (reserved name)\n"
3912 if $snapname eq 'current';
3914 die "unable to use snapshot name 'pending' (reserved name)\n"
3915 if lc($snapname) eq 'pending';
3918 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3919 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3920 $param->{description
});
3923 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3926 __PACKAGE__-
>register_method({
3927 name
=> 'snapshot_cmd_idx',
3928 path
=> '{vmid}/snapshot/{snapname}',
3935 additionalProperties
=> 0,
3937 vmid
=> get_standard_option
('pve-vmid'),
3938 node
=> get_standard_option
('pve-node'),
3939 snapname
=> get_standard_option
('pve-snapshot-name'),
3948 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3955 push @$res, { cmd
=> 'rollback' };
3956 push @$res, { cmd
=> 'config' };
3961 __PACKAGE__-
>register_method({
3962 name
=> 'update_snapshot_config',
3963 path
=> '{vmid}/snapshot/{snapname}/config',
3967 description
=> "Update snapshot metadata.",
3969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3972 additionalProperties
=> 0,
3974 node
=> get_standard_option
('pve-node'),
3975 vmid
=> get_standard_option
('pve-vmid'),
3976 snapname
=> get_standard_option
('pve-snapshot-name'),
3980 description
=> "A textual description or comment.",
3984 returns
=> { type
=> 'null' },
3988 my $rpcenv = PVE
::RPCEnvironment
::get
();
3990 my $authuser = $rpcenv->get_user();
3992 my $vmid = extract_param
($param, 'vmid');
3994 my $snapname = extract_param
($param, 'snapname');
3996 return undef if !defined($param->{description
});
3998 my $updatefn = sub {
4000 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4002 PVE
::QemuConfig-
>check_lock($conf);
4004 my $snap = $conf->{snapshots
}->{$snapname};
4006 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4008 $snap->{description
} = $param->{description
} if defined($param->{description
});
4010 PVE
::QemuConfig-
>write_config($vmid, $conf);
4013 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4018 __PACKAGE__-
>register_method({
4019 name
=> 'get_snapshot_config',
4020 path
=> '{vmid}/snapshot/{snapname}/config',
4023 description
=> "Get snapshot configuration",
4025 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4028 additionalProperties
=> 0,
4030 node
=> get_standard_option
('pve-node'),
4031 vmid
=> get_standard_option
('pve-vmid'),
4032 snapname
=> get_standard_option
('pve-snapshot-name'),
4035 returns
=> { type
=> "object" },
4039 my $rpcenv = PVE
::RPCEnvironment
::get
();
4041 my $authuser = $rpcenv->get_user();
4043 my $vmid = extract_param
($param, 'vmid');
4045 my $snapname = extract_param
($param, 'snapname');
4047 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4049 my $snap = $conf->{snapshots
}->{$snapname};
4051 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4056 __PACKAGE__-
>register_method({
4058 path
=> '{vmid}/snapshot/{snapname}/rollback',
4062 description
=> "Rollback VM state to specified snapshot.",
4064 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4067 additionalProperties
=> 0,
4069 node
=> get_standard_option
('pve-node'),
4070 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4071 snapname
=> get_standard_option
('pve-snapshot-name'),
4076 description
=> "the task ID.",
4081 my $rpcenv = PVE
::RPCEnvironment
::get
();
4083 my $authuser = $rpcenv->get_user();
4085 my $node = extract_param
($param, 'node');
4087 my $vmid = extract_param
($param, 'vmid');
4089 my $snapname = extract_param
($param, 'snapname');
4092 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4093 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4097 # hold migration lock, this makes sure that nobody create replication snapshots
4098 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4101 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4104 __PACKAGE__-
>register_method({
4105 name
=> 'delsnapshot',
4106 path
=> '{vmid}/snapshot/{snapname}',
4110 description
=> "Delete a VM snapshot.",
4112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4115 additionalProperties
=> 0,
4117 node
=> get_standard_option
('pve-node'),
4118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4119 snapname
=> get_standard_option
('pve-snapshot-name'),
4123 description
=> "For removal from config file, even if removing disk snapshots fails.",
4129 description
=> "the task ID.",
4134 my $rpcenv = PVE
::RPCEnvironment
::get
();
4136 my $authuser = $rpcenv->get_user();
4138 my $node = extract_param
($param, 'node');
4140 my $vmid = extract_param
($param, 'vmid');
4142 my $snapname = extract_param
($param, 'snapname');
4145 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4146 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4149 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4152 __PACKAGE__-
>register_method({
4154 path
=> '{vmid}/template',
4158 description
=> "Create a Template.",
4160 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4161 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4164 additionalProperties
=> 0,
4166 node
=> get_standard_option
('pve-node'),
4167 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4171 description
=> "If you want to convert only 1 disk to base image.",
4172 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4177 returns
=> { type
=> 'null'},
4181 my $rpcenv = PVE
::RPCEnvironment
::get
();
4183 my $authuser = $rpcenv->get_user();
4185 my $node = extract_param
($param, 'node');
4187 my $vmid = extract_param
($param, 'vmid');
4189 my $disk = extract_param
($param, 'disk');
4191 my $updatefn = sub {
4193 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4195 PVE
::QemuConfig-
>check_lock($conf);
4197 die "unable to create template, because VM contains snapshots\n"
4198 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4200 die "you can't convert a template to a template\n"
4201 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4203 die "you can't convert a VM to template if VM is running\n"
4204 if PVE
::QemuServer
::check_running
($vmid);
4207 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4210 $conf->{template
} = 1;
4211 PVE
::QemuConfig-
>write_config($vmid, $conf);
4213 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4216 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4220 __PACKAGE__-
>register_method({
4221 name
=> 'cloudinit_generated_config_dump',
4222 path
=> '{vmid}/cloudinit/dump',
4225 description
=> "Get automatically generated cloudinit config.",
4227 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4230 additionalProperties
=> 0,
4232 node
=> get_standard_option
('pve-node'),
4233 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4235 description
=> 'Config type.',
4237 enum
=> ['user', 'network', 'meta'],
4247 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4249 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});