1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::CPUConfig
;
25 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
27 use PVE
::RPCEnvironment
;
28 use PVE
::AccessControl
;
32 use PVE
::API2
::Firewall
::VM
;
33 use PVE
::API2
::Qemu
::Agent
;
34 use PVE
::VZDump
::Plugin
;
35 use PVE
::DataCenterConfig
;
39 if (!$ENV{PVE_GENERATING_DOCS
}) {
40 require PVE
::HA
::Env
::PVE2
;
41 import PVE
::HA
::Env
::PVE2
;
42 require PVE
::HA
::Config
;
43 import PVE
::HA
::Config
;
47 use Data
::Dumper
; # fixme: remove
49 use base
qw(PVE::RESTHandler);
51 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.";
53 my $resolve_cdrom_alias = sub {
56 if (my $value = $param->{cdrom
}) {
57 $value .= ",media=cdrom" if $value !~ m/media=/;
58 $param->{ide2
} = $value;
59 delete $param->{cdrom
};
63 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
64 my $check_storage_access = sub {
65 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
67 PVE
::QemuConfig-
>foreach_volume($settings, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
73 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
75 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
77 } elsif ($isCDROM && ($volid eq 'cdrom')) {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
79 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
80 my ($storeid, $size) = ($2 || $default_storage, $3);
81 die "no storage ID specified (and no default storage)\n" if !$storeid;
82 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
83 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
84 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
85 if !$scfg->{content
}->{images
};
87 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
91 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
92 if defined($settings->{vmstatestorage
});
95 my $check_storage_access_clone = sub {
96 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
100 PVE
::QemuConfig-
>foreach_volume($conf, sub {
101 my ($ds, $drive) = @_;
103 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
105 my $volid = $drive->{file
};
107 return if !$volid || $volid eq 'none';
110 if ($volid eq 'cdrom') {
111 $rpcenv->check($authuser, "/", ['Sys.Console']);
113 # we simply allow access
114 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
115 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
116 $sharedvm = 0 if !$scfg->{shared
};
120 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
121 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
122 $sharedvm = 0 if !$scfg->{shared
};
124 $sid = $storage if $storage;
125 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
129 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
130 if defined($conf->{vmstatestorage
});
135 # Note: $pool is only needed when creating a VM, because pool permissions
136 # are automatically inherited if VM already exists inside a pool.
137 my $create_disks = sub {
138 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
145 my ($ds, $disk) = @_;
147 my $volid = $disk->{file
};
148 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
150 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
151 delete $disk->{size
};
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
153 } elsif (defined($volname) && $volname eq 'cloudinit') {
154 $storeid = $storeid // $default_storage;
155 die "no storage ID specified (and no default storage)\n" if !$storeid;
156 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
157 my $name = "vm-$vmid-cloudinit";
161 $fmt = $disk->{format
} // "qcow2";
164 $fmt = $disk->{format
} // "raw";
167 # Initial disk created with 4 MB and aligned to 4MB on regeneration
168 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
169 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
170 $disk->{file
} = $volid;
171 $disk->{media
} = 'cdrom';
172 push @$vollist, $volid;
173 delete $disk->{format
}; # no longer needed
174 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
175 } elsif ($volid =~ $NEW_DISK_RE) {
176 my ($storeid, $size) = ($2 || $default_storage, $3);
177 die "no storage ID specified (and no default storage)\n" if !$storeid;
178 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
179 my $fmt = $disk->{format
} || $defformat;
181 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
184 if ($ds eq 'efidisk0') {
185 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
187 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
189 push @$vollist, $volid;
190 $disk->{file
} = $volid;
191 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
192 delete $disk->{format
}; # no longer needed
193 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
196 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
198 my $volid_is_new = 1;
201 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
202 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
207 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
209 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
211 die "volume $volid does not exist\n" if !$size;
213 $disk->{size
} = $size;
216 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
220 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
222 # free allocated images on error
224 syslog
('err', "VM $vmid creating disks failed");
225 foreach my $volid (@$vollist) {
226 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
232 # modify vm config if everything went well
233 foreach my $ds (keys %$res) {
234 $conf->{$ds} = $res->{$ds};
240 my $check_cpu_model_access = sub {
241 my ($rpcenv, $authuser, $new, $existing) = @_;
243 return if !defined($new->{cpu
});
245 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
246 return if !$cpu || !$cpu->{cputype
}; # always allow default
247 my $cputype = $cpu->{cputype
};
249 if ($existing && $existing->{cpu
}) {
250 # changing only other settings doesn't require permissions for CPU model
251 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
252 return if $existingCpu->{cputype
} eq $cputype;
255 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
256 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
271 my $memoryoptions = {
277 my $hwtypeoptions = {
290 my $generaloptions = {
297 'migrate_downtime' => 1,
298 'migrate_speed' => 1,
311 my $vmpoweroptions = {
318 'vmstatestorage' => 1,
321 my $cloudinitoptions = {
331 my $check_vm_modify_config_perm = sub {
332 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
334 return 1 if $authuser eq 'root@pam';
336 foreach my $opt (@$key_list) {
337 # some checks (e.g., disk, serial port, usb) need to be done somewhere
338 # else, as there the permission can be value dependend
339 next if PVE
::QemuServer
::is_valid_drivename
($opt);
340 next if $opt eq 'cdrom';
341 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
344 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
345 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
346 } elsif ($memoryoptions->{$opt}) {
347 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
348 } elsif ($hwtypeoptions->{$opt}) {
349 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
350 } elsif ($generaloptions->{$opt}) {
351 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
352 # special case for startup since it changes host behaviour
353 if ($opt eq 'startup') {
354 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
356 } elsif ($vmpoweroptions->{$opt}) {
357 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
358 } elsif ($diskoptions->{$opt}) {
359 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
360 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
361 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
362 } elsif ($opt eq 'vmstate') {
363 # the user needs Disk and PowerMgmt privileges to change the vmstate
364 # also needs privileges on the storage, that will be checked later
365 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
367 # catches hostpci\d+, args, lock, etc.
368 # new options will be checked here
369 die "only root can set '$opt' config\n";
376 __PACKAGE__-
>register_method({
380 description
=> "Virtual machine index (per node).",
382 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
386 protected
=> 1, # qemu pid files are only readable by root
388 additionalProperties
=> 0,
390 node
=> get_standard_option
('pve-node'),
394 description
=> "Determine the full status of active VMs.",
402 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
404 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
409 my $rpcenv = PVE
::RPCEnvironment
::get
();
410 my $authuser = $rpcenv->get_user();
412 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
415 foreach my $vmid (keys %$vmstatus) {
416 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
418 my $data = $vmstatus->{$vmid};
425 my $parse_restore_archive = sub {
426 my ($storecfg, $archive) = @_;
428 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
430 if (defined($archive_storeid)) {
431 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
432 if ($scfg->{type
} eq 'pbs') {
439 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
447 __PACKAGE__-
>register_method({
451 description
=> "Create or restore a virtual machine.",
453 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
454 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
455 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
456 user
=> 'all', # check inside
461 additionalProperties
=> 0,
462 properties
=> PVE
::QemuServer
::json_config_properties
(
464 node
=> get_standard_option
('pve-node'),
465 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
467 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.",
471 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
473 storage
=> get_standard_option
('pve-storage-id', {
474 description
=> "Default storage.",
476 completion
=> \
&PVE
::QemuServer
::complete_storage
,
481 description
=> "Allow to overwrite existing VM.",
482 requires
=> 'archive',
487 description
=> "Assign a unique random ethernet address.",
488 requires
=> 'archive',
492 type
=> 'string', format
=> 'pve-poolid',
493 description
=> "Add the VM to the specified pool.",
496 description
=> "Override I/O bandwidth limit (in KiB/s).",
500 default => 'restore limit from datacenter or storage config',
506 description
=> "Start VM after it was created successfully.",
516 my $rpcenv = PVE
::RPCEnvironment
::get
();
517 my $authuser = $rpcenv->get_user();
519 my $node = extract_param
($param, 'node');
520 my $vmid = extract_param
($param, 'vmid');
522 my $archive = extract_param
($param, 'archive');
523 my $is_restore = !!$archive;
525 my $bwlimit = extract_param
($param, 'bwlimit');
526 my $force = extract_param
($param, 'force');
527 my $pool = extract_param
($param, 'pool');
528 my $start_after_create = extract_param
($param, 'start');
529 my $storage = extract_param
($param, 'storage');
530 my $unique = extract_param
($param, 'unique');
532 if (defined(my $ssh_keys = $param->{sshkeys
})) {
533 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
534 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
537 PVE
::Cluster
::check_cfs_quorum
();
539 my $filename = PVE
::QemuConfig-
>config_file($vmid);
540 my $storecfg = PVE
::Storage
::config
();
542 if (defined($pool)) {
543 $rpcenv->check_pool_exist($pool);
546 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
547 if defined($storage);
549 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
551 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
553 } elsif ($archive && $force && (-f
$filename) &&
554 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
555 # OK: user has VM.Backup permissions, and want to restore an existing VM
561 &$resolve_cdrom_alias($param);
563 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
565 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
567 &$check_cpu_model_access($rpcenv, $authuser, $param);
569 foreach my $opt (keys %$param) {
570 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
571 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
572 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
574 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
575 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
579 PVE
::QemuServer
::add_random_macs
($param);
581 my $keystr = join(' ', keys %$param);
582 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
584 if ($archive eq '-') {
585 die "pipe requires cli environment\n"
586 if $rpcenv->{type
} ne 'cli';
587 $archive = { type
=> 'pipe' };
589 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
591 $archive = $parse_restore_archive->($storecfg, $archive);
595 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
597 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
598 die "$emsg $@" if $@;
600 my $restorefn = sub {
601 my $conf = PVE
::QemuConfig-
>load_config($vmid);
603 PVE
::QemuConfig-
>check_protection($conf, $emsg);
605 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
608 my $restore_options = {
614 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
615 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
616 } elsif ($archive->{type
} eq 'pbs') {
617 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
619 die "unknown backup archive type\n";
621 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
622 # Convert restored VM to template if backup was VM template
623 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
624 warn "Convert to template.\n";
625 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
629 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
632 # ensure no old replication state are exists
633 PVE
::ReplicationState
::delete_guest_states
($vmid);
635 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
637 if ($start_after_create) {
638 print "Execute autostart\n";
639 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
645 # ensure no old replication state are exists
646 PVE
::ReplicationState
::delete_guest_states
($vmid);
650 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
654 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
656 if (!$conf->{bootdisk
}) {
657 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
658 $conf->{bootdisk
} = $firstdisk if $firstdisk;
661 # auto generate uuid if user did not specify smbios1 option
662 if (!$conf->{smbios1
}) {
663 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
666 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
667 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
670 PVE
::QemuConfig-
>write_config($vmid, $conf);
676 foreach my $volid (@$vollist) {
677 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
683 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
686 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
688 if ($start_after_create) {
689 print "Execute autostart\n";
690 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
695 my ($code, $worker_name);
697 $worker_name = 'qmrestore';
699 eval { $restorefn->() };
701 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
707 $worker_name = 'qmcreate';
709 eval { $createfn->() };
712 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
713 unlink($conffile) or die "failed to remove config file: $!\n";
721 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
724 __PACKAGE__-
>register_method({
729 description
=> "Directory index",
734 additionalProperties
=> 0,
736 node
=> get_standard_option
('pve-node'),
737 vmid
=> get_standard_option
('pve-vmid'),
745 subdir
=> { type
=> 'string' },
748 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
754 { subdir
=> 'config' },
755 { subdir
=> 'pending' },
756 { subdir
=> 'status' },
757 { subdir
=> 'unlink' },
758 { subdir
=> 'vncproxy' },
759 { subdir
=> 'termproxy' },
760 { subdir
=> 'migrate' },
761 { subdir
=> 'resize' },
762 { subdir
=> 'move' },
764 { subdir
=> 'rrddata' },
765 { subdir
=> 'monitor' },
766 { subdir
=> 'agent' },
767 { subdir
=> 'snapshot' },
768 { subdir
=> 'spiceproxy' },
769 { subdir
=> 'sendkey' },
770 { subdir
=> 'firewall' },
776 __PACKAGE__-
>register_method ({
777 subclass
=> "PVE::API2::Firewall::VM",
778 path
=> '{vmid}/firewall',
781 __PACKAGE__-
>register_method ({
782 subclass
=> "PVE::API2::Qemu::Agent",
783 path
=> '{vmid}/agent',
786 __PACKAGE__-
>register_method({
788 path
=> '{vmid}/rrd',
790 protected
=> 1, # fixme: can we avoid that?
792 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
794 description
=> "Read VM RRD statistics (returns PNG)",
796 additionalProperties
=> 0,
798 node
=> get_standard_option
('pve-node'),
799 vmid
=> get_standard_option
('pve-vmid'),
801 description
=> "Specify the time frame you are interested in.",
803 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
806 description
=> "The list of datasources you want to display.",
807 type
=> 'string', format
=> 'pve-configid-list',
810 description
=> "The RRD consolidation function",
812 enum
=> [ 'AVERAGE', 'MAX' ],
820 filename
=> { type
=> 'string' },
826 return PVE
::RRD
::create_rrd_graph
(
827 "pve2-vm/$param->{vmid}", $param->{timeframe
},
828 $param->{ds
}, $param->{cf
});
832 __PACKAGE__-
>register_method({
834 path
=> '{vmid}/rrddata',
836 protected
=> 1, # fixme: can we avoid that?
838 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
840 description
=> "Read VM RRD statistics",
842 additionalProperties
=> 0,
844 node
=> get_standard_option
('pve-node'),
845 vmid
=> get_standard_option
('pve-vmid'),
847 description
=> "Specify the time frame you are interested in.",
849 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
852 description
=> "The RRD consolidation function",
854 enum
=> [ 'AVERAGE', 'MAX' ],
869 return PVE
::RRD
::create_rrd_data
(
870 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
874 __PACKAGE__-
>register_method({
876 path
=> '{vmid}/config',
879 description
=> "Get the virtual machine configuration with pending configuration " .
880 "changes applied. Set the 'current' parameter to get the current configuration instead.",
882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
885 additionalProperties
=> 0,
887 node
=> get_standard_option
('pve-node'),
888 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
890 description
=> "Get current values (instead of pending values).",
895 snapshot
=> get_standard_option
('pve-snapshot-name', {
896 description
=> "Fetch config values from given snapshot.",
899 my ($cmd, $pname, $cur, $args) = @_;
900 PVE
::QemuConfig-
>snapshot_list($args->[0]);
906 description
=> "The VM configuration.",
908 properties
=> PVE
::QemuServer
::json_config_properties
({
911 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
918 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
919 current
=> "cannot use 'snapshot' parameter with 'current'"})
920 if ($param->{snapshot
} && $param->{current
});
923 if ($param->{snapshot
}) {
924 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
926 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
928 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
933 __PACKAGE__-
>register_method({
934 name
=> 'vm_pending',
935 path
=> '{vmid}/pending',
938 description
=> "Get the virtual machine configuration with both current and pending values.",
940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
943 additionalProperties
=> 0,
945 node
=> get_standard_option
('pve-node'),
946 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
955 description
=> "Configuration option name.",
959 description
=> "Current value.",
964 description
=> "Pending value.",
969 description
=> "Indicates a pending delete request if present and not 0. " .
970 "The value 2 indicates a force-delete request.",
982 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
984 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
986 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
987 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
989 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
992 # POST/PUT {vmid}/config implementation
994 # The original API used PUT (idempotent) an we assumed that all operations
995 # are fast. But it turned out that almost any configuration change can
996 # involve hot-plug actions, or disk alloc/free. Such actions can take long
997 # time to complete and have side effects (not idempotent).
999 # The new implementation uses POST and forks a worker process. We added
1000 # a new option 'background_delay'. If specified we wait up to
1001 # 'background_delay' second for the worker task to complete. It returns null
1002 # if the task is finished within that time, else we return the UPID.
1004 my $update_vm_api = sub {
1005 my ($param, $sync) = @_;
1007 my $rpcenv = PVE
::RPCEnvironment
::get
();
1009 my $authuser = $rpcenv->get_user();
1011 my $node = extract_param
($param, 'node');
1013 my $vmid = extract_param
($param, 'vmid');
1015 my $digest = extract_param
($param, 'digest');
1017 my $background_delay = extract_param
($param, 'background_delay');
1019 if (defined(my $cipassword = $param->{cipassword
})) {
1020 # Same logic as in cloud-init (but with the regex fixed...)
1021 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1022 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1025 my @paramarr = (); # used for log message
1026 foreach my $key (sort keys %$param) {
1027 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1028 push @paramarr, "-$key", $value;
1031 my $skiplock = extract_param
($param, 'skiplock');
1032 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1033 if $skiplock && $authuser ne 'root@pam';
1035 my $delete_str = extract_param
($param, 'delete');
1037 my $revert_str = extract_param
($param, 'revert');
1039 my $force = extract_param
($param, 'force');
1041 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1042 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1043 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1046 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1048 my $storecfg = PVE
::Storage
::config
();
1050 my $defaults = PVE
::QemuServer
::load_defaults
();
1052 &$resolve_cdrom_alias($param);
1054 # now try to verify all parameters
1057 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1062 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1063 "-revert $opt' at the same time" })
1064 if defined($param->{$opt});
1066 $revert->{$opt} = 1;
1070 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1071 $opt = 'ide2' if $opt eq 'cdrom';
1073 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1074 "-delete $opt' at the same time" })
1075 if defined($param->{$opt});
1077 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1078 "-revert $opt' at the same time" })
1081 if (!PVE
::QemuServer
::option_exists
($opt)) {
1082 raise_param_exc
({ delete => "unknown option '$opt'" });
1088 my $repl_conf = PVE
::ReplicationConfig-
>new();
1089 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1090 my $check_replication = sub {
1092 return if !$is_replicated;
1093 my $volid = $drive->{file
};
1094 return if !$volid || !($drive->{replicate
}//1);
1095 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1097 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1098 return if defined($volname) && $volname eq 'cloudinit';
1101 if ($volid =~ $NEW_DISK_RE) {
1103 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1105 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1107 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1108 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1109 return if $scfg->{shared
};
1110 die "cannot add non-replicatable volume to a replicated VM\n";
1113 foreach my $opt (keys %$param) {
1114 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1115 # cleanup drive path
1116 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1117 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1118 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1119 $check_replication->($drive);
1120 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1121 } elsif ($opt =~ m/^net(\d+)$/) {
1123 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1124 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1125 } elsif ($opt eq 'vmgenid') {
1126 if ($param->{$opt} eq '1') {
1127 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1129 } elsif ($opt eq 'hookscript') {
1130 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1131 raise_param_exc
({ $opt => $@ }) if $@;
1135 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1137 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1139 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1141 my $updatefn = sub {
1143 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1145 die "checksum missmatch (file change by other user?)\n"
1146 if $digest && $digest ne $conf->{digest
};
1148 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1150 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1151 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1152 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1153 delete $conf->{lock}; # for check lock check, not written out
1154 push @delete, 'lock'; # this is the real deal to write it out
1156 push @delete, 'runningmachine' if $conf->{runningmachine
};
1157 push @delete, 'runningcpu' if $conf->{runningcpu
};
1160 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1162 foreach my $opt (keys %$revert) {
1163 if (defined($conf->{$opt})) {
1164 $param->{$opt} = $conf->{$opt};
1165 } elsif (defined($conf->{pending
}->{$opt})) {
1170 if ($param->{memory
} || defined($param->{balloon
})) {
1171 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1172 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1174 die "balloon value too large (must be smaller than assigned memory)\n"
1175 if $balloon && $balloon > $maxmem;
1178 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1182 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1184 # write updates to pending section
1186 my $modified = {}; # record what $option we modify
1188 foreach my $opt (@delete) {
1189 $modified->{$opt} = 1;
1190 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1192 # value of what we want to delete, independent if pending or not
1193 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1194 if (!defined($val)) {
1195 warn "cannot delete '$opt' - not set in current configuration!\n";
1196 $modified->{$opt} = 0;
1199 my $is_pending_val = defined($conf->{pending
}->{$opt});
1200 delete $conf->{pending
}->{$opt};
1202 if ($opt =~ m/^unused/) {
1203 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1204 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1206 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1207 delete $conf->{$opt};
1208 PVE
::QemuConfig-
>write_config($vmid, $conf);
1210 } elsif ($opt eq 'vmstate') {
1211 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1212 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1213 delete $conf->{$opt};
1214 PVE
::QemuConfig-
>write_config($vmid, $conf);
1216 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1217 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1218 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1219 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1221 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1222 PVE
::QemuConfig-
>write_config($vmid, $conf);
1223 } elsif ($opt =~ m/^serial\d+$/) {
1224 if ($val eq 'socket') {
1225 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1226 } elsif ($authuser ne 'root@pam') {
1227 die "only root can delete '$opt' config for real devices\n";
1229 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1230 PVE
::QemuConfig-
>write_config($vmid, $conf);
1231 } elsif ($opt =~ m/^usb\d+$/) {
1232 if ($val =~ m/spice/) {
1233 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1234 } elsif ($authuser ne 'root@pam') {
1235 die "only root can delete '$opt' config for real devices\n";
1237 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1238 PVE
::QemuConfig-
>write_config($vmid, $conf);
1240 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1241 PVE
::QemuConfig-
>write_config($vmid, $conf);
1245 foreach my $opt (keys %$param) { # add/change
1246 $modified->{$opt} = 1;
1247 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1248 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1250 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1252 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1253 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1254 # FIXME: cloudinit: CDROM or Disk?
1255 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1256 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1258 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1260 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1261 if defined($conf->{pending
}->{$opt});
1263 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1264 } elsif ($opt =~ m/^serial\d+/) {
1265 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1266 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1267 } elsif ($authuser ne 'root@pam') {
1268 die "only root can modify '$opt' config for real devices\n";
1270 $conf->{pending
}->{$opt} = $param->{$opt};
1271 } elsif ($opt =~ m/^usb\d+/) {
1272 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1273 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1274 } elsif ($authuser ne 'root@pam') {
1275 die "only root can modify '$opt' config for real devices\n";
1277 $conf->{pending
}->{$opt} = $param->{$opt};
1279 $conf->{pending
}->{$opt} = $param->{$opt};
1281 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1282 PVE
::QemuConfig-
>write_config($vmid, $conf);
1285 # remove pending changes when nothing changed
1286 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1287 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1288 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1290 return if !scalar(keys %{$conf->{pending
}});
1292 my $running = PVE
::QemuServer
::check_running
($vmid);
1294 # apply pending changes
1296 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1300 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1302 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1304 raise_param_exc
($errors) if scalar(keys %$errors);
1313 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1315 if ($background_delay) {
1317 # Note: It would be better to do that in the Event based HTTPServer
1318 # to avoid blocking call to sleep.
1320 my $end_time = time() + $background_delay;
1322 my $task = PVE
::Tools
::upid_decode
($upid);
1325 while (time() < $end_time) {
1326 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1328 sleep(1); # this gets interrupted when child process ends
1332 my $status = PVE
::Tools
::upid_read_status
($upid);
1333 return undef if $status eq 'OK';
1342 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1345 my $vm_config_perm_list = [
1350 'VM.Config.Network',
1352 'VM.Config.Options',
1355 __PACKAGE__-
>register_method({
1356 name
=> 'update_vm_async',
1357 path
=> '{vmid}/config',
1361 description
=> "Set virtual machine options (asynchrounous API).",
1363 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1366 additionalProperties
=> 0,
1367 properties
=> PVE
::QemuServer
::json_config_properties
(
1369 node
=> get_standard_option
('pve-node'),
1370 vmid
=> get_standard_option
('pve-vmid'),
1371 skiplock
=> get_standard_option
('skiplock'),
1373 type
=> 'string', format
=> 'pve-configid-list',
1374 description
=> "A list of settings you want to delete.",
1378 type
=> 'string', format
=> 'pve-configid-list',
1379 description
=> "Revert a pending change.",
1384 description
=> $opt_force_description,
1386 requires
=> 'delete',
1390 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1394 background_delay
=> {
1396 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1407 code
=> $update_vm_api,
1410 __PACKAGE__-
>register_method({
1411 name
=> 'update_vm',
1412 path
=> '{vmid}/config',
1416 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1418 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1421 additionalProperties
=> 0,
1422 properties
=> PVE
::QemuServer
::json_config_properties
(
1424 node
=> get_standard_option
('pve-node'),
1425 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1426 skiplock
=> get_standard_option
('skiplock'),
1428 type
=> 'string', format
=> 'pve-configid-list',
1429 description
=> "A list of settings you want to delete.",
1433 type
=> 'string', format
=> 'pve-configid-list',
1434 description
=> "Revert a pending change.",
1439 description
=> $opt_force_description,
1441 requires
=> 'delete',
1445 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1451 returns
=> { type
=> 'null' },
1454 &$update_vm_api($param, 1);
1459 __PACKAGE__-
>register_method({
1460 name
=> 'destroy_vm',
1465 description
=> "Destroy the vm (also delete all used/owned volumes).",
1467 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1470 additionalProperties
=> 0,
1472 node
=> get_standard_option
('pve-node'),
1473 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1474 skiplock
=> get_standard_option
('skiplock'),
1477 description
=> "Remove vmid from backup cron jobs.",
1488 my $rpcenv = PVE
::RPCEnvironment
::get
();
1489 my $authuser = $rpcenv->get_user();
1490 my $vmid = $param->{vmid
};
1492 my $skiplock = $param->{skiplock
};
1493 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1494 if $skiplock && $authuser ne 'root@pam';
1496 my $early_checks = sub {
1498 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1499 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1501 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1503 if (!$param->{purge
}) {
1504 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1506 # don't allow destroy if with replication jobs but no purge param
1507 my $repl_conf = PVE
::ReplicationConfig-
>new();
1508 $repl_conf->check_for_existing_jobs($vmid);
1511 die "VM $vmid is running - destroy failed\n"
1512 if PVE
::QemuServer
::check_running
($vmid);
1522 my $storecfg = PVE
::Storage
::config
();
1524 syslog
('info', "destroy VM $vmid: $upid\n");
1525 PVE
::QemuConfig-
>lock_config($vmid, sub {
1526 # repeat, config might have changed
1527 my $ha_managed = $early_checks->();
1529 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1531 PVE
::AccessControl
::remove_vm_access
($vmid);
1532 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1533 if ($param->{purge
}) {
1534 print "purging VM $vmid from related configurations..\n";
1535 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1536 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1539 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1540 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1544 # only now remove the zombie config, else we can have reuse race
1545 PVE
::QemuConfig-
>destroy_config($vmid);
1549 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1552 __PACKAGE__-
>register_method({
1554 path
=> '{vmid}/unlink',
1558 description
=> "Unlink/delete disk images.",
1560 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1563 additionalProperties
=> 0,
1565 node
=> get_standard_option
('pve-node'),
1566 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1568 type
=> 'string', format
=> 'pve-configid-list',
1569 description
=> "A list of disk IDs you want to delete.",
1573 description
=> $opt_force_description,
1578 returns
=> { type
=> 'null'},
1582 $param->{delete} = extract_param
($param, 'idlist');
1584 __PACKAGE__-
>update_vm($param);
1591 __PACKAGE__-
>register_method({
1593 path
=> '{vmid}/vncproxy',
1597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1599 description
=> "Creates a TCP VNC proxy connections.",
1601 additionalProperties
=> 0,
1603 node
=> get_standard_option
('pve-node'),
1604 vmid
=> get_standard_option
('pve-vmid'),
1608 description
=> "starts websockify instead of vncproxy",
1613 additionalProperties
=> 0,
1615 user
=> { type
=> 'string' },
1616 ticket
=> { type
=> 'string' },
1617 cert
=> { type
=> 'string' },
1618 port
=> { type
=> 'integer' },
1619 upid
=> { type
=> 'string' },
1625 my $rpcenv = PVE
::RPCEnvironment
::get
();
1627 my $authuser = $rpcenv->get_user();
1629 my $vmid = $param->{vmid
};
1630 my $node = $param->{node
};
1631 my $websocket = $param->{websocket
};
1633 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1634 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1636 my $authpath = "/vms/$vmid";
1638 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1640 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1646 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1647 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1648 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1649 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1650 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1652 $family = PVE
::Tools
::get_host_address_family
($node);
1655 my $port = PVE
::Tools
::next_vnc_port
($family);
1662 syslog
('info', "starting vnc proxy $upid\n");
1668 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1670 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1671 '-timeout', $timeout, '-authpath', $authpath,
1672 '-perm', 'Sys.Console'];
1674 if ($param->{websocket
}) {
1675 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1676 push @$cmd, '-notls', '-listen', 'localhost';
1679 push @$cmd, '-c', @$remcmd, @$termcmd;
1681 PVE
::Tools
::run_command
($cmd);
1685 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1687 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1689 my $sock = IO
::Socket
::IP-
>new(
1694 GetAddrInfoFlags
=> 0,
1695 ) or die "failed to create socket: $!\n";
1696 # Inside the worker we shouldn't have any previous alarms
1697 # running anyway...:
1699 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1701 accept(my $cli, $sock) or die "connection failed: $!\n";
1704 if (PVE
::Tools
::run_command
($cmd,
1705 output
=> '>&'.fileno($cli),
1706 input
=> '<&'.fileno($cli),
1709 die "Failed to run vncproxy.\n";
1716 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1718 PVE
::Tools
::wait_for_vnc_port
($port);
1729 __PACKAGE__-
>register_method({
1730 name
=> 'termproxy',
1731 path
=> '{vmid}/termproxy',
1735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1737 description
=> "Creates a TCP proxy connections.",
1739 additionalProperties
=> 0,
1741 node
=> get_standard_option
('pve-node'),
1742 vmid
=> get_standard_option
('pve-vmid'),
1746 enum
=> [qw(serial0 serial1 serial2 serial3)],
1747 description
=> "opens a serial terminal (defaults to display)",
1752 additionalProperties
=> 0,
1754 user
=> { type
=> 'string' },
1755 ticket
=> { type
=> 'string' },
1756 port
=> { type
=> 'integer' },
1757 upid
=> { type
=> 'string' },
1763 my $rpcenv = PVE
::RPCEnvironment
::get
();
1765 my $authuser = $rpcenv->get_user();
1767 my $vmid = $param->{vmid
};
1768 my $node = $param->{node
};
1769 my $serial = $param->{serial
};
1771 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1773 if (!defined($serial)) {
1774 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1775 $serial = $conf->{vga
};
1779 my $authpath = "/vms/$vmid";
1781 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1786 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1787 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1788 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1789 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1790 push @$remcmd, '--';
1792 $family = PVE
::Tools
::get_host_address_family
($node);
1795 my $port = PVE
::Tools
::next_vnc_port
($family);
1797 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1798 push @$termcmd, '-iface', $serial if $serial;
1803 syslog
('info', "starting qemu termproxy $upid\n");
1805 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1806 '--perm', 'VM.Console', '--'];
1807 push @$cmd, @$remcmd, @$termcmd;
1809 PVE
::Tools
::run_command
($cmd);
1812 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1814 PVE
::Tools
::wait_for_vnc_port
($port);
1824 __PACKAGE__-
>register_method({
1825 name
=> 'vncwebsocket',
1826 path
=> '{vmid}/vncwebsocket',
1829 description
=> "You also need to pass a valid ticket (vncticket).",
1830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1832 description
=> "Opens a weksocket for VNC traffic.",
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid'),
1839 description
=> "Ticket from previous call to vncproxy.",
1844 description
=> "Port number returned by previous vncproxy call.",
1854 port
=> { type
=> 'string' },
1860 my $rpcenv = PVE
::RPCEnvironment
::get
();
1862 my $authuser = $rpcenv->get_user();
1864 my $vmid = $param->{vmid
};
1865 my $node = $param->{node
};
1867 my $authpath = "/vms/$vmid";
1869 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1871 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1873 # Note: VNC ports are acessible from outside, so we do not gain any
1874 # security if we verify that $param->{port} belongs to VM $vmid. This
1875 # check is done by verifying the VNC ticket (inside VNC protocol).
1877 my $port = $param->{port
};
1879 return { port
=> $port };
1882 __PACKAGE__-
>register_method({
1883 name
=> 'spiceproxy',
1884 path
=> '{vmid}/spiceproxy',
1889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1891 description
=> "Returns a SPICE configuration to connect to the VM.",
1893 additionalProperties
=> 0,
1895 node
=> get_standard_option
('pve-node'),
1896 vmid
=> get_standard_option
('pve-vmid'),
1897 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1900 returns
=> get_standard_option
('remote-viewer-config'),
1904 my $rpcenv = PVE
::RPCEnvironment
::get
();
1906 my $authuser = $rpcenv->get_user();
1908 my $vmid = $param->{vmid
};
1909 my $node = $param->{node
};
1910 my $proxy = $param->{proxy
};
1912 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1913 my $title = "VM $vmid";
1914 $title .= " - ". $conf->{name
} if $conf->{name
};
1916 my $port = PVE
::QemuServer
::spice_port
($vmid);
1918 my ($ticket, undef, $remote_viewer_config) =
1919 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1921 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1922 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1924 return $remote_viewer_config;
1927 __PACKAGE__-
>register_method({
1929 path
=> '{vmid}/status',
1932 description
=> "Directory index",
1937 additionalProperties
=> 0,
1939 node
=> get_standard_option
('pve-node'),
1940 vmid
=> get_standard_option
('pve-vmid'),
1948 subdir
=> { type
=> 'string' },
1951 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1957 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1960 { subdir
=> 'current' },
1961 { subdir
=> 'start' },
1962 { subdir
=> 'stop' },
1963 { subdir
=> 'reset' },
1964 { subdir
=> 'shutdown' },
1965 { subdir
=> 'suspend' },
1966 { subdir
=> 'reboot' },
1972 __PACKAGE__-
>register_method({
1973 name
=> 'vm_status',
1974 path
=> '{vmid}/status/current',
1977 protected
=> 1, # qemu pid files are only readable by root
1978 description
=> "Get virtual machine status.",
1980 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1983 additionalProperties
=> 0,
1985 node
=> get_standard_option
('pve-node'),
1986 vmid
=> get_standard_option
('pve-vmid'),
1992 %$PVE::QemuServer
::vmstatus_return_properties
,
1994 description
=> "HA manager service status.",
1998 description
=> "Qemu VGA configuration supports spice.",
2003 description
=> "Qemu GuestAgent enabled in config.",
2013 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2015 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2016 my $status = $vmstatus->{$param->{vmid
}};
2018 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2020 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2021 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2026 __PACKAGE__-
>register_method({
2028 path
=> '{vmid}/status/start',
2032 description
=> "Start virtual machine.",
2034 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2037 additionalProperties
=> 0,
2039 node
=> get_standard_option
('pve-node'),
2040 vmid
=> get_standard_option
('pve-vmid',
2041 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2042 skiplock
=> get_standard_option
('skiplock'),
2043 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2044 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2047 enum
=> ['secure', 'insecure'],
2048 description
=> "Migration traffic is encrypted using an SSH " .
2049 "tunnel by default. On secure, completely private networks " .
2050 "this can be disabled to increase performance.",
2053 migration_network
=> {
2054 type
=> 'string', format
=> 'CIDR',
2055 description
=> "CIDR of the (sub) network that is used for migration.",
2058 machine
=> get_standard_option
('pve-qemu-machine'),
2060 description
=> "Override QEMU's -cpu argument with the given string.",
2064 targetstorage
=> get_standard_option
('pve-targetstorage'),
2066 description
=> "Wait maximal timeout seconds.",
2069 default => 'max(30, vm memory in GiB)',
2080 my $rpcenv = PVE
::RPCEnvironment
::get
();
2081 my $authuser = $rpcenv->get_user();
2083 my $node = extract_param
($param, 'node');
2084 my $vmid = extract_param
($param, 'vmid');
2085 my $timeout = extract_param
($param, 'timeout');
2087 my $machine = extract_param
($param, 'machine');
2088 my $force_cpu = extract_param
($param, 'force-cpu');
2090 my $get_root_param = sub {
2091 my $value = extract_param
($param, $_[0]);
2092 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2093 if $value && $authuser ne 'root@pam';
2097 my $stateuri = $get_root_param->('stateuri');
2098 my $skiplock = $get_root_param->('skiplock');
2099 my $migratedfrom = $get_root_param->('migratedfrom');
2100 my $migration_type = $get_root_param->('migration_type');
2101 my $migration_network = $get_root_param->('migration_network');
2102 my $targetstorage = $get_root_param->('targetstorage');
2106 if ($targetstorage) {
2107 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2109 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2110 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2114 # read spice ticket from STDIN
2116 my $nbd_protocol_version = 0;
2117 my $replicated_volumes = {};
2118 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2119 while (defined(my $line = <STDIN
>)) {
2121 if ($line =~ m/^spice_ticket: (.+)$/) {
2123 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2124 $nbd_protocol_version = $1;
2125 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2126 $replicated_volumes->{$1} = 1;
2128 # fallback for old source node
2129 $spice_ticket = $line;
2134 PVE
::Cluster
::check_cfs_quorum
();
2136 my $storecfg = PVE
::Storage
::config
();
2138 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2142 print "Requesting HA start for VM $vmid\n";
2144 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2145 PVE
::Tools
::run_command
($cmd);
2149 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2156 syslog
('info', "start VM $vmid: $upid\n");
2158 my $migrate_opts = {
2159 migratedfrom
=> $migratedfrom,
2160 spice_ticket
=> $spice_ticket,
2161 network
=> $migration_network,
2162 type
=> $migration_type,
2163 storagemap
=> $storagemap,
2164 nbd_proto_version
=> $nbd_protocol_version,
2165 replicated_volumes
=> $replicated_volumes,
2169 statefile
=> $stateuri,
2170 skiplock
=> $skiplock,
2171 forcemachine
=> $machine,
2172 timeout
=> $timeout,
2173 forcecpu
=> $force_cpu,
2176 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2180 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2184 __PACKAGE__-
>register_method({
2186 path
=> '{vmid}/status/stop',
2190 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2191 "is akin to pulling the power plug of a running computer and may damage the VM data",
2193 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2196 additionalProperties
=> 0,
2198 node
=> get_standard_option
('pve-node'),
2199 vmid
=> get_standard_option
('pve-vmid',
2200 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2201 skiplock
=> get_standard_option
('skiplock'),
2202 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2204 description
=> "Wait maximal timeout seconds.",
2210 description
=> "Do not deactivate storage volumes.",
2223 my $rpcenv = PVE
::RPCEnvironment
::get
();
2224 my $authuser = $rpcenv->get_user();
2226 my $node = extract_param
($param, 'node');
2227 my $vmid = extract_param
($param, 'vmid');
2229 my $skiplock = extract_param
($param, 'skiplock');
2230 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2231 if $skiplock && $authuser ne 'root@pam';
2233 my $keepActive = extract_param
($param, 'keepActive');
2234 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2235 if $keepActive && $authuser ne 'root@pam';
2237 my $migratedfrom = extract_param
($param, 'migratedfrom');
2238 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2239 if $migratedfrom && $authuser ne 'root@pam';
2242 my $storecfg = PVE
::Storage
::config
();
2244 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2249 print "Requesting HA stop for VM $vmid\n";
2251 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2252 PVE
::Tools
::run_command
($cmd);
2256 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2262 syslog
('info', "stop VM $vmid: $upid\n");
2264 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2265 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2269 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2273 __PACKAGE__-
>register_method({
2275 path
=> '{vmid}/status/reset',
2279 description
=> "Reset virtual machine.",
2281 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2284 additionalProperties
=> 0,
2286 node
=> get_standard_option
('pve-node'),
2287 vmid
=> get_standard_option
('pve-vmid',
2288 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2289 skiplock
=> get_standard_option
('skiplock'),
2298 my $rpcenv = PVE
::RPCEnvironment
::get
();
2300 my $authuser = $rpcenv->get_user();
2302 my $node = extract_param
($param, 'node');
2304 my $vmid = extract_param
($param, 'vmid');
2306 my $skiplock = extract_param
($param, 'skiplock');
2307 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2308 if $skiplock && $authuser ne 'root@pam';
2310 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2315 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2320 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2323 __PACKAGE__-
>register_method({
2324 name
=> 'vm_shutdown',
2325 path
=> '{vmid}/status/shutdown',
2329 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2330 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2332 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2335 additionalProperties
=> 0,
2337 node
=> get_standard_option
('pve-node'),
2338 vmid
=> get_standard_option
('pve-vmid',
2339 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2340 skiplock
=> get_standard_option
('skiplock'),
2342 description
=> "Wait maximal timeout seconds.",
2348 description
=> "Make sure the VM stops.",
2354 description
=> "Do not deactivate storage volumes.",
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2368 my $authuser = $rpcenv->get_user();
2370 my $node = extract_param
($param, 'node');
2371 my $vmid = extract_param
($param, 'vmid');
2373 my $skiplock = extract_param
($param, 'skiplock');
2374 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2375 if $skiplock && $authuser ne 'root@pam';
2377 my $keepActive = extract_param
($param, 'keepActive');
2378 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2379 if $keepActive && $authuser ne 'root@pam';
2381 my $storecfg = PVE
::Storage
::config
();
2385 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2386 # otherwise, we will infer a shutdown command, but run into the timeout,
2387 # then when the vm is resumed, it will instantly shutdown
2389 # checking the qmp status here to get feedback to the gui/cli/api
2390 # and the status query should not take too long
2391 my $qmpstatus = eval {
2392 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2393 mon_cmd
($vmid, "query-status");
2397 if (!$err && $qmpstatus->{status
} eq "paused") {
2398 if ($param->{forceStop
}) {
2399 warn "VM is paused - stop instead of shutdown\n";
2402 die "VM is paused - cannot shutdown\n";
2406 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2408 my $timeout = $param->{timeout
} // 60;
2412 print "Requesting HA stop for VM $vmid\n";
2414 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2415 PVE
::Tools
::run_command
($cmd);
2419 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2426 syslog
('info', "shutdown VM $vmid: $upid\n");
2428 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2429 $shutdown, $param->{forceStop
}, $keepActive);
2433 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2437 __PACKAGE__-
>register_method({
2438 name
=> 'vm_reboot',
2439 path
=> '{vmid}/status/reboot',
2443 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2445 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2448 additionalProperties
=> 0,
2450 node
=> get_standard_option
('pve-node'),
2451 vmid
=> get_standard_option
('pve-vmid',
2452 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2454 description
=> "Wait maximal timeout seconds for the shutdown.",
2467 my $rpcenv = PVE
::RPCEnvironment
::get
();
2468 my $authuser = $rpcenv->get_user();
2470 my $node = extract_param
($param, 'node');
2471 my $vmid = extract_param
($param, 'vmid');
2473 my $qmpstatus = eval {
2474 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2475 mon_cmd
($vmid, "query-status");
2479 if (!$err && $qmpstatus->{status
} eq "paused") {
2480 die "VM is paused - cannot shutdown\n";
2483 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2488 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2489 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2493 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2496 __PACKAGE__-
>register_method({
2497 name
=> 'vm_suspend',
2498 path
=> '{vmid}/status/suspend',
2502 description
=> "Suspend virtual machine.",
2504 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2505 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2506 " on the storage for the vmstate.",
2507 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2510 additionalProperties
=> 0,
2512 node
=> get_standard_option
('pve-node'),
2513 vmid
=> get_standard_option
('pve-vmid',
2514 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2515 skiplock
=> get_standard_option
('skiplock'),
2520 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2522 statestorage
=> get_standard_option
('pve-storage-id', {
2523 description
=> "The storage for the VM state",
2524 requires
=> 'todisk',
2526 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2536 my $rpcenv = PVE
::RPCEnvironment
::get
();
2537 my $authuser = $rpcenv->get_user();
2539 my $node = extract_param
($param, 'node');
2540 my $vmid = extract_param
($param, 'vmid');
2542 my $todisk = extract_param
($param, 'todisk') // 0;
2544 my $statestorage = extract_param
($param, 'statestorage');
2546 my $skiplock = extract_param
($param, 'skiplock');
2547 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2548 if $skiplock && $authuser ne 'root@pam';
2550 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2552 die "Cannot suspend HA managed VM to disk\n"
2553 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2555 # early check for storage permission, for better user feedback
2557 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2559 if (!$statestorage) {
2560 # get statestorage from config if none is given
2561 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2562 my $storecfg = PVE
::Storage
::config
();
2563 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2566 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2572 syslog
('info', "suspend VM $vmid: $upid\n");
2574 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2579 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2580 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2583 __PACKAGE__-
>register_method({
2584 name
=> 'vm_resume',
2585 path
=> '{vmid}/status/resume',
2589 description
=> "Resume virtual machine.",
2591 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2594 additionalProperties
=> 0,
2596 node
=> get_standard_option
('pve-node'),
2597 vmid
=> get_standard_option
('pve-vmid',
2598 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2599 skiplock
=> get_standard_option
('skiplock'),
2600 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2610 my $rpcenv = PVE
::RPCEnvironment
::get
();
2612 my $authuser = $rpcenv->get_user();
2614 my $node = extract_param
($param, 'node');
2616 my $vmid = extract_param
($param, 'vmid');
2618 my $skiplock = extract_param
($param, 'skiplock');
2619 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2620 if $skiplock && $authuser ne 'root@pam';
2622 my $nocheck = extract_param
($param, 'nocheck');
2623 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2624 if $nocheck && $authuser ne 'root@pam';
2626 my $to_disk_suspended;
2628 PVE
::QemuConfig-
>lock_config($vmid, sub {
2629 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2630 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2634 die "VM $vmid not running\n"
2635 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2640 syslog
('info', "resume VM $vmid: $upid\n");
2642 if (!$to_disk_suspended) {
2643 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2645 my $storecfg = PVE
::Storage
::config
();
2646 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2652 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2655 __PACKAGE__-
>register_method({
2656 name
=> 'vm_sendkey',
2657 path
=> '{vmid}/sendkey',
2661 description
=> "Send key event to virtual machine.",
2663 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2666 additionalProperties
=> 0,
2668 node
=> get_standard_option
('pve-node'),
2669 vmid
=> get_standard_option
('pve-vmid',
2670 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2671 skiplock
=> get_standard_option
('skiplock'),
2673 description
=> "The key (qemu monitor encoding).",
2678 returns
=> { type
=> 'null'},
2682 my $rpcenv = PVE
::RPCEnvironment
::get
();
2684 my $authuser = $rpcenv->get_user();
2686 my $node = extract_param
($param, 'node');
2688 my $vmid = extract_param
($param, 'vmid');
2690 my $skiplock = extract_param
($param, 'skiplock');
2691 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2692 if $skiplock && $authuser ne 'root@pam';
2694 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2699 __PACKAGE__-
>register_method({
2700 name
=> 'vm_feature',
2701 path
=> '{vmid}/feature',
2705 description
=> "Check if feature for virtual machine is available.",
2707 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2710 additionalProperties
=> 0,
2712 node
=> get_standard_option
('pve-node'),
2713 vmid
=> get_standard_option
('pve-vmid'),
2715 description
=> "Feature to check.",
2717 enum
=> [ 'snapshot', 'clone', 'copy' ],
2719 snapname
=> get_standard_option
('pve-snapshot-name', {
2727 hasFeature
=> { type
=> 'boolean' },
2730 items
=> { type
=> 'string' },
2737 my $node = extract_param
($param, 'node');
2739 my $vmid = extract_param
($param, 'vmid');
2741 my $snapname = extract_param
($param, 'snapname');
2743 my $feature = extract_param
($param, 'feature');
2745 my $running = PVE
::QemuServer
::check_running
($vmid);
2747 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2750 my $snap = $conf->{snapshots
}->{$snapname};
2751 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2754 my $storecfg = PVE
::Storage
::config
();
2756 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2757 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2760 hasFeature
=> $hasFeature,
2761 nodes
=> [ keys %$nodelist ],
2765 __PACKAGE__-
>register_method({
2767 path
=> '{vmid}/clone',
2771 description
=> "Create a copy of virtual machine/template.",
2773 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2774 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2775 "'Datastore.AllocateSpace' on any used storage.",
2778 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2780 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2781 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2786 additionalProperties
=> 0,
2788 node
=> get_standard_option
('pve-node'),
2789 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2790 newid
=> get_standard_option
('pve-vmid', {
2791 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2792 description
=> 'VMID for the clone.' }),
2795 type
=> 'string', format
=> 'dns-name',
2796 description
=> "Set a name for the new VM.",
2801 description
=> "Description for the new VM.",
2805 type
=> 'string', format
=> 'pve-poolid',
2806 description
=> "Add the new VM to the specified pool.",
2808 snapname
=> get_standard_option
('pve-snapshot-name', {
2811 storage
=> get_standard_option
('pve-storage-id', {
2812 description
=> "Target storage for full clone.",
2816 description
=> "Target format for file storage. Only valid for full clone.",
2819 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2824 description
=> "Create a full copy of all disks. This is always done when " .
2825 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2827 target
=> get_standard_option
('pve-node', {
2828 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2832 description
=> "Override I/O bandwidth limit (in KiB/s).",
2836 default => 'clone limit from datacenter or storage config',
2846 my $rpcenv = PVE
::RPCEnvironment
::get
();
2847 my $authuser = $rpcenv->get_user();
2849 my $node = extract_param
($param, 'node');
2850 my $vmid = extract_param
($param, 'vmid');
2851 my $newid = extract_param
($param, 'newid');
2852 my $pool = extract_param
($param, 'pool');
2853 $rpcenv->check_pool_exist($pool) if defined($pool);
2855 my $snapname = extract_param
($param, 'snapname');
2856 my $storage = extract_param
($param, 'storage');
2857 my $format = extract_param
($param, 'format');
2858 my $target = extract_param
($param, 'target');
2860 my $localnode = PVE
::INotify
::nodename
();
2862 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2866 PVE
::Cluster
::check_node_exists
($target) if $target;
2868 my $storecfg = PVE
::Storage
::config
();
2871 # check if storage is enabled on local node
2872 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2874 # check if storage is available on target node
2875 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2876 # clone only works if target storage is shared
2877 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2878 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2882 PVE
::Cluster
::check_cfs_quorum
();
2884 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2887 # do all tests after lock but before forking worker - if possible
2889 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2890 PVE
::QemuConfig-
>check_lock($conf);
2892 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2893 die "unexpected state change\n" if $verify_running != $running;
2895 die "snapshot '$snapname' does not exist\n"
2896 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2898 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2900 die "parameter 'storage' not allowed for linked clones\n"
2901 if defined($storage) && !$full;
2903 die "parameter 'format' not allowed for linked clones\n"
2904 if defined($format) && !$full;
2906 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2908 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2910 die "can't clone VM to node '$target' (VM uses local storage)\n"
2911 if $target && !$sharedvm;
2913 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2914 die "unable to create VM $newid: config file already exists\n"
2917 my $newconf = { lock => 'clone' };
2922 foreach my $opt (keys %$oldconf) {
2923 my $value = $oldconf->{$opt};
2925 # do not copy snapshot related info
2926 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2927 $opt eq 'vmstate' || $opt eq 'snapstate';
2929 # no need to copy unused images, because VMID(owner) changes anyways
2930 next if $opt =~ m/^unused\d+$/;
2932 # always change MAC! address
2933 if ($opt =~ m/^net(\d+)$/) {
2934 my $net = PVE
::QemuServer
::parse_net
($value);
2935 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2936 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2937 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2938 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2939 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2940 die "unable to parse drive options for '$opt'\n" if !$drive;
2941 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2942 $newconf->{$opt} = $value; # simply copy configuration
2944 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2945 die "Full clone feature is not supported for drive '$opt'\n"
2946 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2947 $fullclone->{$opt} = 1;
2949 # not full means clone instead of copy
2950 die "Linked clone feature is not supported for drive '$opt'\n"
2951 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2953 $drives->{$opt} = $drive;
2954 push @$vollist, $drive->{file
};
2957 # copy everything else
2958 $newconf->{$opt} = $value;
2962 # auto generate a new uuid
2963 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2964 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2965 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2966 # auto generate a new vmgenid only if the option was set for template
2967 if ($newconf->{vmgenid
}) {
2968 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2971 delete $newconf->{template
};
2973 if ($param->{name
}) {
2974 $newconf->{name
} = $param->{name
};
2976 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2979 if ($param->{description
}) {
2980 $newconf->{description
} = $param->{description
};
2983 # create empty/temp config - this fails if VM already exists on other node
2984 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2985 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2990 my $newvollist = [];
2997 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2999 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3001 my $bwlimit = extract_param
($param, 'bwlimit');
3003 my $total_jobs = scalar(keys %{$drives});
3006 foreach my $opt (keys %$drives) {
3007 my $drive = $drives->{$opt};
3008 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3009 my $completion = $skipcomplete ?
'skip' : 'complete';
3011 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3012 my $storage_list = [ $src_sid ];
3013 push @$storage_list, $storage if defined($storage);
3014 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3016 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3017 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3018 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3020 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3022 PVE
::QemuConfig-
>write_config($newid, $newconf);
3026 delete $newconf->{lock};
3028 # do not write pending changes
3029 if (my @changes = keys %{$newconf->{pending
}}) {
3030 my $pending = join(',', @changes);
3031 warn "found pending changes for '$pending', discarding for clone\n";
3032 delete $newconf->{pending
};
3035 PVE
::QemuConfig-
>write_config($newid, $newconf);
3038 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3039 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3040 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3042 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3043 die "Failed to move config to node '$target' - rename failed: $!\n"
3044 if !rename($conffile, $newconffile);
3047 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3050 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3051 sleep 1; # some storage like rbd need to wait before release volume - really?
3053 foreach my $volid (@$newvollist) {
3054 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3058 PVE
::Firewall
::remove_vmfw_conf
($newid);
3060 unlink $conffile; # avoid races -> last thing before die
3062 die "clone failed: $err";
3068 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3070 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3073 # Aquire exclusive lock lock for $newid
3074 my $lock_target_vm = sub {
3075 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3078 # exclusive lock if VM is running - else shared lock is enough;
3080 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3082 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3086 __PACKAGE__-
>register_method({
3087 name
=> 'move_vm_disk',
3088 path
=> '{vmid}/move_disk',
3092 description
=> "Move volume to different storage.",
3094 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3096 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3097 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3101 additionalProperties
=> 0,
3103 node
=> get_standard_option
('pve-node'),
3104 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3107 description
=> "The disk you want to move.",
3108 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3110 storage
=> get_standard_option
('pve-storage-id', {
3111 description
=> "Target storage.",
3112 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3116 description
=> "Target Format.",
3117 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3122 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3128 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3133 description
=> "Override I/O bandwidth limit (in KiB/s).",
3137 default => 'move limit from datacenter or storage config',
3143 description
=> "the task ID.",
3148 my $rpcenv = PVE
::RPCEnvironment
::get
();
3149 my $authuser = $rpcenv->get_user();
3151 my $node = extract_param
($param, 'node');
3152 my $vmid = extract_param
($param, 'vmid');
3153 my $digest = extract_param
($param, 'digest');
3154 my $disk = extract_param
($param, 'disk');
3155 my $storeid = extract_param
($param, 'storage');
3156 my $format = extract_param
($param, 'format');
3158 my $storecfg = PVE
::Storage
::config
();
3160 my $updatefn = sub {
3161 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3162 PVE
::QemuConfig-
>check_lock($conf);
3164 die "VM config checksum missmatch (file change by other user?)\n"
3165 if $digest && $digest ne $conf->{digest
};
3167 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3169 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3171 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3172 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3174 my $old_volid = $drive->{file
};
3176 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3177 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3181 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3182 (!$format || !$oldfmt || $oldfmt eq $format);
3184 # this only checks snapshots because $disk is passed!
3185 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3186 die "you can't move a disk with snapshots and delete the source\n"
3187 if $snapshotted && $param->{delete};
3189 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3191 my $running = PVE
::QemuServer
::check_running
($vmid);
3193 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3196 my $newvollist = [];
3202 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3204 warn "moving disk with snapshots, snapshots will not be moved!\n"
3207 my $bwlimit = extract_param
($param, 'bwlimit');
3208 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3210 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3211 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3213 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3215 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3217 # convert moved disk to base if part of template
3218 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3219 if PVE
::QemuConfig-
>is_template($conf);
3221 PVE
::QemuConfig-
>write_config($vmid, $conf);
3223 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3224 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3225 eval { mon_cmd
($vmid, "guest-fstrim") };
3229 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3230 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3236 foreach my $volid (@$newvollist) {
3237 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3240 die "storage migration failed: $err";
3243 if ($param->{delete}) {
3245 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3246 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3252 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3255 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3258 my $check_vm_disks_local = sub {
3259 my ($storecfg, $vmconf, $vmid) = @_;
3261 my $local_disks = {};
3263 # add some more information to the disks e.g. cdrom
3264 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3265 my ($volid, $attr) = @_;
3267 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3269 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3270 return if $scfg->{shared
};
3272 # The shared attr here is just a special case where the vdisk
3273 # is marked as shared manually
3274 return if $attr->{shared
};
3275 return if $attr->{cdrom
} and $volid eq "none";
3277 if (exists $local_disks->{$volid}) {
3278 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3280 $local_disks->{$volid} = $attr;
3281 # ensure volid is present in case it's needed
3282 $local_disks->{$volid}->{volid
} = $volid;
3286 return $local_disks;
3289 __PACKAGE__-
>register_method({
3290 name
=> 'migrate_vm_precondition',
3291 path
=> '{vmid}/migrate',
3295 description
=> "Get preconditions for migration.",
3297 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3300 additionalProperties
=> 0,
3302 node
=> get_standard_option
('pve-node'),
3303 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3304 target
=> get_standard_option
('pve-node', {
3305 description
=> "Target node.",
3306 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3314 running
=> { type
=> 'boolean' },
3318 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3320 not_allowed_nodes
=> {
3323 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3327 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3329 local_resources
=> {
3331 description
=> "List local resources e.g. pci, usb"
3338 my $rpcenv = PVE
::RPCEnvironment
::get
();
3340 my $authuser = $rpcenv->get_user();
3342 PVE
::Cluster
::check_cfs_quorum
();
3346 my $vmid = extract_param
($param, 'vmid');
3347 my $target = extract_param
($param, 'target');
3348 my $localnode = PVE
::INotify
::nodename
();
3352 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3353 my $storecfg = PVE
::Storage
::config
();
3356 # try to detect errors early
3357 PVE
::QemuConfig-
>check_lock($vmconf);
3359 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3361 # if vm is not running, return target nodes where local storage is available
3362 # for offline migration
3363 if (!$res->{running
}) {
3364 $res->{allowed_nodes
} = [];
3365 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3366 delete $checked_nodes->{$localnode};
3368 foreach my $node (keys %$checked_nodes) {
3369 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3370 push @{$res->{allowed_nodes
}}, $node;
3374 $res->{not_allowed_nodes
} = $checked_nodes;
3378 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3379 $res->{local_disks
} = [ values %$local_disks ];;
3381 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3383 $res->{local_resources
} = $local_resources;
3390 __PACKAGE__-
>register_method({
3391 name
=> 'migrate_vm',
3392 path
=> '{vmid}/migrate',
3396 description
=> "Migrate virtual machine. Creates a new migration task.",
3398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3401 additionalProperties
=> 0,
3403 node
=> get_standard_option
('pve-node'),
3404 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3405 target
=> get_standard_option
('pve-node', {
3406 description
=> "Target node.",
3407 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3411 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3416 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3421 enum
=> ['secure', 'insecure'],
3422 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3425 migration_network
=> {
3426 type
=> 'string', format
=> 'CIDR',
3427 description
=> "CIDR of the (sub) network that is used for migration.",
3430 "with-local-disks" => {
3432 description
=> "Enable live storage migration for local disk",
3435 targetstorage
=> get_standard_option
('pve-targetstorage', {
3436 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3439 description
=> "Override I/O bandwidth limit (in KiB/s).",
3443 default => 'migrate limit from datacenter or storage config',
3449 description
=> "the task ID.",
3454 my $rpcenv = PVE
::RPCEnvironment
::get
();
3455 my $authuser = $rpcenv->get_user();
3457 my $target = extract_param
($param, 'target');
3459 my $localnode = PVE
::INotify
::nodename
();
3460 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3462 PVE
::Cluster
::check_cfs_quorum
();
3464 PVE
::Cluster
::check_node_exists
($target);
3466 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3468 my $vmid = extract_param
($param, 'vmid');
3470 raise_param_exc
({ force
=> "Only root may use this option." })
3471 if $param->{force
} && $authuser ne 'root@pam';
3473 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3474 if $param->{migration_type
} && $authuser ne 'root@pam';
3476 # allow root only until better network permissions are available
3477 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3478 if $param->{migration_network
} && $authuser ne 'root@pam';
3481 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3483 # try to detect errors early
3485 PVE
::QemuConfig-
>check_lock($conf);
3487 if (PVE
::QemuServer
::check_running
($vmid)) {
3488 die "can't migrate running VM without --online\n" if !$param->{online
};
3490 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3491 $param->{online
} = 0;
3494 my $storecfg = PVE
::Storage
::config
();
3496 if (my $targetstorage = $param->{targetstorage
}) {
3497 my $check_storage = sub {
3498 my ($target_sid) = @_;
3499 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3500 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3501 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3502 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3503 if !$scfg->{content
}->{images
};
3506 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3507 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3510 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3511 if !defined($storagemap->{identity
});
3513 foreach my $source (values %{$storagemap->{entries
}}) {
3514 $check_storage->($source);
3517 $check_storage->($storagemap->{default})
3518 if $storagemap->{default};
3520 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3521 if $storagemap->{identity
};
3523 $param->{storagemap
} = $storagemap;
3525 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3528 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3533 print "Requesting HA migration for VM $vmid to node $target\n";
3535 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3536 PVE
::Tools
::run_command
($cmd);
3540 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3545 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3549 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3552 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3557 __PACKAGE__-
>register_method({
3559 path
=> '{vmid}/monitor',
3563 description
=> "Execute Qemu monitor commands.",
3565 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3569 additionalProperties
=> 0,
3571 node
=> get_standard_option
('pve-node'),
3572 vmid
=> get_standard_option
('pve-vmid'),
3575 description
=> "The monitor command.",
3579 returns
=> { type
=> 'string'},
3583 my $rpcenv = PVE
::RPCEnvironment
::get
();
3584 my $authuser = $rpcenv->get_user();
3587 my $command = shift;
3588 return $command =~ m/^\s*info(\s+|$)/
3589 || $command =~ m/^\s*help\s*$/;
3592 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3593 if !&$is_ro($param->{command
});
3595 my $vmid = $param->{vmid
};
3597 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3601 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3603 $res = "ERROR: $@" if $@;
3608 __PACKAGE__-
>register_method({
3609 name
=> 'resize_vm',
3610 path
=> '{vmid}/resize',
3614 description
=> "Extend volume size.",
3616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3619 additionalProperties
=> 0,
3621 node
=> get_standard_option
('pve-node'),
3622 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3623 skiplock
=> get_standard_option
('skiplock'),
3626 description
=> "The disk you want to resize.",
3627 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3631 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3632 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.",
3636 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3642 returns
=> { type
=> 'null'},
3646 my $rpcenv = PVE
::RPCEnvironment
::get
();
3648 my $authuser = $rpcenv->get_user();
3650 my $node = extract_param
($param, 'node');
3652 my $vmid = extract_param
($param, 'vmid');
3654 my $digest = extract_param
($param, 'digest');
3656 my $disk = extract_param
($param, 'disk');
3658 my $sizestr = extract_param
($param, 'size');
3660 my $skiplock = extract_param
($param, 'skiplock');
3661 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3662 if $skiplock && $authuser ne 'root@pam';
3664 my $storecfg = PVE
::Storage
::config
();
3666 my $updatefn = sub {
3668 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3670 die "checksum missmatch (file change by other user?)\n"
3671 if $digest && $digest ne $conf->{digest
};
3672 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3674 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3676 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3678 my (undef, undef, undef, undef, undef, undef, $format) =
3679 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3681 die "can't resize volume: $disk if snapshot exists\n"
3682 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3684 my $volid = $drive->{file
};
3686 die "disk '$disk' has no associated volume\n" if !$volid;
3688 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3690 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3692 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3694 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3695 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3697 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3699 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3700 my ($ext, $newsize, $unit) = ($1, $2, $4);
3703 $newsize = $newsize * 1024;
3704 } elsif ($unit eq 'M') {
3705 $newsize = $newsize * 1024 * 1024;
3706 } elsif ($unit eq 'G') {
3707 $newsize = $newsize * 1024 * 1024 * 1024;
3708 } elsif ($unit eq 'T') {
3709 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3712 $newsize += $size if $ext;
3713 $newsize = int($newsize);
3715 die "shrinking disks is not supported\n" if $newsize < $size;
3717 return if $size == $newsize;
3719 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3721 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3723 $drive->{size
} = $newsize;
3724 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3726 PVE
::QemuConfig-
>write_config($vmid, $conf);
3729 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3733 __PACKAGE__-
>register_method({
3734 name
=> 'snapshot_list',
3735 path
=> '{vmid}/snapshot',
3737 description
=> "List all snapshots.",
3739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3742 protected
=> 1, # qemu pid files are only readable by root
3744 additionalProperties
=> 0,
3746 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3747 node
=> get_standard_option
('pve-node'),
3756 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3760 description
=> "Snapshot includes RAM.",
3765 description
=> "Snapshot description.",
3769 description
=> "Snapshot creation time",
3771 renderer
=> 'timestamp',
3775 description
=> "Parent snapshot identifier.",
3781 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3786 my $vmid = $param->{vmid
};
3788 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3789 my $snaphash = $conf->{snapshots
} || {};
3793 foreach my $name (keys %$snaphash) {
3794 my $d = $snaphash->{$name};
3797 snaptime
=> $d->{snaptime
} || 0,
3798 vmstate
=> $d->{vmstate
} ?
1 : 0,
3799 description
=> $d->{description
} || '',
3801 $item->{parent
} = $d->{parent
} if $d->{parent
};
3802 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3806 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3809 digest
=> $conf->{digest
},
3810 running
=> $running,
3811 description
=> "You are here!",
3813 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3815 push @$res, $current;
3820 __PACKAGE__-
>register_method({
3822 path
=> '{vmid}/snapshot',
3826 description
=> "Snapshot a VM.",
3828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3831 additionalProperties
=> 0,
3833 node
=> get_standard_option
('pve-node'),
3834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3835 snapname
=> get_standard_option
('pve-snapshot-name'),
3839 description
=> "Save the vmstate",
3844 description
=> "A textual description or comment.",
3850 description
=> "the task ID.",
3855 my $rpcenv = PVE
::RPCEnvironment
::get
();
3857 my $authuser = $rpcenv->get_user();
3859 my $node = extract_param
($param, 'node');
3861 my $vmid = extract_param
($param, 'vmid');
3863 my $snapname = extract_param
($param, 'snapname');
3865 die "unable to use snapshot name 'current' (reserved name)\n"
3866 if $snapname eq 'current';
3868 die "unable to use snapshot name 'pending' (reserved name)\n"
3869 if lc($snapname) eq 'pending';
3872 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3873 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3874 $param->{description
});
3877 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3880 __PACKAGE__-
>register_method({
3881 name
=> 'snapshot_cmd_idx',
3882 path
=> '{vmid}/snapshot/{snapname}',
3889 additionalProperties
=> 0,
3891 vmid
=> get_standard_option
('pve-vmid'),
3892 node
=> get_standard_option
('pve-node'),
3893 snapname
=> get_standard_option
('pve-snapshot-name'),
3902 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3909 push @$res, { cmd
=> 'rollback' };
3910 push @$res, { cmd
=> 'config' };
3915 __PACKAGE__-
>register_method({
3916 name
=> 'update_snapshot_config',
3917 path
=> '{vmid}/snapshot/{snapname}/config',
3921 description
=> "Update snapshot metadata.",
3923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3926 additionalProperties
=> 0,
3928 node
=> get_standard_option
('pve-node'),
3929 vmid
=> get_standard_option
('pve-vmid'),
3930 snapname
=> get_standard_option
('pve-snapshot-name'),
3934 description
=> "A textual description or comment.",
3938 returns
=> { type
=> 'null' },
3942 my $rpcenv = PVE
::RPCEnvironment
::get
();
3944 my $authuser = $rpcenv->get_user();
3946 my $vmid = extract_param
($param, 'vmid');
3948 my $snapname = extract_param
($param, 'snapname');
3950 return undef if !defined($param->{description
});
3952 my $updatefn = sub {
3954 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3956 PVE
::QemuConfig-
>check_lock($conf);
3958 my $snap = $conf->{snapshots
}->{$snapname};
3960 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3962 $snap->{description
} = $param->{description
} if defined($param->{description
});
3964 PVE
::QemuConfig-
>write_config($vmid, $conf);
3967 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3972 __PACKAGE__-
>register_method({
3973 name
=> 'get_snapshot_config',
3974 path
=> '{vmid}/snapshot/{snapname}/config',
3977 description
=> "Get snapshot configuration",
3979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3982 additionalProperties
=> 0,
3984 node
=> get_standard_option
('pve-node'),
3985 vmid
=> get_standard_option
('pve-vmid'),
3986 snapname
=> get_standard_option
('pve-snapshot-name'),
3989 returns
=> { type
=> "object" },
3993 my $rpcenv = PVE
::RPCEnvironment
::get
();
3995 my $authuser = $rpcenv->get_user();
3997 my $vmid = extract_param
($param, 'vmid');
3999 my $snapname = extract_param
($param, 'snapname');
4001 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4003 my $snap = $conf->{snapshots
}->{$snapname};
4005 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4010 __PACKAGE__-
>register_method({
4012 path
=> '{vmid}/snapshot/{snapname}/rollback',
4016 description
=> "Rollback VM state to specified snapshot.",
4018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4021 additionalProperties
=> 0,
4023 node
=> get_standard_option
('pve-node'),
4024 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4025 snapname
=> get_standard_option
('pve-snapshot-name'),
4030 description
=> "the task ID.",
4035 my $rpcenv = PVE
::RPCEnvironment
::get
();
4037 my $authuser = $rpcenv->get_user();
4039 my $node = extract_param
($param, 'node');
4041 my $vmid = extract_param
($param, 'vmid');
4043 my $snapname = extract_param
($param, 'snapname');
4046 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4047 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4051 # hold migration lock, this makes sure that nobody create replication snapshots
4052 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4055 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4058 __PACKAGE__-
>register_method({
4059 name
=> 'delsnapshot',
4060 path
=> '{vmid}/snapshot/{snapname}',
4064 description
=> "Delete a VM snapshot.",
4066 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4069 additionalProperties
=> 0,
4071 node
=> get_standard_option
('pve-node'),
4072 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4073 snapname
=> get_standard_option
('pve-snapshot-name'),
4077 description
=> "For removal from config file, even if removing disk snapshots fails.",
4083 description
=> "the task ID.",
4088 my $rpcenv = PVE
::RPCEnvironment
::get
();
4090 my $authuser = $rpcenv->get_user();
4092 my $node = extract_param
($param, 'node');
4094 my $vmid = extract_param
($param, 'vmid');
4096 my $snapname = extract_param
($param, 'snapname');
4099 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4100 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4103 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4106 __PACKAGE__-
>register_method({
4108 path
=> '{vmid}/template',
4112 description
=> "Create a Template.",
4114 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4115 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4118 additionalProperties
=> 0,
4120 node
=> get_standard_option
('pve-node'),
4121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4125 description
=> "If you want to convert only 1 disk to base image.",
4126 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4131 returns
=> { type
=> 'null'},
4135 my $rpcenv = PVE
::RPCEnvironment
::get
();
4137 my $authuser = $rpcenv->get_user();
4139 my $node = extract_param
($param, 'node');
4141 my $vmid = extract_param
($param, 'vmid');
4143 my $disk = extract_param
($param, 'disk');
4145 my $updatefn = sub {
4147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4149 PVE
::QemuConfig-
>check_lock($conf);
4151 die "unable to create template, because VM contains snapshots\n"
4152 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4154 die "you can't convert a template to a template\n"
4155 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4157 die "you can't convert a VM to template if VM is running\n"
4158 if PVE
::QemuServer
::check_running
($vmid);
4161 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4164 $conf->{template
} = 1;
4165 PVE
::QemuConfig-
>write_config($vmid, $conf);
4167 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4170 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4174 __PACKAGE__-
>register_method({
4175 name
=> 'cloudinit_generated_config_dump',
4176 path
=> '{vmid}/cloudinit/dump',
4179 description
=> "Get automatically generated cloudinit config.",
4181 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4184 additionalProperties
=> 0,
4186 node
=> get_standard_option
('pve-node'),
4187 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4189 description
=> 'Config type.',
4191 enum
=> ['user', 'network', 'meta'],
4201 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4203 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});