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 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1099 if !defined($storeid);
1101 return if defined($volname) && $volname eq 'cloudinit';
1104 if ($volid =~ $NEW_DISK_RE) {
1106 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1108 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1110 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1111 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1112 return if $scfg->{shared
};
1113 die "cannot add non-replicatable volume to a replicated VM\n";
1116 foreach my $opt (keys %$param) {
1117 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1118 # cleanup drive path
1119 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1120 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1121 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1122 $check_replication->($drive);
1123 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1124 } elsif ($opt =~ m/^net(\d+)$/) {
1126 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1127 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1128 } elsif ($opt eq 'vmgenid') {
1129 if ($param->{$opt} eq '1') {
1130 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1132 } elsif ($opt eq 'hookscript') {
1133 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1134 raise_param_exc
({ $opt => $@ }) if $@;
1138 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1140 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1142 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1144 my $updatefn = sub {
1146 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1148 die "checksum missmatch (file change by other user?)\n"
1149 if $digest && $digest ne $conf->{digest
};
1151 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1153 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1154 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1155 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1156 delete $conf->{lock}; # for check lock check, not written out
1157 push @delete, 'lock'; # this is the real deal to write it out
1159 push @delete, 'runningmachine' if $conf->{runningmachine
};
1160 push @delete, 'runningcpu' if $conf->{runningcpu
};
1163 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1165 foreach my $opt (keys %$revert) {
1166 if (defined($conf->{$opt})) {
1167 $param->{$opt} = $conf->{$opt};
1168 } elsif (defined($conf->{pending
}->{$opt})) {
1173 if ($param->{memory
} || defined($param->{balloon
})) {
1174 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1175 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1177 die "balloon value too large (must be smaller than assigned memory)\n"
1178 if $balloon && $balloon > $maxmem;
1181 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1185 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1187 # write updates to pending section
1189 my $modified = {}; # record what $option we modify
1191 foreach my $opt (@delete) {
1192 $modified->{$opt} = 1;
1193 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1195 # value of what we want to delete, independent if pending or not
1196 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1197 if (!defined($val)) {
1198 warn "cannot delete '$opt' - not set in current configuration!\n";
1199 $modified->{$opt} = 0;
1202 my $is_pending_val = defined($conf->{pending
}->{$opt});
1203 delete $conf->{pending
}->{$opt};
1205 if ($opt =~ m/^unused/) {
1206 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1207 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1209 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1210 delete $conf->{$opt};
1211 PVE
::QemuConfig-
>write_config($vmid, $conf);
1213 } elsif ($opt eq 'vmstate') {
1214 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1215 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1216 delete $conf->{$opt};
1217 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1220 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1221 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1222 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1224 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1225 PVE
::QemuConfig-
>write_config($vmid, $conf);
1226 } elsif ($opt =~ m/^serial\d+$/) {
1227 if ($val eq 'socket') {
1228 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1229 } elsif ($authuser ne 'root@pam') {
1230 die "only root can delete '$opt' config for real devices\n";
1232 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1233 PVE
::QemuConfig-
>write_config($vmid, $conf);
1234 } elsif ($opt =~ m/^usb\d+$/) {
1235 if ($val =~ m/spice/) {
1236 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1237 } elsif ($authuser ne 'root@pam') {
1238 die "only root can delete '$opt' config for real devices\n";
1240 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1241 PVE
::QemuConfig-
>write_config($vmid, $conf);
1243 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1244 PVE
::QemuConfig-
>write_config($vmid, $conf);
1248 foreach my $opt (keys %$param) { # add/change
1249 $modified->{$opt} = 1;
1250 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1251 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1253 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1255 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1256 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1257 # FIXME: cloudinit: CDROM or Disk?
1258 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1259 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1261 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1263 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1264 if defined($conf->{pending
}->{$opt});
1266 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1267 } elsif ($opt =~ m/^serial\d+/) {
1268 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1269 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1270 } elsif ($authuser ne 'root@pam') {
1271 die "only root can modify '$opt' config for real devices\n";
1273 $conf->{pending
}->{$opt} = $param->{$opt};
1274 } elsif ($opt =~ m/^usb\d+/) {
1275 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1276 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1277 } elsif ($authuser ne 'root@pam') {
1278 die "only root can modify '$opt' config for real devices\n";
1280 $conf->{pending
}->{$opt} = $param->{$opt};
1282 $conf->{pending
}->{$opt} = $param->{$opt};
1284 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1285 PVE
::QemuConfig-
>write_config($vmid, $conf);
1288 # remove pending changes when nothing changed
1289 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1290 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1291 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1293 return if !scalar(keys %{$conf->{pending
}});
1295 my $running = PVE
::QemuServer
::check_running
($vmid);
1297 # apply pending changes
1299 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1303 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1305 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1307 raise_param_exc
($errors) if scalar(keys %$errors);
1316 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1318 if ($background_delay) {
1320 # Note: It would be better to do that in the Event based HTTPServer
1321 # to avoid blocking call to sleep.
1323 my $end_time = time() + $background_delay;
1325 my $task = PVE
::Tools
::upid_decode
($upid);
1328 while (time() < $end_time) {
1329 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1331 sleep(1); # this gets interrupted when child process ends
1335 my $status = PVE
::Tools
::upid_read_status
($upid);
1336 return undef if $status eq 'OK';
1345 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1348 my $vm_config_perm_list = [
1353 'VM.Config.Network',
1355 'VM.Config.Options',
1358 __PACKAGE__-
>register_method({
1359 name
=> 'update_vm_async',
1360 path
=> '{vmid}/config',
1364 description
=> "Set virtual machine options (asynchrounous API).",
1366 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1369 additionalProperties
=> 0,
1370 properties
=> PVE
::QemuServer
::json_config_properties
(
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid'),
1374 skiplock
=> get_standard_option
('skiplock'),
1376 type
=> 'string', format
=> 'pve-configid-list',
1377 description
=> "A list of settings you want to delete.",
1381 type
=> 'string', format
=> 'pve-configid-list',
1382 description
=> "Revert a pending change.",
1387 description
=> $opt_force_description,
1389 requires
=> 'delete',
1393 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1397 background_delay
=> {
1399 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1410 code
=> $update_vm_api,
1413 __PACKAGE__-
>register_method({
1414 name
=> 'update_vm',
1415 path
=> '{vmid}/config',
1419 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1421 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1424 additionalProperties
=> 0,
1425 properties
=> PVE
::QemuServer
::json_config_properties
(
1427 node
=> get_standard_option
('pve-node'),
1428 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1429 skiplock
=> get_standard_option
('skiplock'),
1431 type
=> 'string', format
=> 'pve-configid-list',
1432 description
=> "A list of settings you want to delete.",
1436 type
=> 'string', format
=> 'pve-configid-list',
1437 description
=> "Revert a pending change.",
1442 description
=> $opt_force_description,
1444 requires
=> 'delete',
1448 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1454 returns
=> { type
=> 'null' },
1457 &$update_vm_api($param, 1);
1462 __PACKAGE__-
>register_method({
1463 name
=> 'destroy_vm',
1468 description
=> "Destroy the vm (also delete all used/owned volumes).",
1470 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1473 additionalProperties
=> 0,
1475 node
=> get_standard_option
('pve-node'),
1476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1477 skiplock
=> get_standard_option
('skiplock'),
1480 description
=> "Remove vmid from backup cron jobs.",
1491 my $rpcenv = PVE
::RPCEnvironment
::get
();
1492 my $authuser = $rpcenv->get_user();
1493 my $vmid = $param->{vmid
};
1495 my $skiplock = $param->{skiplock
};
1496 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1497 if $skiplock && $authuser ne 'root@pam';
1499 my $early_checks = sub {
1501 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1502 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1504 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1506 if (!$param->{purge
}) {
1507 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1509 # don't allow destroy if with replication jobs but no purge param
1510 my $repl_conf = PVE
::ReplicationConfig-
>new();
1511 $repl_conf->check_for_existing_jobs($vmid);
1514 die "VM $vmid is running - destroy failed\n"
1515 if PVE
::QemuServer
::check_running
($vmid);
1525 my $storecfg = PVE
::Storage
::config
();
1527 syslog
('info', "destroy VM $vmid: $upid\n");
1528 PVE
::QemuConfig-
>lock_config($vmid, sub {
1529 # repeat, config might have changed
1530 my $ha_managed = $early_checks->();
1532 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1534 PVE
::AccessControl
::remove_vm_access
($vmid);
1535 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1536 if ($param->{purge
}) {
1537 print "purging VM $vmid from related configurations..\n";
1538 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1539 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1542 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1543 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1547 # only now remove the zombie config, else we can have reuse race
1548 PVE
::QemuConfig-
>destroy_config($vmid);
1552 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1555 __PACKAGE__-
>register_method({
1557 path
=> '{vmid}/unlink',
1561 description
=> "Unlink/delete disk images.",
1563 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1566 additionalProperties
=> 0,
1568 node
=> get_standard_option
('pve-node'),
1569 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1571 type
=> 'string', format
=> 'pve-configid-list',
1572 description
=> "A list of disk IDs you want to delete.",
1576 description
=> $opt_force_description,
1581 returns
=> { type
=> 'null'},
1585 $param->{delete} = extract_param
($param, 'idlist');
1587 __PACKAGE__-
>update_vm($param);
1594 __PACKAGE__-
>register_method({
1596 path
=> '{vmid}/vncproxy',
1600 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1602 description
=> "Creates a TCP VNC proxy connections.",
1604 additionalProperties
=> 0,
1606 node
=> get_standard_option
('pve-node'),
1607 vmid
=> get_standard_option
('pve-vmid'),
1611 description
=> "starts websockify instead of vncproxy",
1616 additionalProperties
=> 0,
1618 user
=> { type
=> 'string' },
1619 ticket
=> { type
=> 'string' },
1620 cert
=> { type
=> 'string' },
1621 port
=> { type
=> 'integer' },
1622 upid
=> { type
=> 'string' },
1628 my $rpcenv = PVE
::RPCEnvironment
::get
();
1630 my $authuser = $rpcenv->get_user();
1632 my $vmid = $param->{vmid
};
1633 my $node = $param->{node
};
1634 my $websocket = $param->{websocket
};
1636 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1637 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1639 my $authpath = "/vms/$vmid";
1641 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1643 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1649 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1650 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1651 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1652 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1653 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1655 $family = PVE
::Tools
::get_host_address_family
($node);
1658 my $port = PVE
::Tools
::next_vnc_port
($family);
1665 syslog
('info', "starting vnc proxy $upid\n");
1671 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1673 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1674 '-timeout', $timeout, '-authpath', $authpath,
1675 '-perm', 'Sys.Console'];
1677 if ($param->{websocket
}) {
1678 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1679 push @$cmd, '-notls', '-listen', 'localhost';
1682 push @$cmd, '-c', @$remcmd, @$termcmd;
1684 PVE
::Tools
::run_command
($cmd);
1688 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1690 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1692 my $sock = IO
::Socket
::IP-
>new(
1697 GetAddrInfoFlags
=> 0,
1698 ) or die "failed to create socket: $!\n";
1699 # Inside the worker we shouldn't have any previous alarms
1700 # running anyway...:
1702 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1704 accept(my $cli, $sock) or die "connection failed: $!\n";
1707 if (PVE
::Tools
::run_command
($cmd,
1708 output
=> '>&'.fileno($cli),
1709 input
=> '<&'.fileno($cli),
1712 die "Failed to run vncproxy.\n";
1719 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1721 PVE
::Tools
::wait_for_vnc_port
($port);
1732 __PACKAGE__-
>register_method({
1733 name
=> 'termproxy',
1734 path
=> '{vmid}/termproxy',
1738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1740 description
=> "Creates a TCP proxy connections.",
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1749 enum
=> [qw(serial0 serial1 serial2 serial3)],
1750 description
=> "opens a serial terminal (defaults to display)",
1755 additionalProperties
=> 0,
1757 user
=> { type
=> 'string' },
1758 ticket
=> { type
=> 'string' },
1759 port
=> { type
=> 'integer' },
1760 upid
=> { type
=> 'string' },
1766 my $rpcenv = PVE
::RPCEnvironment
::get
();
1768 my $authuser = $rpcenv->get_user();
1770 my $vmid = $param->{vmid
};
1771 my $node = $param->{node
};
1772 my $serial = $param->{serial
};
1774 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1776 if (!defined($serial)) {
1777 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1778 $serial = $conf->{vga
};
1782 my $authpath = "/vms/$vmid";
1784 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1789 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1790 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1791 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1792 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1793 push @$remcmd, '--';
1795 $family = PVE
::Tools
::get_host_address_family
($node);
1798 my $port = PVE
::Tools
::next_vnc_port
($family);
1800 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1801 push @$termcmd, '-iface', $serial if $serial;
1806 syslog
('info', "starting qemu termproxy $upid\n");
1808 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1809 '--perm', 'VM.Console', '--'];
1810 push @$cmd, @$remcmd, @$termcmd;
1812 PVE
::Tools
::run_command
($cmd);
1815 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1817 PVE
::Tools
::wait_for_vnc_port
($port);
1827 __PACKAGE__-
>register_method({
1828 name
=> 'vncwebsocket',
1829 path
=> '{vmid}/vncwebsocket',
1832 description
=> "You also need to pass a valid ticket (vncticket).",
1833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1835 description
=> "Opens a weksocket for VNC traffic.",
1837 additionalProperties
=> 0,
1839 node
=> get_standard_option
('pve-node'),
1840 vmid
=> get_standard_option
('pve-vmid'),
1842 description
=> "Ticket from previous call to vncproxy.",
1847 description
=> "Port number returned by previous vncproxy call.",
1857 port
=> { type
=> 'string' },
1863 my $rpcenv = PVE
::RPCEnvironment
::get
();
1865 my $authuser = $rpcenv->get_user();
1867 my $vmid = $param->{vmid
};
1868 my $node = $param->{node
};
1870 my $authpath = "/vms/$vmid";
1872 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1874 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1876 # Note: VNC ports are acessible from outside, so we do not gain any
1877 # security if we verify that $param->{port} belongs to VM $vmid. This
1878 # check is done by verifying the VNC ticket (inside VNC protocol).
1880 my $port = $param->{port
};
1882 return { port
=> $port };
1885 __PACKAGE__-
>register_method({
1886 name
=> 'spiceproxy',
1887 path
=> '{vmid}/spiceproxy',
1892 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1894 description
=> "Returns a SPICE configuration to connect to the VM.",
1896 additionalProperties
=> 0,
1898 node
=> get_standard_option
('pve-node'),
1899 vmid
=> get_standard_option
('pve-vmid'),
1900 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1903 returns
=> get_standard_option
('remote-viewer-config'),
1907 my $rpcenv = PVE
::RPCEnvironment
::get
();
1909 my $authuser = $rpcenv->get_user();
1911 my $vmid = $param->{vmid
};
1912 my $node = $param->{node
};
1913 my $proxy = $param->{proxy
};
1915 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1916 my $title = "VM $vmid";
1917 $title .= " - ". $conf->{name
} if $conf->{name
};
1919 my $port = PVE
::QemuServer
::spice_port
($vmid);
1921 my ($ticket, undef, $remote_viewer_config) =
1922 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1924 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1925 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1927 return $remote_viewer_config;
1930 __PACKAGE__-
>register_method({
1932 path
=> '{vmid}/status',
1935 description
=> "Directory index",
1940 additionalProperties
=> 0,
1942 node
=> get_standard_option
('pve-node'),
1943 vmid
=> get_standard_option
('pve-vmid'),
1951 subdir
=> { type
=> 'string' },
1954 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1960 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1963 { subdir
=> 'current' },
1964 { subdir
=> 'start' },
1965 { subdir
=> 'stop' },
1966 { subdir
=> 'reset' },
1967 { subdir
=> 'shutdown' },
1968 { subdir
=> 'suspend' },
1969 { subdir
=> 'reboot' },
1975 __PACKAGE__-
>register_method({
1976 name
=> 'vm_status',
1977 path
=> '{vmid}/status/current',
1980 protected
=> 1, # qemu pid files are only readable by root
1981 description
=> "Get virtual machine status.",
1983 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1986 additionalProperties
=> 0,
1988 node
=> get_standard_option
('pve-node'),
1989 vmid
=> get_standard_option
('pve-vmid'),
1995 %$PVE::QemuServer
::vmstatus_return_properties
,
1997 description
=> "HA manager service status.",
2001 description
=> "Qemu VGA configuration supports spice.",
2006 description
=> "Qemu GuestAgent enabled in config.",
2016 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2018 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2019 my $status = $vmstatus->{$param->{vmid
}};
2021 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2023 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2024 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2029 __PACKAGE__-
>register_method({
2031 path
=> '{vmid}/status/start',
2035 description
=> "Start virtual machine.",
2037 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2040 additionalProperties
=> 0,
2042 node
=> get_standard_option
('pve-node'),
2043 vmid
=> get_standard_option
('pve-vmid',
2044 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2045 skiplock
=> get_standard_option
('skiplock'),
2046 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2047 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2050 enum
=> ['secure', 'insecure'],
2051 description
=> "Migration traffic is encrypted using an SSH " .
2052 "tunnel by default. On secure, completely private networks " .
2053 "this can be disabled to increase performance.",
2056 migration_network
=> {
2057 type
=> 'string', format
=> 'CIDR',
2058 description
=> "CIDR of the (sub) network that is used for migration.",
2061 machine
=> get_standard_option
('pve-qemu-machine'),
2063 description
=> "Override QEMU's -cpu argument with the given string.",
2067 targetstorage
=> get_standard_option
('pve-targetstorage'),
2069 description
=> "Wait maximal timeout seconds.",
2072 default => 'max(30, vm memory in GiB)',
2083 my $rpcenv = PVE
::RPCEnvironment
::get
();
2084 my $authuser = $rpcenv->get_user();
2086 my $node = extract_param
($param, 'node');
2087 my $vmid = extract_param
($param, 'vmid');
2088 my $timeout = extract_param
($param, 'timeout');
2090 my $machine = extract_param
($param, 'machine');
2091 my $force_cpu = extract_param
($param, 'force-cpu');
2093 my $get_root_param = sub {
2094 my $value = extract_param
($param, $_[0]);
2095 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2096 if $value && $authuser ne 'root@pam';
2100 my $stateuri = $get_root_param->('stateuri');
2101 my $skiplock = $get_root_param->('skiplock');
2102 my $migratedfrom = $get_root_param->('migratedfrom');
2103 my $migration_type = $get_root_param->('migration_type');
2104 my $migration_network = $get_root_param->('migration_network');
2105 my $targetstorage = $get_root_param->('targetstorage');
2109 if ($targetstorage) {
2110 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2112 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2113 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2117 # read spice ticket from STDIN
2119 my $nbd_protocol_version = 0;
2120 my $replicated_volumes = {};
2121 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2122 while (defined(my $line = <STDIN
>)) {
2124 if ($line =~ m/^spice_ticket: (.+)$/) {
2126 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2127 $nbd_protocol_version = $1;
2128 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2129 $replicated_volumes->{$1} = 1;
2131 # fallback for old source node
2132 $spice_ticket = $line;
2137 PVE
::Cluster
::check_cfs_quorum
();
2139 my $storecfg = PVE
::Storage
::config
();
2141 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2145 print "Requesting HA start for VM $vmid\n";
2147 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2148 PVE
::Tools
::run_command
($cmd);
2152 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2159 syslog
('info', "start VM $vmid: $upid\n");
2161 my $migrate_opts = {
2162 migratedfrom
=> $migratedfrom,
2163 spice_ticket
=> $spice_ticket,
2164 network
=> $migration_network,
2165 type
=> $migration_type,
2166 storagemap
=> $storagemap,
2167 nbd_proto_version
=> $nbd_protocol_version,
2168 replicated_volumes
=> $replicated_volumes,
2172 statefile
=> $stateuri,
2173 skiplock
=> $skiplock,
2174 forcemachine
=> $machine,
2175 timeout
=> $timeout,
2176 forcecpu
=> $force_cpu,
2179 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2183 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2187 __PACKAGE__-
>register_method({
2189 path
=> '{vmid}/status/stop',
2193 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2194 "is akin to pulling the power plug of a running computer and may damage the VM data",
2196 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2199 additionalProperties
=> 0,
2201 node
=> get_standard_option
('pve-node'),
2202 vmid
=> get_standard_option
('pve-vmid',
2203 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2204 skiplock
=> get_standard_option
('skiplock'),
2205 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2207 description
=> "Wait maximal timeout seconds.",
2213 description
=> "Do not deactivate storage volumes.",
2226 my $rpcenv = PVE
::RPCEnvironment
::get
();
2227 my $authuser = $rpcenv->get_user();
2229 my $node = extract_param
($param, 'node');
2230 my $vmid = extract_param
($param, 'vmid');
2232 my $skiplock = extract_param
($param, 'skiplock');
2233 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2234 if $skiplock && $authuser ne 'root@pam';
2236 my $keepActive = extract_param
($param, 'keepActive');
2237 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2238 if $keepActive && $authuser ne 'root@pam';
2240 my $migratedfrom = extract_param
($param, 'migratedfrom');
2241 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2242 if $migratedfrom && $authuser ne 'root@pam';
2245 my $storecfg = PVE
::Storage
::config
();
2247 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2252 print "Requesting HA stop for VM $vmid\n";
2254 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2255 PVE
::Tools
::run_command
($cmd);
2259 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2265 syslog
('info', "stop VM $vmid: $upid\n");
2267 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2268 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2272 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2276 __PACKAGE__-
>register_method({
2278 path
=> '{vmid}/status/reset',
2282 description
=> "Reset virtual machine.",
2284 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2287 additionalProperties
=> 0,
2289 node
=> get_standard_option
('pve-node'),
2290 vmid
=> get_standard_option
('pve-vmid',
2291 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2292 skiplock
=> get_standard_option
('skiplock'),
2301 my $rpcenv = PVE
::RPCEnvironment
::get
();
2303 my $authuser = $rpcenv->get_user();
2305 my $node = extract_param
($param, 'node');
2307 my $vmid = extract_param
($param, 'vmid');
2309 my $skiplock = extract_param
($param, 'skiplock');
2310 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2311 if $skiplock && $authuser ne 'root@pam';
2313 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2318 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2323 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2326 __PACKAGE__-
>register_method({
2327 name
=> 'vm_shutdown',
2328 path
=> '{vmid}/status/shutdown',
2332 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2333 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2335 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2338 additionalProperties
=> 0,
2340 node
=> get_standard_option
('pve-node'),
2341 vmid
=> get_standard_option
('pve-vmid',
2342 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2343 skiplock
=> get_standard_option
('skiplock'),
2345 description
=> "Wait maximal timeout seconds.",
2351 description
=> "Make sure the VM stops.",
2357 description
=> "Do not deactivate storage volumes.",
2370 my $rpcenv = PVE
::RPCEnvironment
::get
();
2371 my $authuser = $rpcenv->get_user();
2373 my $node = extract_param
($param, 'node');
2374 my $vmid = extract_param
($param, 'vmid');
2376 my $skiplock = extract_param
($param, 'skiplock');
2377 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2378 if $skiplock && $authuser ne 'root@pam';
2380 my $keepActive = extract_param
($param, 'keepActive');
2381 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2382 if $keepActive && $authuser ne 'root@pam';
2384 my $storecfg = PVE
::Storage
::config
();
2388 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2389 # otherwise, we will infer a shutdown command, but run into the timeout,
2390 # then when the vm is resumed, it will instantly shutdown
2392 # checking the qmp status here to get feedback to the gui/cli/api
2393 # and the status query should not take too long
2394 my $qmpstatus = eval {
2395 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2396 mon_cmd
($vmid, "query-status");
2400 if (!$err && $qmpstatus->{status
} eq "paused") {
2401 if ($param->{forceStop
}) {
2402 warn "VM is paused - stop instead of shutdown\n";
2405 die "VM is paused - cannot shutdown\n";
2409 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2411 my $timeout = $param->{timeout
} // 60;
2415 print "Requesting HA stop for VM $vmid\n";
2417 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2418 PVE
::Tools
::run_command
($cmd);
2422 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2429 syslog
('info', "shutdown VM $vmid: $upid\n");
2431 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2432 $shutdown, $param->{forceStop
}, $keepActive);
2436 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2440 __PACKAGE__-
>register_method({
2441 name
=> 'vm_reboot',
2442 path
=> '{vmid}/status/reboot',
2446 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2448 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2451 additionalProperties
=> 0,
2453 node
=> get_standard_option
('pve-node'),
2454 vmid
=> get_standard_option
('pve-vmid',
2455 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2457 description
=> "Wait maximal timeout seconds for the shutdown.",
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2471 my $authuser = $rpcenv->get_user();
2473 my $node = extract_param
($param, 'node');
2474 my $vmid = extract_param
($param, 'vmid');
2476 my $qmpstatus = eval {
2477 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2478 mon_cmd
($vmid, "query-status");
2482 if (!$err && $qmpstatus->{status
} eq "paused") {
2483 die "VM is paused - cannot shutdown\n";
2486 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2491 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2492 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2496 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2499 __PACKAGE__-
>register_method({
2500 name
=> 'vm_suspend',
2501 path
=> '{vmid}/status/suspend',
2505 description
=> "Suspend virtual machine.",
2507 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2508 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2509 " on the storage for the vmstate.",
2510 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2513 additionalProperties
=> 0,
2515 node
=> get_standard_option
('pve-node'),
2516 vmid
=> get_standard_option
('pve-vmid',
2517 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2518 skiplock
=> get_standard_option
('skiplock'),
2523 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2525 statestorage
=> get_standard_option
('pve-storage-id', {
2526 description
=> "The storage for the VM state",
2527 requires
=> 'todisk',
2529 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2540 my $authuser = $rpcenv->get_user();
2542 my $node = extract_param
($param, 'node');
2543 my $vmid = extract_param
($param, 'vmid');
2545 my $todisk = extract_param
($param, 'todisk') // 0;
2547 my $statestorage = extract_param
($param, 'statestorage');
2549 my $skiplock = extract_param
($param, 'skiplock');
2550 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2551 if $skiplock && $authuser ne 'root@pam';
2553 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2555 die "Cannot suspend HA managed VM to disk\n"
2556 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2558 # early check for storage permission, for better user feedback
2560 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2562 if (!$statestorage) {
2563 # get statestorage from config if none is given
2564 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2565 my $storecfg = PVE
::Storage
::config
();
2566 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2569 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2575 syslog
('info', "suspend VM $vmid: $upid\n");
2577 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2582 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2583 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2586 __PACKAGE__-
>register_method({
2587 name
=> 'vm_resume',
2588 path
=> '{vmid}/status/resume',
2592 description
=> "Resume virtual machine.",
2594 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2597 additionalProperties
=> 0,
2599 node
=> get_standard_option
('pve-node'),
2600 vmid
=> get_standard_option
('pve-vmid',
2601 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2602 skiplock
=> get_standard_option
('skiplock'),
2603 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2613 my $rpcenv = PVE
::RPCEnvironment
::get
();
2615 my $authuser = $rpcenv->get_user();
2617 my $node = extract_param
($param, 'node');
2619 my $vmid = extract_param
($param, 'vmid');
2621 my $skiplock = extract_param
($param, 'skiplock');
2622 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2623 if $skiplock && $authuser ne 'root@pam';
2625 my $nocheck = extract_param
($param, 'nocheck');
2626 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2627 if $nocheck && $authuser ne 'root@pam';
2629 my $to_disk_suspended;
2631 PVE
::QemuConfig-
>lock_config($vmid, sub {
2632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2633 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2637 die "VM $vmid not running\n"
2638 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2643 syslog
('info', "resume VM $vmid: $upid\n");
2645 if (!$to_disk_suspended) {
2646 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2648 my $storecfg = PVE
::Storage
::config
();
2649 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2655 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2658 __PACKAGE__-
>register_method({
2659 name
=> 'vm_sendkey',
2660 path
=> '{vmid}/sendkey',
2664 description
=> "Send key event to virtual machine.",
2666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2669 additionalProperties
=> 0,
2671 node
=> get_standard_option
('pve-node'),
2672 vmid
=> get_standard_option
('pve-vmid',
2673 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2674 skiplock
=> get_standard_option
('skiplock'),
2676 description
=> "The key (qemu monitor encoding).",
2681 returns
=> { type
=> 'null'},
2685 my $rpcenv = PVE
::RPCEnvironment
::get
();
2687 my $authuser = $rpcenv->get_user();
2689 my $node = extract_param
($param, 'node');
2691 my $vmid = extract_param
($param, 'vmid');
2693 my $skiplock = extract_param
($param, 'skiplock');
2694 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2695 if $skiplock && $authuser ne 'root@pam';
2697 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2702 __PACKAGE__-
>register_method({
2703 name
=> 'vm_feature',
2704 path
=> '{vmid}/feature',
2708 description
=> "Check if feature for virtual machine is available.",
2710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2713 additionalProperties
=> 0,
2715 node
=> get_standard_option
('pve-node'),
2716 vmid
=> get_standard_option
('pve-vmid'),
2718 description
=> "Feature to check.",
2720 enum
=> [ 'snapshot', 'clone', 'copy' ],
2722 snapname
=> get_standard_option
('pve-snapshot-name', {
2730 hasFeature
=> { type
=> 'boolean' },
2733 items
=> { type
=> 'string' },
2740 my $node = extract_param
($param, 'node');
2742 my $vmid = extract_param
($param, 'vmid');
2744 my $snapname = extract_param
($param, 'snapname');
2746 my $feature = extract_param
($param, 'feature');
2748 my $running = PVE
::QemuServer
::check_running
($vmid);
2750 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2753 my $snap = $conf->{snapshots
}->{$snapname};
2754 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2757 my $storecfg = PVE
::Storage
::config
();
2759 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2760 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2763 hasFeature
=> $hasFeature,
2764 nodes
=> [ keys %$nodelist ],
2768 __PACKAGE__-
>register_method({
2770 path
=> '{vmid}/clone',
2774 description
=> "Create a copy of virtual machine/template.",
2776 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2777 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2778 "'Datastore.AllocateSpace' on any used storage.",
2781 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2783 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2784 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2789 additionalProperties
=> 0,
2791 node
=> get_standard_option
('pve-node'),
2792 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2793 newid
=> get_standard_option
('pve-vmid', {
2794 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2795 description
=> 'VMID for the clone.' }),
2798 type
=> 'string', format
=> 'dns-name',
2799 description
=> "Set a name for the new VM.",
2804 description
=> "Description for the new VM.",
2808 type
=> 'string', format
=> 'pve-poolid',
2809 description
=> "Add the new VM to the specified pool.",
2811 snapname
=> get_standard_option
('pve-snapshot-name', {
2814 storage
=> get_standard_option
('pve-storage-id', {
2815 description
=> "Target storage for full clone.",
2819 description
=> "Target format for file storage. Only valid for full clone.",
2822 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2827 description
=> "Create a full copy of all disks. This is always done when " .
2828 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2830 target
=> get_standard_option
('pve-node', {
2831 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2835 description
=> "Override I/O bandwidth limit (in KiB/s).",
2839 default => 'clone limit from datacenter or storage config',
2849 my $rpcenv = PVE
::RPCEnvironment
::get
();
2850 my $authuser = $rpcenv->get_user();
2852 my $node = extract_param
($param, 'node');
2853 my $vmid = extract_param
($param, 'vmid');
2854 my $newid = extract_param
($param, 'newid');
2855 my $pool = extract_param
($param, 'pool');
2856 $rpcenv->check_pool_exist($pool) if defined($pool);
2858 my $snapname = extract_param
($param, 'snapname');
2859 my $storage = extract_param
($param, 'storage');
2860 my $format = extract_param
($param, 'format');
2861 my $target = extract_param
($param, 'target');
2863 my $localnode = PVE
::INotify
::nodename
();
2865 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2869 PVE
::Cluster
::check_node_exists
($target) if $target;
2871 my $storecfg = PVE
::Storage
::config
();
2874 # check if storage is enabled on local node
2875 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2877 # check if storage is available on target node
2878 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2879 # clone only works if target storage is shared
2880 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2881 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2885 PVE
::Cluster
::check_cfs_quorum
();
2887 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2890 # do all tests after lock but before forking worker - if possible
2892 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2893 PVE
::QemuConfig-
>check_lock($conf);
2895 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2896 die "unexpected state change\n" if $verify_running != $running;
2898 die "snapshot '$snapname' does not exist\n"
2899 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2901 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2903 die "parameter 'storage' not allowed for linked clones\n"
2904 if defined($storage) && !$full;
2906 die "parameter 'format' not allowed for linked clones\n"
2907 if defined($format) && !$full;
2909 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2911 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2913 die "can't clone VM to node '$target' (VM uses local storage)\n"
2914 if $target && !$sharedvm;
2916 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2917 die "unable to create VM $newid: config file already exists\n"
2920 my $newconf = { lock => 'clone' };
2925 foreach my $opt (keys %$oldconf) {
2926 my $value = $oldconf->{$opt};
2928 # do not copy snapshot related info
2929 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2930 $opt eq 'vmstate' || $opt eq 'snapstate';
2932 # no need to copy unused images, because VMID(owner) changes anyways
2933 next if $opt =~ m/^unused\d+$/;
2935 # always change MAC! address
2936 if ($opt =~ m/^net(\d+)$/) {
2937 my $net = PVE
::QemuServer
::parse_net
($value);
2938 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2939 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2940 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2941 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2942 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2943 die "unable to parse drive options for '$opt'\n" if !$drive;
2944 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2945 $newconf->{$opt} = $value; # simply copy configuration
2947 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2948 die "Full clone feature is not supported for drive '$opt'\n"
2949 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2950 $fullclone->{$opt} = 1;
2952 # not full means clone instead of copy
2953 die "Linked clone feature is not supported for drive '$opt'\n"
2954 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2956 $drives->{$opt} = $drive;
2957 push @$vollist, $drive->{file
};
2960 # copy everything else
2961 $newconf->{$opt} = $value;
2965 # auto generate a new uuid
2966 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2967 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2968 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2969 # auto generate a new vmgenid only if the option was set for template
2970 if ($newconf->{vmgenid
}) {
2971 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2974 delete $newconf->{template
};
2976 if ($param->{name
}) {
2977 $newconf->{name
} = $param->{name
};
2979 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2982 if ($param->{description
}) {
2983 $newconf->{description
} = $param->{description
};
2986 # create empty/temp config - this fails if VM already exists on other node
2987 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2988 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2993 my $newvollist = [];
3000 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3002 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3004 my $bwlimit = extract_param
($param, 'bwlimit');
3006 my $total_jobs = scalar(keys %{$drives});
3009 foreach my $opt (keys %$drives) {
3010 my $drive = $drives->{$opt};
3011 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3012 my $completion = $skipcomplete ?
'skip' : 'complete';
3014 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3015 my $storage_list = [ $src_sid ];
3016 push @$storage_list, $storage if defined($storage);
3017 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3019 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3020 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3021 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3023 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3025 PVE
::QemuConfig-
>write_config($newid, $newconf);
3029 delete $newconf->{lock};
3031 # do not write pending changes
3032 if (my @changes = keys %{$newconf->{pending
}}) {
3033 my $pending = join(',', @changes);
3034 warn "found pending changes for '$pending', discarding for clone\n";
3035 delete $newconf->{pending
};
3038 PVE
::QemuConfig-
>write_config($newid, $newconf);
3041 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3042 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3043 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3045 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3046 die "Failed to move config to node '$target' - rename failed: $!\n"
3047 if !rename($conffile, $newconffile);
3050 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3053 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3054 sleep 1; # some storage like rbd need to wait before release volume - really?
3056 foreach my $volid (@$newvollist) {
3057 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3061 PVE
::Firewall
::remove_vmfw_conf
($newid);
3063 unlink $conffile; # avoid races -> last thing before die
3065 die "clone failed: $err";
3071 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3073 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3076 # Aquire exclusive lock lock for $newid
3077 my $lock_target_vm = sub {
3078 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3081 # exclusive lock if VM is running - else shared lock is enough;
3083 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3085 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3089 __PACKAGE__-
>register_method({
3090 name
=> 'move_vm_disk',
3091 path
=> '{vmid}/move_disk',
3095 description
=> "Move volume to different storage.",
3097 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3099 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3100 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3104 additionalProperties
=> 0,
3106 node
=> get_standard_option
('pve-node'),
3107 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3110 description
=> "The disk you want to move.",
3111 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3113 storage
=> get_standard_option
('pve-storage-id', {
3114 description
=> "Target storage.",
3115 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3119 description
=> "Target Format.",
3120 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3125 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3131 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3136 description
=> "Override I/O bandwidth limit (in KiB/s).",
3140 default => 'move limit from datacenter or storage config',
3146 description
=> "the task ID.",
3151 my $rpcenv = PVE
::RPCEnvironment
::get
();
3152 my $authuser = $rpcenv->get_user();
3154 my $node = extract_param
($param, 'node');
3155 my $vmid = extract_param
($param, 'vmid');
3156 my $digest = extract_param
($param, 'digest');
3157 my $disk = extract_param
($param, 'disk');
3158 my $storeid = extract_param
($param, 'storage');
3159 my $format = extract_param
($param, 'format');
3161 my $storecfg = PVE
::Storage
::config
();
3163 my $updatefn = sub {
3164 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3165 PVE
::QemuConfig-
>check_lock($conf);
3167 die "VM config checksum missmatch (file change by other user?)\n"
3168 if $digest && $digest ne $conf->{digest
};
3170 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3172 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3174 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3175 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3177 my $old_volid = $drive->{file
};
3179 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3180 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3184 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3185 (!$format || !$oldfmt || $oldfmt eq $format);
3187 # this only checks snapshots because $disk is passed!
3188 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3189 die "you can't move a disk with snapshots and delete the source\n"
3190 if $snapshotted && $param->{delete};
3192 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3194 my $running = PVE
::QemuServer
::check_running
($vmid);
3196 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3199 my $newvollist = [];
3205 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3207 warn "moving disk with snapshots, snapshots will not be moved!\n"
3210 my $bwlimit = extract_param
($param, 'bwlimit');
3211 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3213 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3214 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3216 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3218 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3220 # convert moved disk to base if part of template
3221 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3222 if PVE
::QemuConfig-
>is_template($conf);
3224 PVE
::QemuConfig-
>write_config($vmid, $conf);
3226 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3227 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3228 eval { mon_cmd
($vmid, "guest-fstrim") };
3232 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3233 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3239 foreach my $volid (@$newvollist) {
3240 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3243 die "storage migration failed: $err";
3246 if ($param->{delete}) {
3248 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3249 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3255 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3258 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3261 my $check_vm_disks_local = sub {
3262 my ($storecfg, $vmconf, $vmid) = @_;
3264 my $local_disks = {};
3266 # add some more information to the disks e.g. cdrom
3267 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3268 my ($volid, $attr) = @_;
3270 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3272 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3273 return if $scfg->{shared
};
3275 # The shared attr here is just a special case where the vdisk
3276 # is marked as shared manually
3277 return if $attr->{shared
};
3278 return if $attr->{cdrom
} and $volid eq "none";
3280 if (exists $local_disks->{$volid}) {
3281 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3283 $local_disks->{$volid} = $attr;
3284 # ensure volid is present in case it's needed
3285 $local_disks->{$volid}->{volid
} = $volid;
3289 return $local_disks;
3292 __PACKAGE__-
>register_method({
3293 name
=> 'migrate_vm_precondition',
3294 path
=> '{vmid}/migrate',
3298 description
=> "Get preconditions for migration.",
3300 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3303 additionalProperties
=> 0,
3305 node
=> get_standard_option
('pve-node'),
3306 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3307 target
=> get_standard_option
('pve-node', {
3308 description
=> "Target node.",
3309 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3317 running
=> { type
=> 'boolean' },
3321 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3323 not_allowed_nodes
=> {
3326 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3330 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3332 local_resources
=> {
3334 description
=> "List local resources e.g. pci, usb"
3341 my $rpcenv = PVE
::RPCEnvironment
::get
();
3343 my $authuser = $rpcenv->get_user();
3345 PVE
::Cluster
::check_cfs_quorum
();
3349 my $vmid = extract_param
($param, 'vmid');
3350 my $target = extract_param
($param, 'target');
3351 my $localnode = PVE
::INotify
::nodename
();
3355 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3356 my $storecfg = PVE
::Storage
::config
();
3359 # try to detect errors early
3360 PVE
::QemuConfig-
>check_lock($vmconf);
3362 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3364 # if vm is not running, return target nodes where local storage is available
3365 # for offline migration
3366 if (!$res->{running
}) {
3367 $res->{allowed_nodes
} = [];
3368 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3369 delete $checked_nodes->{$localnode};
3371 foreach my $node (keys %$checked_nodes) {
3372 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3373 push @{$res->{allowed_nodes
}}, $node;
3377 $res->{not_allowed_nodes
} = $checked_nodes;
3381 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3382 $res->{local_disks
} = [ values %$local_disks ];;
3384 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3386 $res->{local_resources
} = $local_resources;
3393 __PACKAGE__-
>register_method({
3394 name
=> 'migrate_vm',
3395 path
=> '{vmid}/migrate',
3399 description
=> "Migrate virtual machine. Creates a new migration task.",
3401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3404 additionalProperties
=> 0,
3406 node
=> get_standard_option
('pve-node'),
3407 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3408 target
=> get_standard_option
('pve-node', {
3409 description
=> "Target node.",
3410 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3414 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3419 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3424 enum
=> ['secure', 'insecure'],
3425 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3428 migration_network
=> {
3429 type
=> 'string', format
=> 'CIDR',
3430 description
=> "CIDR of the (sub) network that is used for migration.",
3433 "with-local-disks" => {
3435 description
=> "Enable live storage migration for local disk",
3438 targetstorage
=> get_standard_option
('pve-targetstorage', {
3439 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3442 description
=> "Override I/O bandwidth limit (in KiB/s).",
3446 default => 'migrate limit from datacenter or storage config',
3452 description
=> "the task ID.",
3457 my $rpcenv = PVE
::RPCEnvironment
::get
();
3458 my $authuser = $rpcenv->get_user();
3460 my $target = extract_param
($param, 'target');
3462 my $localnode = PVE
::INotify
::nodename
();
3463 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3465 PVE
::Cluster
::check_cfs_quorum
();
3467 PVE
::Cluster
::check_node_exists
($target);
3469 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3471 my $vmid = extract_param
($param, 'vmid');
3473 raise_param_exc
({ force
=> "Only root may use this option." })
3474 if $param->{force
} && $authuser ne 'root@pam';
3476 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3477 if $param->{migration_type
} && $authuser ne 'root@pam';
3479 # allow root only until better network permissions are available
3480 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3481 if $param->{migration_network
} && $authuser ne 'root@pam';
3484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3486 # try to detect errors early
3488 PVE
::QemuConfig-
>check_lock($conf);
3490 if (PVE
::QemuServer
::check_running
($vmid)) {
3491 die "can't migrate running VM without --online\n" if !$param->{online
};
3493 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3494 $param->{online
} = 0;
3497 my $storecfg = PVE
::Storage
::config
();
3499 if (my $targetstorage = $param->{targetstorage
}) {
3500 my $check_storage = sub {
3501 my ($target_sid) = @_;
3502 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3503 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3504 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3505 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3506 if !$scfg->{content
}->{images
};
3509 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3510 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3513 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3514 if !defined($storagemap->{identity
});
3516 foreach my $source (values %{$storagemap->{entries
}}) {
3517 $check_storage->($source);
3520 $check_storage->($storagemap->{default})
3521 if $storagemap->{default};
3523 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3524 if $storagemap->{identity
};
3526 $param->{storagemap
} = $storagemap;
3528 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3531 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3536 print "Requesting HA migration for VM $vmid to node $target\n";
3538 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3539 PVE
::Tools
::run_command
($cmd);
3543 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3548 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3552 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3555 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3560 __PACKAGE__-
>register_method({
3562 path
=> '{vmid}/monitor',
3566 description
=> "Execute Qemu monitor commands.",
3568 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3569 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3572 additionalProperties
=> 0,
3574 node
=> get_standard_option
('pve-node'),
3575 vmid
=> get_standard_option
('pve-vmid'),
3578 description
=> "The monitor command.",
3582 returns
=> { type
=> 'string'},
3586 my $rpcenv = PVE
::RPCEnvironment
::get
();
3587 my $authuser = $rpcenv->get_user();
3590 my $command = shift;
3591 return $command =~ m/^\s*info(\s+|$)/
3592 || $command =~ m/^\s*help\s*$/;
3595 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3596 if !&$is_ro($param->{command
});
3598 my $vmid = $param->{vmid
};
3600 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3604 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3606 $res = "ERROR: $@" if $@;
3611 __PACKAGE__-
>register_method({
3612 name
=> 'resize_vm',
3613 path
=> '{vmid}/resize',
3617 description
=> "Extend volume size.",
3619 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3622 additionalProperties
=> 0,
3624 node
=> get_standard_option
('pve-node'),
3625 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3626 skiplock
=> get_standard_option
('skiplock'),
3629 description
=> "The disk you want to resize.",
3630 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3634 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3635 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.",
3639 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3645 returns
=> { type
=> 'null'},
3649 my $rpcenv = PVE
::RPCEnvironment
::get
();
3651 my $authuser = $rpcenv->get_user();
3653 my $node = extract_param
($param, 'node');
3655 my $vmid = extract_param
($param, 'vmid');
3657 my $digest = extract_param
($param, 'digest');
3659 my $disk = extract_param
($param, 'disk');
3661 my $sizestr = extract_param
($param, 'size');
3663 my $skiplock = extract_param
($param, 'skiplock');
3664 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3665 if $skiplock && $authuser ne 'root@pam';
3667 my $storecfg = PVE
::Storage
::config
();
3669 my $updatefn = sub {
3671 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3673 die "checksum missmatch (file change by other user?)\n"
3674 if $digest && $digest ne $conf->{digest
};
3675 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3677 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3679 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3681 my (undef, undef, undef, undef, undef, undef, $format) =
3682 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3684 die "can't resize volume: $disk if snapshot exists\n"
3685 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3687 my $volid = $drive->{file
};
3689 die "disk '$disk' has no associated volume\n" if !$volid;
3691 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3693 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3695 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3697 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3698 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3700 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3702 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3703 my ($ext, $newsize, $unit) = ($1, $2, $4);
3706 $newsize = $newsize * 1024;
3707 } elsif ($unit eq 'M') {
3708 $newsize = $newsize * 1024 * 1024;
3709 } elsif ($unit eq 'G') {
3710 $newsize = $newsize * 1024 * 1024 * 1024;
3711 } elsif ($unit eq 'T') {
3712 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3715 $newsize += $size if $ext;
3716 $newsize = int($newsize);
3718 die "shrinking disks is not supported\n" if $newsize < $size;
3720 return if $size == $newsize;
3722 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3724 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3726 $drive->{size
} = $newsize;
3727 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3729 PVE
::QemuConfig-
>write_config($vmid, $conf);
3732 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3736 __PACKAGE__-
>register_method({
3737 name
=> 'snapshot_list',
3738 path
=> '{vmid}/snapshot',
3740 description
=> "List all snapshots.",
3742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3745 protected
=> 1, # qemu pid files are only readable by root
3747 additionalProperties
=> 0,
3749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3750 node
=> get_standard_option
('pve-node'),
3759 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3763 description
=> "Snapshot includes RAM.",
3768 description
=> "Snapshot description.",
3772 description
=> "Snapshot creation time",
3774 renderer
=> 'timestamp',
3778 description
=> "Parent snapshot identifier.",
3784 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3789 my $vmid = $param->{vmid
};
3791 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3792 my $snaphash = $conf->{snapshots
} || {};
3796 foreach my $name (keys %$snaphash) {
3797 my $d = $snaphash->{$name};
3800 snaptime
=> $d->{snaptime
} || 0,
3801 vmstate
=> $d->{vmstate
} ?
1 : 0,
3802 description
=> $d->{description
} || '',
3804 $item->{parent
} = $d->{parent
} if $d->{parent
};
3805 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3809 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3812 digest
=> $conf->{digest
},
3813 running
=> $running,
3814 description
=> "You are here!",
3816 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3818 push @$res, $current;
3823 __PACKAGE__-
>register_method({
3825 path
=> '{vmid}/snapshot',
3829 description
=> "Snapshot a VM.",
3831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3834 additionalProperties
=> 0,
3836 node
=> get_standard_option
('pve-node'),
3837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3838 snapname
=> get_standard_option
('pve-snapshot-name'),
3842 description
=> "Save the vmstate",
3847 description
=> "A textual description or comment.",
3853 description
=> "the task ID.",
3858 my $rpcenv = PVE
::RPCEnvironment
::get
();
3860 my $authuser = $rpcenv->get_user();
3862 my $node = extract_param
($param, 'node');
3864 my $vmid = extract_param
($param, 'vmid');
3866 my $snapname = extract_param
($param, 'snapname');
3868 die "unable to use snapshot name 'current' (reserved name)\n"
3869 if $snapname eq 'current';
3871 die "unable to use snapshot name 'pending' (reserved name)\n"
3872 if lc($snapname) eq 'pending';
3875 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3876 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3877 $param->{description
});
3880 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3883 __PACKAGE__-
>register_method({
3884 name
=> 'snapshot_cmd_idx',
3885 path
=> '{vmid}/snapshot/{snapname}',
3892 additionalProperties
=> 0,
3894 vmid
=> get_standard_option
('pve-vmid'),
3895 node
=> get_standard_option
('pve-node'),
3896 snapname
=> get_standard_option
('pve-snapshot-name'),
3905 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3912 push @$res, { cmd
=> 'rollback' };
3913 push @$res, { cmd
=> 'config' };
3918 __PACKAGE__-
>register_method({
3919 name
=> 'update_snapshot_config',
3920 path
=> '{vmid}/snapshot/{snapname}/config',
3924 description
=> "Update snapshot metadata.",
3926 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3929 additionalProperties
=> 0,
3931 node
=> get_standard_option
('pve-node'),
3932 vmid
=> get_standard_option
('pve-vmid'),
3933 snapname
=> get_standard_option
('pve-snapshot-name'),
3937 description
=> "A textual description or comment.",
3941 returns
=> { type
=> 'null' },
3945 my $rpcenv = PVE
::RPCEnvironment
::get
();
3947 my $authuser = $rpcenv->get_user();
3949 my $vmid = extract_param
($param, 'vmid');
3951 my $snapname = extract_param
($param, 'snapname');
3953 return undef if !defined($param->{description
});
3955 my $updatefn = sub {
3957 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3959 PVE
::QemuConfig-
>check_lock($conf);
3961 my $snap = $conf->{snapshots
}->{$snapname};
3963 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3965 $snap->{description
} = $param->{description
} if defined($param->{description
});
3967 PVE
::QemuConfig-
>write_config($vmid, $conf);
3970 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3975 __PACKAGE__-
>register_method({
3976 name
=> 'get_snapshot_config',
3977 path
=> '{vmid}/snapshot/{snapname}/config',
3980 description
=> "Get snapshot configuration",
3982 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3985 additionalProperties
=> 0,
3987 node
=> get_standard_option
('pve-node'),
3988 vmid
=> get_standard_option
('pve-vmid'),
3989 snapname
=> get_standard_option
('pve-snapshot-name'),
3992 returns
=> { type
=> "object" },
3996 my $rpcenv = PVE
::RPCEnvironment
::get
();
3998 my $authuser = $rpcenv->get_user();
4000 my $vmid = extract_param
($param, 'vmid');
4002 my $snapname = extract_param
($param, 'snapname');
4004 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4006 my $snap = $conf->{snapshots
}->{$snapname};
4008 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4013 __PACKAGE__-
>register_method({
4015 path
=> '{vmid}/snapshot/{snapname}/rollback',
4019 description
=> "Rollback VM state to specified snapshot.",
4021 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4024 additionalProperties
=> 0,
4026 node
=> get_standard_option
('pve-node'),
4027 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4028 snapname
=> get_standard_option
('pve-snapshot-name'),
4033 description
=> "the task ID.",
4038 my $rpcenv = PVE
::RPCEnvironment
::get
();
4040 my $authuser = $rpcenv->get_user();
4042 my $node = extract_param
($param, 'node');
4044 my $vmid = extract_param
($param, 'vmid');
4046 my $snapname = extract_param
($param, 'snapname');
4049 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4050 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4054 # hold migration lock, this makes sure that nobody create replication snapshots
4055 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4058 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4061 __PACKAGE__-
>register_method({
4062 name
=> 'delsnapshot',
4063 path
=> '{vmid}/snapshot/{snapname}',
4067 description
=> "Delete a VM snapshot.",
4069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4072 additionalProperties
=> 0,
4074 node
=> get_standard_option
('pve-node'),
4075 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4076 snapname
=> get_standard_option
('pve-snapshot-name'),
4080 description
=> "For removal from config file, even if removing disk snapshots fails.",
4086 description
=> "the task ID.",
4091 my $rpcenv = PVE
::RPCEnvironment
::get
();
4093 my $authuser = $rpcenv->get_user();
4095 my $node = extract_param
($param, 'node');
4097 my $vmid = extract_param
($param, 'vmid');
4099 my $snapname = extract_param
($param, 'snapname');
4102 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4103 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4106 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4109 __PACKAGE__-
>register_method({
4111 path
=> '{vmid}/template',
4115 description
=> "Create a Template.",
4117 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4118 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4121 additionalProperties
=> 0,
4123 node
=> get_standard_option
('pve-node'),
4124 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4128 description
=> "If you want to convert only 1 disk to base image.",
4129 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4134 returns
=> { type
=> 'null'},
4138 my $rpcenv = PVE
::RPCEnvironment
::get
();
4140 my $authuser = $rpcenv->get_user();
4142 my $node = extract_param
($param, 'node');
4144 my $vmid = extract_param
($param, 'vmid');
4146 my $disk = extract_param
($param, 'disk');
4148 my $updatefn = sub {
4150 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4152 PVE
::QemuConfig-
>check_lock($conf);
4154 die "unable to create template, because VM contains snapshots\n"
4155 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4157 die "you can't convert a template to a template\n"
4158 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4160 die "you can't convert a VM to template if VM is running\n"
4161 if PVE
::QemuServer
::check_running
($vmid);
4164 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4167 $conf->{template
} = 1;
4168 PVE
::QemuConfig-
>write_config($vmid, $conf);
4170 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4173 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4177 __PACKAGE__-
>register_method({
4178 name
=> 'cloudinit_generated_config_dump',
4179 path
=> '{vmid}/cloudinit/dump',
4182 description
=> "Get automatically generated cloudinit config.",
4184 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4187 additionalProperties
=> 0,
4189 node
=> get_standard_option
('pve-node'),
4190 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4192 description
=> 'Config type.',
4194 enum
=> ['user', 'network', 'meta'],
4204 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4206 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});