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
1600 my @rand_bytes = split '', Crypt
::OpenSSL
::Random
::random_bytes
($length);
1601 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } @rand_bytes);
1608 __PACKAGE__-
>register_method({
1610 path
=> '{vmid}/vncproxy',
1614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1616 description
=> "Creates a TCP VNC proxy connections.",
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid'),
1625 description
=> "starts websockify instead of vncproxy",
1627 'generate-password' => {
1631 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1636 additionalProperties
=> 0,
1638 user
=> { type
=> 'string' },
1639 ticket
=> { type
=> 'string' },
1642 description
=> "Returned if requested with 'generate-password' param."
1643 ." Consists of printable ASCII characters ('!' .. '~').",
1646 cert
=> { type
=> 'string' },
1647 port
=> { type
=> 'integer' },
1648 upid
=> { type
=> 'string' },
1654 my $rpcenv = PVE
::RPCEnvironment
::get
();
1656 my $authuser = $rpcenv->get_user();
1658 my $vmid = $param->{vmid
};
1659 my $node = $param->{node
};
1660 my $websocket = $param->{websocket
};
1662 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1666 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1667 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1670 my $authpath = "/vms/$vmid";
1672 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1673 my $password = $ticket;
1674 if ($param->{'generate-password'}) {
1675 $password = $gen_rand_chars->(8);
1678 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1684 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1685 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1686 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1687 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1688 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1690 $family = PVE
::Tools
::get_host_address_family
($node);
1693 my $port = PVE
::Tools
::next_vnc_port
($family);
1700 syslog
('info', "starting vnc proxy $upid\n");
1704 if (defined($serial)) {
1706 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1708 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1709 '-timeout', $timeout, '-authpath', $authpath,
1710 '-perm', 'Sys.Console'];
1712 if ($param->{websocket
}) {
1713 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1714 push @$cmd, '-notls', '-listen', 'localhost';
1717 push @$cmd, '-c', @$remcmd, @$termcmd;
1719 PVE
::Tools
::run_command
($cmd);
1723 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1725 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1727 my $sock = IO
::Socket
::IP-
>new(
1732 GetAddrInfoFlags
=> 0,
1733 ) or die "failed to create socket: $!\n";
1734 # Inside the worker we shouldn't have any previous alarms
1735 # running anyway...:
1737 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1739 accept(my $cli, $sock) or die "connection failed: $!\n";
1742 if (PVE
::Tools
::run_command
($cmd,
1743 output
=> '>&'.fileno($cli),
1744 input
=> '<&'.fileno($cli),
1747 die "Failed to run vncproxy.\n";
1754 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1756 PVE
::Tools
::wait_for_vnc_port
($port);
1765 $res->{password
} = $password if $param->{'generate-password'};
1770 __PACKAGE__-
>register_method({
1771 name
=> 'termproxy',
1772 path
=> '{vmid}/termproxy',
1776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1778 description
=> "Creates a TCP proxy connections.",
1780 additionalProperties
=> 0,
1782 node
=> get_standard_option
('pve-node'),
1783 vmid
=> get_standard_option
('pve-vmid'),
1787 enum
=> [qw(serial0 serial1 serial2 serial3)],
1788 description
=> "opens a serial terminal (defaults to display)",
1793 additionalProperties
=> 0,
1795 user
=> { type
=> 'string' },
1796 ticket
=> { type
=> 'string' },
1797 port
=> { type
=> 'integer' },
1798 upid
=> { type
=> 'string' },
1804 my $rpcenv = PVE
::RPCEnvironment
::get
();
1806 my $authuser = $rpcenv->get_user();
1808 my $vmid = $param->{vmid
};
1809 my $node = $param->{node
};
1810 my $serial = $param->{serial
};
1812 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1814 if (!defined($serial)) {
1816 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1817 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1821 my $authpath = "/vms/$vmid";
1823 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1828 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1829 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1830 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1831 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1832 push @$remcmd, '--';
1834 $family = PVE
::Tools
::get_host_address_family
($node);
1837 my $port = PVE
::Tools
::next_vnc_port
($family);
1839 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1840 push @$termcmd, '-iface', $serial if $serial;
1845 syslog
('info', "starting qemu termproxy $upid\n");
1847 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1848 '--perm', 'VM.Console', '--'];
1849 push @$cmd, @$remcmd, @$termcmd;
1851 PVE
::Tools
::run_command
($cmd);
1854 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1856 PVE
::Tools
::wait_for_vnc_port
($port);
1866 __PACKAGE__-
>register_method({
1867 name
=> 'vncwebsocket',
1868 path
=> '{vmid}/vncwebsocket',
1871 description
=> "You also need to pass a valid ticket (vncticket).",
1872 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1874 description
=> "Opens a weksocket for VNC traffic.",
1876 additionalProperties
=> 0,
1878 node
=> get_standard_option
('pve-node'),
1879 vmid
=> get_standard_option
('pve-vmid'),
1881 description
=> "Ticket from previous call to vncproxy.",
1886 description
=> "Port number returned by previous vncproxy call.",
1896 port
=> { type
=> 'string' },
1902 my $rpcenv = PVE
::RPCEnvironment
::get
();
1904 my $authuser = $rpcenv->get_user();
1906 my $vmid = $param->{vmid
};
1907 my $node = $param->{node
};
1909 my $authpath = "/vms/$vmid";
1911 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1913 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1915 # Note: VNC ports are acessible from outside, so we do not gain any
1916 # security if we verify that $param->{port} belongs to VM $vmid. This
1917 # check is done by verifying the VNC ticket (inside VNC protocol).
1919 my $port = $param->{port
};
1921 return { port
=> $port };
1924 __PACKAGE__-
>register_method({
1925 name
=> 'spiceproxy',
1926 path
=> '{vmid}/spiceproxy',
1931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1933 description
=> "Returns a SPICE configuration to connect to the VM.",
1935 additionalProperties
=> 0,
1937 node
=> get_standard_option
('pve-node'),
1938 vmid
=> get_standard_option
('pve-vmid'),
1939 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1942 returns
=> get_standard_option
('remote-viewer-config'),
1946 my $rpcenv = PVE
::RPCEnvironment
::get
();
1948 my $authuser = $rpcenv->get_user();
1950 my $vmid = $param->{vmid
};
1951 my $node = $param->{node
};
1952 my $proxy = $param->{proxy
};
1954 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1955 my $title = "VM $vmid";
1956 $title .= " - ". $conf->{name
} if $conf->{name
};
1958 my $port = PVE
::QemuServer
::spice_port
($vmid);
1960 my ($ticket, undef, $remote_viewer_config) =
1961 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1963 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1964 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1966 return $remote_viewer_config;
1969 __PACKAGE__-
>register_method({
1971 path
=> '{vmid}/status',
1974 description
=> "Directory index",
1979 additionalProperties
=> 0,
1981 node
=> get_standard_option
('pve-node'),
1982 vmid
=> get_standard_option
('pve-vmid'),
1990 subdir
=> { type
=> 'string' },
1993 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1999 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2002 { subdir
=> 'current' },
2003 { subdir
=> 'start' },
2004 { subdir
=> 'stop' },
2005 { subdir
=> 'reset' },
2006 { subdir
=> 'shutdown' },
2007 { subdir
=> 'suspend' },
2008 { subdir
=> 'reboot' },
2014 __PACKAGE__-
>register_method({
2015 name
=> 'vm_status',
2016 path
=> '{vmid}/status/current',
2019 protected
=> 1, # qemu pid files are only readable by root
2020 description
=> "Get virtual machine status.",
2022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2025 additionalProperties
=> 0,
2027 node
=> get_standard_option
('pve-node'),
2028 vmid
=> get_standard_option
('pve-vmid'),
2034 %$PVE::QemuServer
::vmstatus_return_properties
,
2036 description
=> "HA manager service status.",
2040 description
=> "Qemu VGA configuration supports spice.",
2045 description
=> "Qemu GuestAgent enabled in config.",
2055 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2057 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2058 my $status = $vmstatus->{$param->{vmid
}};
2060 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2062 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2063 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2068 __PACKAGE__-
>register_method({
2070 path
=> '{vmid}/status/start',
2074 description
=> "Start virtual machine.",
2076 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2079 additionalProperties
=> 0,
2081 node
=> get_standard_option
('pve-node'),
2082 vmid
=> get_standard_option
('pve-vmid',
2083 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2084 skiplock
=> get_standard_option
('skiplock'),
2085 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2086 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2089 enum
=> ['secure', 'insecure'],
2090 description
=> "Migration traffic is encrypted using an SSH " .
2091 "tunnel by default. On secure, completely private networks " .
2092 "this can be disabled to increase performance.",
2095 migration_network
=> {
2096 type
=> 'string', format
=> 'CIDR',
2097 description
=> "CIDR of the (sub) network that is used for migration.",
2100 machine
=> get_standard_option
('pve-qemu-machine'),
2102 description
=> "Override QEMU's -cpu argument with the given string.",
2106 targetstorage
=> get_standard_option
('pve-targetstorage'),
2108 description
=> "Wait maximal timeout seconds.",
2111 default => 'max(30, vm memory in GiB)',
2122 my $rpcenv = PVE
::RPCEnvironment
::get
();
2123 my $authuser = $rpcenv->get_user();
2125 my $node = extract_param
($param, 'node');
2126 my $vmid = extract_param
($param, 'vmid');
2127 my $timeout = extract_param
($param, 'timeout');
2129 my $machine = extract_param
($param, 'machine');
2130 my $force_cpu = extract_param
($param, 'force-cpu');
2132 my $get_root_param = sub {
2133 my $value = extract_param
($param, $_[0]);
2134 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2135 if $value && $authuser ne 'root@pam';
2139 my $stateuri = $get_root_param->('stateuri');
2140 my $skiplock = $get_root_param->('skiplock');
2141 my $migratedfrom = $get_root_param->('migratedfrom');
2142 my $migration_type = $get_root_param->('migration_type');
2143 my $migration_network = $get_root_param->('migration_network');
2144 my $targetstorage = $get_root_param->('targetstorage');
2148 if ($targetstorage) {
2149 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2151 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2152 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2156 # read spice ticket from STDIN
2158 my $nbd_protocol_version = 0;
2159 my $replicated_volumes = {};
2160 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2161 while (defined(my $line = <STDIN
>)) {
2163 if ($line =~ m/^spice_ticket: (.+)$/) {
2165 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2166 $nbd_protocol_version = $1;
2167 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2168 $replicated_volumes->{$1} = 1;
2170 # fallback for old source node
2171 $spice_ticket = $line;
2176 PVE
::Cluster
::check_cfs_quorum
();
2178 my $storecfg = PVE
::Storage
::config
();
2180 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2184 print "Requesting HA start for VM $vmid\n";
2186 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2187 PVE
::Tools
::run_command
($cmd);
2191 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2198 syslog
('info', "start VM $vmid: $upid\n");
2200 my $migrate_opts = {
2201 migratedfrom
=> $migratedfrom,
2202 spice_ticket
=> $spice_ticket,
2203 network
=> $migration_network,
2204 type
=> $migration_type,
2205 storagemap
=> $storagemap,
2206 nbd_proto_version
=> $nbd_protocol_version,
2207 replicated_volumes
=> $replicated_volumes,
2211 statefile
=> $stateuri,
2212 skiplock
=> $skiplock,
2213 forcemachine
=> $machine,
2214 timeout
=> $timeout,
2215 forcecpu
=> $force_cpu,
2218 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2222 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2226 __PACKAGE__-
>register_method({
2228 path
=> '{vmid}/status/stop',
2232 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2233 "is akin to pulling the power plug of a running computer and may damage the VM data",
2235 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2238 additionalProperties
=> 0,
2240 node
=> get_standard_option
('pve-node'),
2241 vmid
=> get_standard_option
('pve-vmid',
2242 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2243 skiplock
=> get_standard_option
('skiplock'),
2244 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2246 description
=> "Wait maximal timeout seconds.",
2252 description
=> "Do not deactivate storage volumes.",
2265 my $rpcenv = PVE
::RPCEnvironment
::get
();
2266 my $authuser = $rpcenv->get_user();
2268 my $node = extract_param
($param, 'node');
2269 my $vmid = extract_param
($param, 'vmid');
2271 my $skiplock = extract_param
($param, 'skiplock');
2272 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2273 if $skiplock && $authuser ne 'root@pam';
2275 my $keepActive = extract_param
($param, 'keepActive');
2276 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2277 if $keepActive && $authuser ne 'root@pam';
2279 my $migratedfrom = extract_param
($param, 'migratedfrom');
2280 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2281 if $migratedfrom && $authuser ne 'root@pam';
2284 my $storecfg = PVE
::Storage
::config
();
2286 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2291 print "Requesting HA stop for VM $vmid\n";
2293 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2294 PVE
::Tools
::run_command
($cmd);
2298 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2304 syslog
('info', "stop VM $vmid: $upid\n");
2306 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2307 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2311 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2315 __PACKAGE__-
>register_method({
2317 path
=> '{vmid}/status/reset',
2321 description
=> "Reset virtual machine.",
2323 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2326 additionalProperties
=> 0,
2328 node
=> get_standard_option
('pve-node'),
2329 vmid
=> get_standard_option
('pve-vmid',
2330 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2331 skiplock
=> get_standard_option
('skiplock'),
2340 my $rpcenv = PVE
::RPCEnvironment
::get
();
2342 my $authuser = $rpcenv->get_user();
2344 my $node = extract_param
($param, 'node');
2346 my $vmid = extract_param
($param, 'vmid');
2348 my $skiplock = extract_param
($param, 'skiplock');
2349 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2350 if $skiplock && $authuser ne 'root@pam';
2352 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2357 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2362 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2365 __PACKAGE__-
>register_method({
2366 name
=> 'vm_shutdown',
2367 path
=> '{vmid}/status/shutdown',
2371 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2372 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2374 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2377 additionalProperties
=> 0,
2379 node
=> get_standard_option
('pve-node'),
2380 vmid
=> get_standard_option
('pve-vmid',
2381 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2382 skiplock
=> get_standard_option
('skiplock'),
2384 description
=> "Wait maximal timeout seconds.",
2390 description
=> "Make sure the VM stops.",
2396 description
=> "Do not deactivate storage volumes.",
2409 my $rpcenv = PVE
::RPCEnvironment
::get
();
2410 my $authuser = $rpcenv->get_user();
2412 my $node = extract_param
($param, 'node');
2413 my $vmid = extract_param
($param, 'vmid');
2415 my $skiplock = extract_param
($param, 'skiplock');
2416 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2417 if $skiplock && $authuser ne 'root@pam';
2419 my $keepActive = extract_param
($param, 'keepActive');
2420 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2421 if $keepActive && $authuser ne 'root@pam';
2423 my $storecfg = PVE
::Storage
::config
();
2427 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2428 # otherwise, we will infer a shutdown command, but run into the timeout,
2429 # then when the vm is resumed, it will instantly shutdown
2431 # checking the qmp status here to get feedback to the gui/cli/api
2432 # and the status query should not take too long
2433 my $qmpstatus = eval {
2434 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2435 mon_cmd
($vmid, "query-status");
2439 if (!$err && $qmpstatus->{status
} eq "paused") {
2440 if ($param->{forceStop
}) {
2441 warn "VM is paused - stop instead of shutdown\n";
2444 die "VM is paused - cannot shutdown\n";
2448 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2450 my $timeout = $param->{timeout
} // 60;
2454 print "Requesting HA stop for VM $vmid\n";
2456 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2457 PVE
::Tools
::run_command
($cmd);
2461 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2468 syslog
('info', "shutdown VM $vmid: $upid\n");
2470 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2471 $shutdown, $param->{forceStop
}, $keepActive);
2475 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2479 __PACKAGE__-
>register_method({
2480 name
=> 'vm_reboot',
2481 path
=> '{vmid}/status/reboot',
2485 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2487 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2490 additionalProperties
=> 0,
2492 node
=> get_standard_option
('pve-node'),
2493 vmid
=> get_standard_option
('pve-vmid',
2494 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2496 description
=> "Wait maximal timeout seconds for the shutdown.",
2509 my $rpcenv = PVE
::RPCEnvironment
::get
();
2510 my $authuser = $rpcenv->get_user();
2512 my $node = extract_param
($param, 'node');
2513 my $vmid = extract_param
($param, 'vmid');
2515 my $qmpstatus = eval {
2516 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2517 mon_cmd
($vmid, "query-status");
2521 if (!$err && $qmpstatus->{status
} eq "paused") {
2522 die "VM is paused - cannot shutdown\n";
2525 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2530 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2531 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2535 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2538 __PACKAGE__-
>register_method({
2539 name
=> 'vm_suspend',
2540 path
=> '{vmid}/status/suspend',
2544 description
=> "Suspend virtual machine.",
2546 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2547 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2548 " on the storage for the vmstate.",
2549 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid',
2556 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2557 skiplock
=> get_standard_option
('skiplock'),
2562 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2564 statestorage
=> get_standard_option
('pve-storage-id', {
2565 description
=> "The storage for the VM state",
2566 requires
=> 'todisk',
2568 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2578 my $rpcenv = PVE
::RPCEnvironment
::get
();
2579 my $authuser = $rpcenv->get_user();
2581 my $node = extract_param
($param, 'node');
2582 my $vmid = extract_param
($param, 'vmid');
2584 my $todisk = extract_param
($param, 'todisk') // 0;
2586 my $statestorage = extract_param
($param, 'statestorage');
2588 my $skiplock = extract_param
($param, 'skiplock');
2589 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2590 if $skiplock && $authuser ne 'root@pam';
2592 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2594 die "Cannot suspend HA managed VM to disk\n"
2595 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2597 # early check for storage permission, for better user feedback
2599 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2601 if (!$statestorage) {
2602 # get statestorage from config if none is given
2603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2604 my $storecfg = PVE
::Storage
::config
();
2605 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2608 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2614 syslog
('info', "suspend VM $vmid: $upid\n");
2616 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2621 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2622 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2625 __PACKAGE__-
>register_method({
2626 name
=> 'vm_resume',
2627 path
=> '{vmid}/status/resume',
2631 description
=> "Resume virtual machine.",
2633 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2636 additionalProperties
=> 0,
2638 node
=> get_standard_option
('pve-node'),
2639 vmid
=> get_standard_option
('pve-vmid',
2640 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2641 skiplock
=> get_standard_option
('skiplock'),
2642 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2652 my $rpcenv = PVE
::RPCEnvironment
::get
();
2654 my $authuser = $rpcenv->get_user();
2656 my $node = extract_param
($param, 'node');
2658 my $vmid = extract_param
($param, 'vmid');
2660 my $skiplock = extract_param
($param, 'skiplock');
2661 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2662 if $skiplock && $authuser ne 'root@pam';
2664 my $nocheck = extract_param
($param, 'nocheck');
2665 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2666 if $nocheck && $authuser ne 'root@pam';
2668 my $to_disk_suspended;
2670 PVE
::QemuConfig-
>lock_config($vmid, sub {
2671 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2672 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2676 die "VM $vmid not running\n"
2677 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2682 syslog
('info', "resume VM $vmid: $upid\n");
2684 if (!$to_disk_suspended) {
2685 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2687 my $storecfg = PVE
::Storage
::config
();
2688 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2694 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2697 __PACKAGE__-
>register_method({
2698 name
=> 'vm_sendkey',
2699 path
=> '{vmid}/sendkey',
2703 description
=> "Send key event to virtual machine.",
2705 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2708 additionalProperties
=> 0,
2710 node
=> get_standard_option
('pve-node'),
2711 vmid
=> get_standard_option
('pve-vmid',
2712 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2713 skiplock
=> get_standard_option
('skiplock'),
2715 description
=> "The key (qemu monitor encoding).",
2720 returns
=> { type
=> 'null'},
2724 my $rpcenv = PVE
::RPCEnvironment
::get
();
2726 my $authuser = $rpcenv->get_user();
2728 my $node = extract_param
($param, 'node');
2730 my $vmid = extract_param
($param, 'vmid');
2732 my $skiplock = extract_param
($param, 'skiplock');
2733 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2734 if $skiplock && $authuser ne 'root@pam';
2736 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2741 __PACKAGE__-
>register_method({
2742 name
=> 'vm_feature',
2743 path
=> '{vmid}/feature',
2747 description
=> "Check if feature for virtual machine is available.",
2749 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2752 additionalProperties
=> 0,
2754 node
=> get_standard_option
('pve-node'),
2755 vmid
=> get_standard_option
('pve-vmid'),
2757 description
=> "Feature to check.",
2759 enum
=> [ 'snapshot', 'clone', 'copy' ],
2761 snapname
=> get_standard_option
('pve-snapshot-name', {
2769 hasFeature
=> { type
=> 'boolean' },
2772 items
=> { type
=> 'string' },
2779 my $node = extract_param
($param, 'node');
2781 my $vmid = extract_param
($param, 'vmid');
2783 my $snapname = extract_param
($param, 'snapname');
2785 my $feature = extract_param
($param, 'feature');
2787 my $running = PVE
::QemuServer
::check_running
($vmid);
2789 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2792 my $snap = $conf->{snapshots
}->{$snapname};
2793 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2796 my $storecfg = PVE
::Storage
::config
();
2798 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2799 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2802 hasFeature
=> $hasFeature,
2803 nodes
=> [ keys %$nodelist ],
2807 __PACKAGE__-
>register_method({
2809 path
=> '{vmid}/clone',
2813 description
=> "Create a copy of virtual machine/template.",
2815 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2816 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2817 "'Datastore.AllocateSpace' on any used storage.",
2820 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2822 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2823 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2828 additionalProperties
=> 0,
2830 node
=> get_standard_option
('pve-node'),
2831 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2832 newid
=> get_standard_option
('pve-vmid', {
2833 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2834 description
=> 'VMID for the clone.' }),
2837 type
=> 'string', format
=> 'dns-name',
2838 description
=> "Set a name for the new VM.",
2843 description
=> "Description for the new VM.",
2847 type
=> 'string', format
=> 'pve-poolid',
2848 description
=> "Add the new VM to the specified pool.",
2850 snapname
=> get_standard_option
('pve-snapshot-name', {
2853 storage
=> get_standard_option
('pve-storage-id', {
2854 description
=> "Target storage for full clone.",
2858 description
=> "Target format for file storage. Only valid for full clone.",
2861 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2866 description
=> "Create a full copy of all disks. This is always done when " .
2867 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2869 target
=> get_standard_option
('pve-node', {
2870 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2874 description
=> "Override I/O bandwidth limit (in KiB/s).",
2878 default => 'clone limit from datacenter or storage config',
2888 my $rpcenv = PVE
::RPCEnvironment
::get
();
2889 my $authuser = $rpcenv->get_user();
2891 my $node = extract_param
($param, 'node');
2892 my $vmid = extract_param
($param, 'vmid');
2893 my $newid = extract_param
($param, 'newid');
2894 my $pool = extract_param
($param, 'pool');
2895 $rpcenv->check_pool_exist($pool) if defined($pool);
2897 my $snapname = extract_param
($param, 'snapname');
2898 my $storage = extract_param
($param, 'storage');
2899 my $format = extract_param
($param, 'format');
2900 my $target = extract_param
($param, 'target');
2902 my $localnode = PVE
::INotify
::nodename
();
2904 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2908 PVE
::Cluster
::check_node_exists
($target) if $target;
2910 my $storecfg = PVE
::Storage
::config
();
2913 # check if storage is enabled on local node
2914 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2916 # check if storage is available on target node
2917 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2918 # clone only works if target storage is shared
2919 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2920 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2924 PVE
::Cluster
::check_cfs_quorum
();
2926 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2929 # do all tests after lock but before forking worker - if possible
2931 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2932 PVE
::QemuConfig-
>check_lock($conf);
2934 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2935 die "unexpected state change\n" if $verify_running != $running;
2937 die "snapshot '$snapname' does not exist\n"
2938 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2940 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2942 die "parameter 'storage' not allowed for linked clones\n"
2943 if defined($storage) && !$full;
2945 die "parameter 'format' not allowed for linked clones\n"
2946 if defined($format) && !$full;
2948 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2950 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2952 die "can't clone VM to node '$target' (VM uses local storage)\n"
2953 if $target && !$sharedvm;
2955 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2956 die "unable to create VM $newid: config file already exists\n"
2959 my $newconf = { lock => 'clone' };
2964 foreach my $opt (keys %$oldconf) {
2965 my $value = $oldconf->{$opt};
2967 # do not copy snapshot related info
2968 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2969 $opt eq 'vmstate' || $opt eq 'snapstate';
2971 # no need to copy unused images, because VMID(owner) changes anyways
2972 next if $opt =~ m/^unused\d+$/;
2974 # always change MAC! address
2975 if ($opt =~ m/^net(\d+)$/) {
2976 my $net = PVE
::QemuServer
::parse_net
($value);
2977 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2978 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2979 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2980 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2981 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2982 die "unable to parse drive options for '$opt'\n" if !$drive;
2983 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2984 $newconf->{$opt} = $value; # simply copy configuration
2986 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2987 die "Full clone feature is not supported for drive '$opt'\n"
2988 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2989 $fullclone->{$opt} = 1;
2991 # not full means clone instead of copy
2992 die "Linked clone feature is not supported for drive '$opt'\n"
2993 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2995 $drives->{$opt} = $drive;
2996 push @$vollist, $drive->{file
};
2999 # copy everything else
3000 $newconf->{$opt} = $value;
3004 # auto generate a new uuid
3005 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3006 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3007 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3008 # auto generate a new vmgenid only if the option was set for template
3009 if ($newconf->{vmgenid
}) {
3010 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3013 delete $newconf->{template
};
3015 if ($param->{name
}) {
3016 $newconf->{name
} = $param->{name
};
3018 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3021 if ($param->{description
}) {
3022 $newconf->{description
} = $param->{description
};
3025 # create empty/temp config - this fails if VM already exists on other node
3026 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3027 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3032 my $newvollist = [];
3039 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3041 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3043 my $bwlimit = extract_param
($param, 'bwlimit');
3045 my $total_jobs = scalar(keys %{$drives});
3048 foreach my $opt (keys %$drives) {
3049 my $drive = $drives->{$opt};
3050 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3051 my $completion = $skipcomplete ?
'skip' : 'complete';
3053 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3054 my $storage_list = [ $src_sid ];
3055 push @$storage_list, $storage if defined($storage);
3056 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3058 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3059 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3060 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3062 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3064 PVE
::QemuConfig-
>write_config($newid, $newconf);
3068 delete $newconf->{lock};
3070 # do not write pending changes
3071 if (my @changes = keys %{$newconf->{pending
}}) {
3072 my $pending = join(',', @changes);
3073 warn "found pending changes for '$pending', discarding for clone\n";
3074 delete $newconf->{pending
};
3077 PVE
::QemuConfig-
>write_config($newid, $newconf);
3080 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3081 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3082 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3084 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3085 die "Failed to move config to node '$target' - rename failed: $!\n"
3086 if !rename($conffile, $newconffile);
3089 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3092 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3093 sleep 1; # some storage like rbd need to wait before release volume - really?
3095 foreach my $volid (@$newvollist) {
3096 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3100 PVE
::Firewall
::remove_vmfw_conf
($newid);
3102 unlink $conffile; # avoid races -> last thing before die
3104 die "clone failed: $err";
3110 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3112 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3115 # Aquire exclusive lock lock for $newid
3116 my $lock_target_vm = sub {
3117 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3120 # exclusive lock if VM is running - else shared lock is enough;
3122 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3124 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3128 __PACKAGE__-
>register_method({
3129 name
=> 'move_vm_disk',
3130 path
=> '{vmid}/move_disk',
3134 description
=> "Move volume to different storage.",
3136 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3138 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3139 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3143 additionalProperties
=> 0,
3145 node
=> get_standard_option
('pve-node'),
3146 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3149 description
=> "The disk you want to move.",
3150 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3152 storage
=> get_standard_option
('pve-storage-id', {
3153 description
=> "Target storage.",
3154 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3158 description
=> "Target Format.",
3159 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3164 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3170 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3175 description
=> "Override I/O bandwidth limit (in KiB/s).",
3179 default => 'move limit from datacenter or storage config',
3185 description
=> "the task ID.",
3190 my $rpcenv = PVE
::RPCEnvironment
::get
();
3191 my $authuser = $rpcenv->get_user();
3193 my $node = extract_param
($param, 'node');
3194 my $vmid = extract_param
($param, 'vmid');
3195 my $digest = extract_param
($param, 'digest');
3196 my $disk = extract_param
($param, 'disk');
3197 my $storeid = extract_param
($param, 'storage');
3198 my $format = extract_param
($param, 'format');
3200 my $storecfg = PVE
::Storage
::config
();
3202 my $updatefn = sub {
3203 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3204 PVE
::QemuConfig-
>check_lock($conf);
3206 die "VM config checksum missmatch (file change by other user?)\n"
3207 if $digest && $digest ne $conf->{digest
};
3209 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3211 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3213 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3214 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3216 my $old_volid = $drive->{file
};
3218 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3219 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3223 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3224 (!$format || !$oldfmt || $oldfmt eq $format);
3226 # this only checks snapshots because $disk is passed!
3227 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3228 die "you can't move a disk with snapshots and delete the source\n"
3229 if $snapshotted && $param->{delete};
3231 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3233 my $running = PVE
::QemuServer
::check_running
($vmid);
3235 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3238 my $newvollist = [];
3244 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3246 warn "moving disk with snapshots, snapshots will not be moved!\n"
3249 my $bwlimit = extract_param
($param, 'bwlimit');
3250 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3252 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3253 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3255 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3257 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3259 # convert moved disk to base if part of template
3260 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3261 if PVE
::QemuConfig-
>is_template($conf);
3263 PVE
::QemuConfig-
>write_config($vmid, $conf);
3265 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3266 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3267 eval { mon_cmd
($vmid, "guest-fstrim") };
3271 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3272 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3278 foreach my $volid (@$newvollist) {
3279 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3282 die "storage migration failed: $err";
3285 if ($param->{delete}) {
3287 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3288 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3294 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3297 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3300 my $check_vm_disks_local = sub {
3301 my ($storecfg, $vmconf, $vmid) = @_;
3303 my $local_disks = {};
3305 # add some more information to the disks e.g. cdrom
3306 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3307 my ($volid, $attr) = @_;
3309 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3311 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3312 return if $scfg->{shared
};
3314 # The shared attr here is just a special case where the vdisk
3315 # is marked as shared manually
3316 return if $attr->{shared
};
3317 return if $attr->{cdrom
} and $volid eq "none";
3319 if (exists $local_disks->{$volid}) {
3320 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3322 $local_disks->{$volid} = $attr;
3323 # ensure volid is present in case it's needed
3324 $local_disks->{$volid}->{volid
} = $volid;
3328 return $local_disks;
3331 __PACKAGE__-
>register_method({
3332 name
=> 'migrate_vm_precondition',
3333 path
=> '{vmid}/migrate',
3337 description
=> "Get preconditions for migration.",
3339 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3342 additionalProperties
=> 0,
3344 node
=> get_standard_option
('pve-node'),
3345 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3346 target
=> get_standard_option
('pve-node', {
3347 description
=> "Target node.",
3348 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3356 running
=> { type
=> 'boolean' },
3360 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3362 not_allowed_nodes
=> {
3365 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3369 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3371 local_resources
=> {
3373 description
=> "List local resources e.g. pci, usb"
3380 my $rpcenv = PVE
::RPCEnvironment
::get
();
3382 my $authuser = $rpcenv->get_user();
3384 PVE
::Cluster
::check_cfs_quorum
();
3388 my $vmid = extract_param
($param, 'vmid');
3389 my $target = extract_param
($param, 'target');
3390 my $localnode = PVE
::INotify
::nodename
();
3394 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3395 my $storecfg = PVE
::Storage
::config
();
3398 # try to detect errors early
3399 PVE
::QemuConfig-
>check_lock($vmconf);
3401 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3403 # if vm is not running, return target nodes where local storage is available
3404 # for offline migration
3405 if (!$res->{running
}) {
3406 $res->{allowed_nodes
} = [];
3407 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3408 delete $checked_nodes->{$localnode};
3410 foreach my $node (keys %$checked_nodes) {
3411 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3412 push @{$res->{allowed_nodes
}}, $node;
3416 $res->{not_allowed_nodes
} = $checked_nodes;
3420 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3421 $res->{local_disks
} = [ values %$local_disks ];;
3423 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3425 $res->{local_resources
} = $local_resources;
3432 __PACKAGE__-
>register_method({
3433 name
=> 'migrate_vm',
3434 path
=> '{vmid}/migrate',
3438 description
=> "Migrate virtual machine. Creates a new migration task.",
3440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3443 additionalProperties
=> 0,
3445 node
=> get_standard_option
('pve-node'),
3446 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3447 target
=> get_standard_option
('pve-node', {
3448 description
=> "Target node.",
3449 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3453 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3458 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3463 enum
=> ['secure', 'insecure'],
3464 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3467 migration_network
=> {
3468 type
=> 'string', format
=> 'CIDR',
3469 description
=> "CIDR of the (sub) network that is used for migration.",
3472 "with-local-disks" => {
3474 description
=> "Enable live storage migration for local disk",
3477 targetstorage
=> get_standard_option
('pve-targetstorage', {
3478 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3481 description
=> "Override I/O bandwidth limit (in KiB/s).",
3485 default => 'migrate limit from datacenter or storage config',
3491 description
=> "the task ID.",
3496 my $rpcenv = PVE
::RPCEnvironment
::get
();
3497 my $authuser = $rpcenv->get_user();
3499 my $target = extract_param
($param, 'target');
3501 my $localnode = PVE
::INotify
::nodename
();
3502 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3504 PVE
::Cluster
::check_cfs_quorum
();
3506 PVE
::Cluster
::check_node_exists
($target);
3508 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3510 my $vmid = extract_param
($param, 'vmid');
3512 raise_param_exc
({ force
=> "Only root may use this option." })
3513 if $param->{force
} && $authuser ne 'root@pam';
3515 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3516 if $param->{migration_type
} && $authuser ne 'root@pam';
3518 # allow root only until better network permissions are available
3519 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3520 if $param->{migration_network
} && $authuser ne 'root@pam';
3523 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3525 # try to detect errors early
3527 PVE
::QemuConfig-
>check_lock($conf);
3529 if (PVE
::QemuServer
::check_running
($vmid)) {
3530 die "can't migrate running VM without --online\n" if !$param->{online
};
3532 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3533 $param->{online
} = 0;
3536 my $storecfg = PVE
::Storage
::config
();
3538 if (my $targetstorage = $param->{targetstorage
}) {
3539 my $check_storage = sub {
3540 my ($target_sid) = @_;
3541 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3542 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3543 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3544 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3545 if !$scfg->{content
}->{images
};
3548 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3549 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3552 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3553 if !defined($storagemap->{identity
});
3555 foreach my $source (values %{$storagemap->{entries
}}) {
3556 $check_storage->($source);
3559 $check_storage->($storagemap->{default})
3560 if $storagemap->{default};
3562 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3563 if $storagemap->{identity
};
3565 $param->{storagemap
} = $storagemap;
3567 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3570 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3575 print "Requesting HA migration for VM $vmid to node $target\n";
3577 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3578 PVE
::Tools
::run_command
($cmd);
3582 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3587 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3591 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3594 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3599 __PACKAGE__-
>register_method({
3601 path
=> '{vmid}/monitor',
3605 description
=> "Execute Qemu monitor commands.",
3607 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3608 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3611 additionalProperties
=> 0,
3613 node
=> get_standard_option
('pve-node'),
3614 vmid
=> get_standard_option
('pve-vmid'),
3617 description
=> "The monitor command.",
3621 returns
=> { type
=> 'string'},
3625 my $rpcenv = PVE
::RPCEnvironment
::get
();
3626 my $authuser = $rpcenv->get_user();
3629 my $command = shift;
3630 return $command =~ m/^\s*info(\s+|$)/
3631 || $command =~ m/^\s*help\s*$/;
3634 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3635 if !&$is_ro($param->{command
});
3637 my $vmid = $param->{vmid
};
3639 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3643 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3645 $res = "ERROR: $@" if $@;
3650 __PACKAGE__-
>register_method({
3651 name
=> 'resize_vm',
3652 path
=> '{vmid}/resize',
3656 description
=> "Extend volume size.",
3658 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3661 additionalProperties
=> 0,
3663 node
=> get_standard_option
('pve-node'),
3664 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3665 skiplock
=> get_standard_option
('skiplock'),
3668 description
=> "The disk you want to resize.",
3669 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3673 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3674 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.",
3678 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3684 returns
=> { type
=> 'null'},
3688 my $rpcenv = PVE
::RPCEnvironment
::get
();
3690 my $authuser = $rpcenv->get_user();
3692 my $node = extract_param
($param, 'node');
3694 my $vmid = extract_param
($param, 'vmid');
3696 my $digest = extract_param
($param, 'digest');
3698 my $disk = extract_param
($param, 'disk');
3700 my $sizestr = extract_param
($param, 'size');
3702 my $skiplock = extract_param
($param, 'skiplock');
3703 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3704 if $skiplock && $authuser ne 'root@pam';
3706 my $storecfg = PVE
::Storage
::config
();
3708 my $updatefn = sub {
3710 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3712 die "checksum missmatch (file change by other user?)\n"
3713 if $digest && $digest ne $conf->{digest
};
3714 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3716 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3718 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3720 my (undef, undef, undef, undef, undef, undef, $format) =
3721 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3723 die "can't resize volume: $disk if snapshot exists\n"
3724 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3726 my $volid = $drive->{file
};
3728 die "disk '$disk' has no associated volume\n" if !$volid;
3730 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3732 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3734 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3736 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3737 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3739 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3741 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3742 my ($ext, $newsize, $unit) = ($1, $2, $4);
3745 $newsize = $newsize * 1024;
3746 } elsif ($unit eq 'M') {
3747 $newsize = $newsize * 1024 * 1024;
3748 } elsif ($unit eq 'G') {
3749 $newsize = $newsize * 1024 * 1024 * 1024;
3750 } elsif ($unit eq 'T') {
3751 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3754 $newsize += $size if $ext;
3755 $newsize = int($newsize);
3757 die "shrinking disks is not supported\n" if $newsize < $size;
3759 return if $size == $newsize;
3761 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3763 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3765 $drive->{size
} = $newsize;
3766 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3768 PVE
::QemuConfig-
>write_config($vmid, $conf);
3771 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3775 __PACKAGE__-
>register_method({
3776 name
=> 'snapshot_list',
3777 path
=> '{vmid}/snapshot',
3779 description
=> "List all snapshots.",
3781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3784 protected
=> 1, # qemu pid files are only readable by root
3786 additionalProperties
=> 0,
3788 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3789 node
=> get_standard_option
('pve-node'),
3798 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3802 description
=> "Snapshot includes RAM.",
3807 description
=> "Snapshot description.",
3811 description
=> "Snapshot creation time",
3813 renderer
=> 'timestamp',
3817 description
=> "Parent snapshot identifier.",
3823 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3828 my $vmid = $param->{vmid
};
3830 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3831 my $snaphash = $conf->{snapshots
} || {};
3835 foreach my $name (keys %$snaphash) {
3836 my $d = $snaphash->{$name};
3839 snaptime
=> $d->{snaptime
} || 0,
3840 vmstate
=> $d->{vmstate
} ?
1 : 0,
3841 description
=> $d->{description
} || '',
3843 $item->{parent
} = $d->{parent
} if $d->{parent
};
3844 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3848 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3851 digest
=> $conf->{digest
},
3852 running
=> $running,
3853 description
=> "You are here!",
3855 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3857 push @$res, $current;
3862 __PACKAGE__-
>register_method({
3864 path
=> '{vmid}/snapshot',
3868 description
=> "Snapshot a VM.",
3870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3873 additionalProperties
=> 0,
3875 node
=> get_standard_option
('pve-node'),
3876 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3877 snapname
=> get_standard_option
('pve-snapshot-name'),
3881 description
=> "Save the vmstate",
3886 description
=> "A textual description or comment.",
3892 description
=> "the task ID.",
3897 my $rpcenv = PVE
::RPCEnvironment
::get
();
3899 my $authuser = $rpcenv->get_user();
3901 my $node = extract_param
($param, 'node');
3903 my $vmid = extract_param
($param, 'vmid');
3905 my $snapname = extract_param
($param, 'snapname');
3907 die "unable to use snapshot name 'current' (reserved name)\n"
3908 if $snapname eq 'current';
3910 die "unable to use snapshot name 'pending' (reserved name)\n"
3911 if lc($snapname) eq 'pending';
3914 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3915 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3916 $param->{description
});
3919 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3922 __PACKAGE__-
>register_method({
3923 name
=> 'snapshot_cmd_idx',
3924 path
=> '{vmid}/snapshot/{snapname}',
3931 additionalProperties
=> 0,
3933 vmid
=> get_standard_option
('pve-vmid'),
3934 node
=> get_standard_option
('pve-node'),
3935 snapname
=> get_standard_option
('pve-snapshot-name'),
3944 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3951 push @$res, { cmd
=> 'rollback' };
3952 push @$res, { cmd
=> 'config' };
3957 __PACKAGE__-
>register_method({
3958 name
=> 'update_snapshot_config',
3959 path
=> '{vmid}/snapshot/{snapname}/config',
3963 description
=> "Update snapshot metadata.",
3965 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3968 additionalProperties
=> 0,
3970 node
=> get_standard_option
('pve-node'),
3971 vmid
=> get_standard_option
('pve-vmid'),
3972 snapname
=> get_standard_option
('pve-snapshot-name'),
3976 description
=> "A textual description or comment.",
3980 returns
=> { type
=> 'null' },
3984 my $rpcenv = PVE
::RPCEnvironment
::get
();
3986 my $authuser = $rpcenv->get_user();
3988 my $vmid = extract_param
($param, 'vmid');
3990 my $snapname = extract_param
($param, 'snapname');
3992 return undef if !defined($param->{description
});
3994 my $updatefn = sub {
3996 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3998 PVE
::QemuConfig-
>check_lock($conf);
4000 my $snap = $conf->{snapshots
}->{$snapname};
4002 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4004 $snap->{description
} = $param->{description
} if defined($param->{description
});
4006 PVE
::QemuConfig-
>write_config($vmid, $conf);
4009 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4014 __PACKAGE__-
>register_method({
4015 name
=> 'get_snapshot_config',
4016 path
=> '{vmid}/snapshot/{snapname}/config',
4019 description
=> "Get snapshot configuration",
4021 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4024 additionalProperties
=> 0,
4026 node
=> get_standard_option
('pve-node'),
4027 vmid
=> get_standard_option
('pve-vmid'),
4028 snapname
=> get_standard_option
('pve-snapshot-name'),
4031 returns
=> { type
=> "object" },
4035 my $rpcenv = PVE
::RPCEnvironment
::get
();
4037 my $authuser = $rpcenv->get_user();
4039 my $vmid = extract_param
($param, 'vmid');
4041 my $snapname = extract_param
($param, 'snapname');
4043 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4045 my $snap = $conf->{snapshots
}->{$snapname};
4047 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4052 __PACKAGE__-
>register_method({
4054 path
=> '{vmid}/snapshot/{snapname}/rollback',
4058 description
=> "Rollback VM state to specified snapshot.",
4060 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4063 additionalProperties
=> 0,
4065 node
=> get_standard_option
('pve-node'),
4066 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4067 snapname
=> get_standard_option
('pve-snapshot-name'),
4072 description
=> "the task ID.",
4077 my $rpcenv = PVE
::RPCEnvironment
::get
();
4079 my $authuser = $rpcenv->get_user();
4081 my $node = extract_param
($param, 'node');
4083 my $vmid = extract_param
($param, 'vmid');
4085 my $snapname = extract_param
($param, 'snapname');
4088 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4089 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4093 # hold migration lock, this makes sure that nobody create replication snapshots
4094 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4097 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4100 __PACKAGE__-
>register_method({
4101 name
=> 'delsnapshot',
4102 path
=> '{vmid}/snapshot/{snapname}',
4106 description
=> "Delete a VM snapshot.",
4108 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4111 additionalProperties
=> 0,
4113 node
=> get_standard_option
('pve-node'),
4114 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4115 snapname
=> get_standard_option
('pve-snapshot-name'),
4119 description
=> "For removal from config file, even if removing disk snapshots fails.",
4125 description
=> "the task ID.",
4130 my $rpcenv = PVE
::RPCEnvironment
::get
();
4132 my $authuser = $rpcenv->get_user();
4134 my $node = extract_param
($param, 'node');
4136 my $vmid = extract_param
($param, 'vmid');
4138 my $snapname = extract_param
($param, 'snapname');
4141 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4142 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4145 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4148 __PACKAGE__-
>register_method({
4150 path
=> '{vmid}/template',
4154 description
=> "Create a Template.",
4156 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4157 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4160 additionalProperties
=> 0,
4162 node
=> get_standard_option
('pve-node'),
4163 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4167 description
=> "If you want to convert only 1 disk to base image.",
4168 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4173 returns
=> { type
=> 'null'},
4177 my $rpcenv = PVE
::RPCEnvironment
::get
();
4179 my $authuser = $rpcenv->get_user();
4181 my $node = extract_param
($param, 'node');
4183 my $vmid = extract_param
($param, 'vmid');
4185 my $disk = extract_param
($param, 'disk');
4187 my $updatefn = sub {
4189 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4191 PVE
::QemuConfig-
>check_lock($conf);
4193 die "unable to create template, because VM contains snapshots\n"
4194 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4196 die "you can't convert a template to a template\n"
4197 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4199 die "you can't convert a VM to template if VM is running\n"
4200 if PVE
::QemuServer
::check_running
($vmid);
4203 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4206 $conf->{template
} = 1;
4207 PVE
::QemuConfig-
>write_config($vmid, $conf);
4209 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4212 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4216 __PACKAGE__-
>register_method({
4217 name
=> 'cloudinit_generated_config_dump',
4218 path
=> '{vmid}/cloudinit/dump',
4221 description
=> "Get automatically generated cloudinit config.",
4223 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4226 additionalProperties
=> 0,
4228 node
=> get_standard_option
('pve-node'),
4229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4231 description
=> 'Config type.',
4233 enum
=> ['user', 'network', 'meta'],
4243 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4245 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});