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
1640 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1641 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1644 my $authpath = "/vms/$vmid";
1646 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1648 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1654 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1655 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1656 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1657 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1658 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1660 $family = PVE
::Tools
::get_host_address_family
($node);
1663 my $port = PVE
::Tools
::next_vnc_port
($family);
1670 syslog
('info', "starting vnc proxy $upid\n");
1674 if (defined($serial)) {
1676 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1678 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1679 '-timeout', $timeout, '-authpath', $authpath,
1680 '-perm', 'Sys.Console'];
1682 if ($param->{websocket
}) {
1683 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1684 push @$cmd, '-notls', '-listen', 'localhost';
1687 push @$cmd, '-c', @$remcmd, @$termcmd;
1689 PVE
::Tools
::run_command
($cmd);
1693 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1695 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1697 my $sock = IO
::Socket
::IP-
>new(
1702 GetAddrInfoFlags
=> 0,
1703 ) or die "failed to create socket: $!\n";
1704 # Inside the worker we shouldn't have any previous alarms
1705 # running anyway...:
1707 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1709 accept(my $cli, $sock) or die "connection failed: $!\n";
1712 if (PVE
::Tools
::run_command
($cmd,
1713 output
=> '>&'.fileno($cli),
1714 input
=> '<&'.fileno($cli),
1717 die "Failed to run vncproxy.\n";
1724 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1726 PVE
::Tools
::wait_for_vnc_port
($port);
1737 __PACKAGE__-
>register_method({
1738 name
=> 'termproxy',
1739 path
=> '{vmid}/termproxy',
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1745 description
=> "Creates a TCP proxy connections.",
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1754 enum
=> [qw(serial0 serial1 serial2 serial3)],
1755 description
=> "opens a serial terminal (defaults to display)",
1760 additionalProperties
=> 0,
1762 user
=> { type
=> 'string' },
1763 ticket
=> { type
=> 'string' },
1764 port
=> { type
=> 'integer' },
1765 upid
=> { type
=> 'string' },
1771 my $rpcenv = PVE
::RPCEnvironment
::get
();
1773 my $authuser = $rpcenv->get_user();
1775 my $vmid = $param->{vmid
};
1776 my $node = $param->{node
};
1777 my $serial = $param->{serial
};
1779 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1781 if (!defined($serial)) {
1783 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1784 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1788 my $authpath = "/vms/$vmid";
1790 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1795 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1796 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1797 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1798 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1799 push @$remcmd, '--';
1801 $family = PVE
::Tools
::get_host_address_family
($node);
1804 my $port = PVE
::Tools
::next_vnc_port
($family);
1806 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1807 push @$termcmd, '-iface', $serial if $serial;
1812 syslog
('info', "starting qemu termproxy $upid\n");
1814 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1815 '--perm', 'VM.Console', '--'];
1816 push @$cmd, @$remcmd, @$termcmd;
1818 PVE
::Tools
::run_command
($cmd);
1821 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1823 PVE
::Tools
::wait_for_vnc_port
($port);
1833 __PACKAGE__-
>register_method({
1834 name
=> 'vncwebsocket',
1835 path
=> '{vmid}/vncwebsocket',
1838 description
=> "You also need to pass a valid ticket (vncticket).",
1839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1841 description
=> "Opens a weksocket for VNC traffic.",
1843 additionalProperties
=> 0,
1845 node
=> get_standard_option
('pve-node'),
1846 vmid
=> get_standard_option
('pve-vmid'),
1848 description
=> "Ticket from previous call to vncproxy.",
1853 description
=> "Port number returned by previous vncproxy call.",
1863 port
=> { type
=> 'string' },
1869 my $rpcenv = PVE
::RPCEnvironment
::get
();
1871 my $authuser = $rpcenv->get_user();
1873 my $vmid = $param->{vmid
};
1874 my $node = $param->{node
};
1876 my $authpath = "/vms/$vmid";
1878 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1880 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1882 # Note: VNC ports are acessible from outside, so we do not gain any
1883 # security if we verify that $param->{port} belongs to VM $vmid. This
1884 # check is done by verifying the VNC ticket (inside VNC protocol).
1886 my $port = $param->{port
};
1888 return { port
=> $port };
1891 __PACKAGE__-
>register_method({
1892 name
=> 'spiceproxy',
1893 path
=> '{vmid}/spiceproxy',
1898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1900 description
=> "Returns a SPICE configuration to connect to the VM.",
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid'),
1906 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1909 returns
=> get_standard_option
('remote-viewer-config'),
1913 my $rpcenv = PVE
::RPCEnvironment
::get
();
1915 my $authuser = $rpcenv->get_user();
1917 my $vmid = $param->{vmid
};
1918 my $node = $param->{node
};
1919 my $proxy = $param->{proxy
};
1921 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1922 my $title = "VM $vmid";
1923 $title .= " - ". $conf->{name
} if $conf->{name
};
1925 my $port = PVE
::QemuServer
::spice_port
($vmid);
1927 my ($ticket, undef, $remote_viewer_config) =
1928 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1930 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1931 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1933 return $remote_viewer_config;
1936 __PACKAGE__-
>register_method({
1938 path
=> '{vmid}/status',
1941 description
=> "Directory index",
1946 additionalProperties
=> 0,
1948 node
=> get_standard_option
('pve-node'),
1949 vmid
=> get_standard_option
('pve-vmid'),
1957 subdir
=> { type
=> 'string' },
1960 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1966 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1969 { subdir
=> 'current' },
1970 { subdir
=> 'start' },
1971 { subdir
=> 'stop' },
1972 { subdir
=> 'reset' },
1973 { subdir
=> 'shutdown' },
1974 { subdir
=> 'suspend' },
1975 { subdir
=> 'reboot' },
1981 __PACKAGE__-
>register_method({
1982 name
=> 'vm_status',
1983 path
=> '{vmid}/status/current',
1986 protected
=> 1, # qemu pid files are only readable by root
1987 description
=> "Get virtual machine status.",
1989 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1992 additionalProperties
=> 0,
1994 node
=> get_standard_option
('pve-node'),
1995 vmid
=> get_standard_option
('pve-vmid'),
2001 %$PVE::QemuServer
::vmstatus_return_properties
,
2003 description
=> "HA manager service status.",
2007 description
=> "Qemu VGA configuration supports spice.",
2012 description
=> "Qemu GuestAgent enabled in config.",
2022 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2024 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2025 my $status = $vmstatus->{$param->{vmid
}};
2027 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2029 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2030 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2035 __PACKAGE__-
>register_method({
2037 path
=> '{vmid}/status/start',
2041 description
=> "Start virtual machine.",
2043 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid',
2050 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2051 skiplock
=> get_standard_option
('skiplock'),
2052 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2053 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2056 enum
=> ['secure', 'insecure'],
2057 description
=> "Migration traffic is encrypted using an SSH " .
2058 "tunnel by default. On secure, completely private networks " .
2059 "this can be disabled to increase performance.",
2062 migration_network
=> {
2063 type
=> 'string', format
=> 'CIDR',
2064 description
=> "CIDR of the (sub) network that is used for migration.",
2067 machine
=> get_standard_option
('pve-qemu-machine'),
2069 description
=> "Override QEMU's -cpu argument with the given string.",
2073 targetstorage
=> get_standard_option
('pve-targetstorage'),
2075 description
=> "Wait maximal timeout seconds.",
2078 default => 'max(30, vm memory in GiB)',
2089 my $rpcenv = PVE
::RPCEnvironment
::get
();
2090 my $authuser = $rpcenv->get_user();
2092 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2094 my $timeout = extract_param
($param, 'timeout');
2096 my $machine = extract_param
($param, 'machine');
2097 my $force_cpu = extract_param
($param, 'force-cpu');
2099 my $get_root_param = sub {
2100 my $value = extract_param
($param, $_[0]);
2101 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2102 if $value && $authuser ne 'root@pam';
2106 my $stateuri = $get_root_param->('stateuri');
2107 my $skiplock = $get_root_param->('skiplock');
2108 my $migratedfrom = $get_root_param->('migratedfrom');
2109 my $migration_type = $get_root_param->('migration_type');
2110 my $migration_network = $get_root_param->('migration_network');
2111 my $targetstorage = $get_root_param->('targetstorage');
2115 if ($targetstorage) {
2116 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2118 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2119 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2123 # read spice ticket from STDIN
2125 my $nbd_protocol_version = 0;
2126 my $replicated_volumes = {};
2127 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2128 while (defined(my $line = <STDIN
>)) {
2130 if ($line =~ m/^spice_ticket: (.+)$/) {
2132 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2133 $nbd_protocol_version = $1;
2134 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2135 $replicated_volumes->{$1} = 1;
2137 # fallback for old source node
2138 $spice_ticket = $line;
2143 PVE
::Cluster
::check_cfs_quorum
();
2145 my $storecfg = PVE
::Storage
::config
();
2147 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2151 print "Requesting HA start for VM $vmid\n";
2153 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2154 PVE
::Tools
::run_command
($cmd);
2158 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2165 syslog
('info', "start VM $vmid: $upid\n");
2167 my $migrate_opts = {
2168 migratedfrom
=> $migratedfrom,
2169 spice_ticket
=> $spice_ticket,
2170 network
=> $migration_network,
2171 type
=> $migration_type,
2172 storagemap
=> $storagemap,
2173 nbd_proto_version
=> $nbd_protocol_version,
2174 replicated_volumes
=> $replicated_volumes,
2178 statefile
=> $stateuri,
2179 skiplock
=> $skiplock,
2180 forcemachine
=> $machine,
2181 timeout
=> $timeout,
2182 forcecpu
=> $force_cpu,
2185 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2189 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2193 __PACKAGE__-
>register_method({
2195 path
=> '{vmid}/status/stop',
2199 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2200 "is akin to pulling the power plug of a running computer and may damage the VM data",
2202 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2205 additionalProperties
=> 0,
2207 node
=> get_standard_option
('pve-node'),
2208 vmid
=> get_standard_option
('pve-vmid',
2209 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2210 skiplock
=> get_standard_option
('skiplock'),
2211 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2213 description
=> "Wait maximal timeout seconds.",
2219 description
=> "Do not deactivate storage volumes.",
2232 my $rpcenv = PVE
::RPCEnvironment
::get
();
2233 my $authuser = $rpcenv->get_user();
2235 my $node = extract_param
($param, 'node');
2236 my $vmid = extract_param
($param, 'vmid');
2238 my $skiplock = extract_param
($param, 'skiplock');
2239 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2240 if $skiplock && $authuser ne 'root@pam';
2242 my $keepActive = extract_param
($param, 'keepActive');
2243 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2244 if $keepActive && $authuser ne 'root@pam';
2246 my $migratedfrom = extract_param
($param, 'migratedfrom');
2247 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2248 if $migratedfrom && $authuser ne 'root@pam';
2251 my $storecfg = PVE
::Storage
::config
();
2253 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2258 print "Requesting HA stop for VM $vmid\n";
2260 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2261 PVE
::Tools
::run_command
($cmd);
2265 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2271 syslog
('info', "stop VM $vmid: $upid\n");
2273 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2274 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2278 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2282 __PACKAGE__-
>register_method({
2284 path
=> '{vmid}/status/reset',
2288 description
=> "Reset virtual machine.",
2290 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2293 additionalProperties
=> 0,
2295 node
=> get_standard_option
('pve-node'),
2296 vmid
=> get_standard_option
('pve-vmid',
2297 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2298 skiplock
=> get_standard_option
('skiplock'),
2307 my $rpcenv = PVE
::RPCEnvironment
::get
();
2309 my $authuser = $rpcenv->get_user();
2311 my $node = extract_param
($param, 'node');
2313 my $vmid = extract_param
($param, 'vmid');
2315 my $skiplock = extract_param
($param, 'skiplock');
2316 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2317 if $skiplock && $authuser ne 'root@pam';
2319 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2324 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2329 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2332 __PACKAGE__-
>register_method({
2333 name
=> 'vm_shutdown',
2334 path
=> '{vmid}/status/shutdown',
2338 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2339 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2341 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2344 additionalProperties
=> 0,
2346 node
=> get_standard_option
('pve-node'),
2347 vmid
=> get_standard_option
('pve-vmid',
2348 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2349 skiplock
=> get_standard_option
('skiplock'),
2351 description
=> "Wait maximal timeout seconds.",
2357 description
=> "Make sure the VM stops.",
2363 description
=> "Do not deactivate storage volumes.",
2376 my $rpcenv = PVE
::RPCEnvironment
::get
();
2377 my $authuser = $rpcenv->get_user();
2379 my $node = extract_param
($param, 'node');
2380 my $vmid = extract_param
($param, 'vmid');
2382 my $skiplock = extract_param
($param, 'skiplock');
2383 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2384 if $skiplock && $authuser ne 'root@pam';
2386 my $keepActive = extract_param
($param, 'keepActive');
2387 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2388 if $keepActive && $authuser ne 'root@pam';
2390 my $storecfg = PVE
::Storage
::config
();
2394 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2395 # otherwise, we will infer a shutdown command, but run into the timeout,
2396 # then when the vm is resumed, it will instantly shutdown
2398 # checking the qmp status here to get feedback to the gui/cli/api
2399 # and the status query should not take too long
2400 my $qmpstatus = eval {
2401 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2402 mon_cmd
($vmid, "query-status");
2406 if (!$err && $qmpstatus->{status
} eq "paused") {
2407 if ($param->{forceStop
}) {
2408 warn "VM is paused - stop instead of shutdown\n";
2411 die "VM is paused - cannot shutdown\n";
2415 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2417 my $timeout = $param->{timeout
} // 60;
2421 print "Requesting HA stop for VM $vmid\n";
2423 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2424 PVE
::Tools
::run_command
($cmd);
2428 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2435 syslog
('info', "shutdown VM $vmid: $upid\n");
2437 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2438 $shutdown, $param->{forceStop
}, $keepActive);
2442 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2446 __PACKAGE__-
>register_method({
2447 name
=> 'vm_reboot',
2448 path
=> '{vmid}/status/reboot',
2452 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2454 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2457 additionalProperties
=> 0,
2459 node
=> get_standard_option
('pve-node'),
2460 vmid
=> get_standard_option
('pve-vmid',
2461 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2463 description
=> "Wait maximal timeout seconds for the shutdown.",
2476 my $rpcenv = PVE
::RPCEnvironment
::get
();
2477 my $authuser = $rpcenv->get_user();
2479 my $node = extract_param
($param, 'node');
2480 my $vmid = extract_param
($param, 'vmid');
2482 my $qmpstatus = eval {
2483 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2484 mon_cmd
($vmid, "query-status");
2488 if (!$err && $qmpstatus->{status
} eq "paused") {
2489 die "VM is paused - cannot shutdown\n";
2492 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2497 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2498 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2502 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2505 __PACKAGE__-
>register_method({
2506 name
=> 'vm_suspend',
2507 path
=> '{vmid}/status/suspend',
2511 description
=> "Suspend virtual machine.",
2513 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2514 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2515 " on the storage for the vmstate.",
2516 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2519 additionalProperties
=> 0,
2521 node
=> get_standard_option
('pve-node'),
2522 vmid
=> get_standard_option
('pve-vmid',
2523 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2524 skiplock
=> get_standard_option
('skiplock'),
2529 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2531 statestorage
=> get_standard_option
('pve-storage-id', {
2532 description
=> "The storage for the VM state",
2533 requires
=> 'todisk',
2535 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2545 my $rpcenv = PVE
::RPCEnvironment
::get
();
2546 my $authuser = $rpcenv->get_user();
2548 my $node = extract_param
($param, 'node');
2549 my $vmid = extract_param
($param, 'vmid');
2551 my $todisk = extract_param
($param, 'todisk') // 0;
2553 my $statestorage = extract_param
($param, 'statestorage');
2555 my $skiplock = extract_param
($param, 'skiplock');
2556 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2557 if $skiplock && $authuser ne 'root@pam';
2559 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2561 die "Cannot suspend HA managed VM to disk\n"
2562 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2564 # early check for storage permission, for better user feedback
2566 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2568 if (!$statestorage) {
2569 # get statestorage from config if none is given
2570 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2571 my $storecfg = PVE
::Storage
::config
();
2572 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2575 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2581 syslog
('info', "suspend VM $vmid: $upid\n");
2583 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2588 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2589 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2592 __PACKAGE__-
>register_method({
2593 name
=> 'vm_resume',
2594 path
=> '{vmid}/status/resume',
2598 description
=> "Resume virtual machine.",
2600 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2603 additionalProperties
=> 0,
2605 node
=> get_standard_option
('pve-node'),
2606 vmid
=> get_standard_option
('pve-vmid',
2607 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2608 skiplock
=> get_standard_option
('skiplock'),
2609 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2619 my $rpcenv = PVE
::RPCEnvironment
::get
();
2621 my $authuser = $rpcenv->get_user();
2623 my $node = extract_param
($param, 'node');
2625 my $vmid = extract_param
($param, 'vmid');
2627 my $skiplock = extract_param
($param, 'skiplock');
2628 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2629 if $skiplock && $authuser ne 'root@pam';
2631 my $nocheck = extract_param
($param, 'nocheck');
2632 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2633 if $nocheck && $authuser ne 'root@pam';
2635 my $to_disk_suspended;
2637 PVE
::QemuConfig-
>lock_config($vmid, sub {
2638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2639 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2643 die "VM $vmid not running\n"
2644 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2649 syslog
('info', "resume VM $vmid: $upid\n");
2651 if (!$to_disk_suspended) {
2652 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2654 my $storecfg = PVE
::Storage
::config
();
2655 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2661 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2664 __PACKAGE__-
>register_method({
2665 name
=> 'vm_sendkey',
2666 path
=> '{vmid}/sendkey',
2670 description
=> "Send key event to virtual machine.",
2672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2675 additionalProperties
=> 0,
2677 node
=> get_standard_option
('pve-node'),
2678 vmid
=> get_standard_option
('pve-vmid',
2679 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2680 skiplock
=> get_standard_option
('skiplock'),
2682 description
=> "The key (qemu monitor encoding).",
2687 returns
=> { type
=> 'null'},
2691 my $rpcenv = PVE
::RPCEnvironment
::get
();
2693 my $authuser = $rpcenv->get_user();
2695 my $node = extract_param
($param, 'node');
2697 my $vmid = extract_param
($param, 'vmid');
2699 my $skiplock = extract_param
($param, 'skiplock');
2700 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2701 if $skiplock && $authuser ne 'root@pam';
2703 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2708 __PACKAGE__-
>register_method({
2709 name
=> 'vm_feature',
2710 path
=> '{vmid}/feature',
2714 description
=> "Check if feature for virtual machine is available.",
2716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2719 additionalProperties
=> 0,
2721 node
=> get_standard_option
('pve-node'),
2722 vmid
=> get_standard_option
('pve-vmid'),
2724 description
=> "Feature to check.",
2726 enum
=> [ 'snapshot', 'clone', 'copy' ],
2728 snapname
=> get_standard_option
('pve-snapshot-name', {
2736 hasFeature
=> { type
=> 'boolean' },
2739 items
=> { type
=> 'string' },
2746 my $node = extract_param
($param, 'node');
2748 my $vmid = extract_param
($param, 'vmid');
2750 my $snapname = extract_param
($param, 'snapname');
2752 my $feature = extract_param
($param, 'feature');
2754 my $running = PVE
::QemuServer
::check_running
($vmid);
2756 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2759 my $snap = $conf->{snapshots
}->{$snapname};
2760 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2763 my $storecfg = PVE
::Storage
::config
();
2765 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2766 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2769 hasFeature
=> $hasFeature,
2770 nodes
=> [ keys %$nodelist ],
2774 __PACKAGE__-
>register_method({
2776 path
=> '{vmid}/clone',
2780 description
=> "Create a copy of virtual machine/template.",
2782 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2783 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2784 "'Datastore.AllocateSpace' on any used storage.",
2787 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2789 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2790 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2795 additionalProperties
=> 0,
2797 node
=> get_standard_option
('pve-node'),
2798 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2799 newid
=> get_standard_option
('pve-vmid', {
2800 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2801 description
=> 'VMID for the clone.' }),
2804 type
=> 'string', format
=> 'dns-name',
2805 description
=> "Set a name for the new VM.",
2810 description
=> "Description for the new VM.",
2814 type
=> 'string', format
=> 'pve-poolid',
2815 description
=> "Add the new VM to the specified pool.",
2817 snapname
=> get_standard_option
('pve-snapshot-name', {
2820 storage
=> get_standard_option
('pve-storage-id', {
2821 description
=> "Target storage for full clone.",
2825 description
=> "Target format for file storage. Only valid for full clone.",
2828 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2833 description
=> "Create a full copy of all disks. This is always done when " .
2834 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2836 target
=> get_standard_option
('pve-node', {
2837 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2841 description
=> "Override I/O bandwidth limit (in KiB/s).",
2845 default => 'clone limit from datacenter or storage config',
2855 my $rpcenv = PVE
::RPCEnvironment
::get
();
2856 my $authuser = $rpcenv->get_user();
2858 my $node = extract_param
($param, 'node');
2859 my $vmid = extract_param
($param, 'vmid');
2860 my $newid = extract_param
($param, 'newid');
2861 my $pool = extract_param
($param, 'pool');
2862 $rpcenv->check_pool_exist($pool) if defined($pool);
2864 my $snapname = extract_param
($param, 'snapname');
2865 my $storage = extract_param
($param, 'storage');
2866 my $format = extract_param
($param, 'format');
2867 my $target = extract_param
($param, 'target');
2869 my $localnode = PVE
::INotify
::nodename
();
2871 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2875 PVE
::Cluster
::check_node_exists
($target) if $target;
2877 my $storecfg = PVE
::Storage
::config
();
2880 # check if storage is enabled on local node
2881 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2883 # check if storage is available on target node
2884 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2885 # clone only works if target storage is shared
2886 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2887 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2891 PVE
::Cluster
::check_cfs_quorum
();
2893 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2896 # do all tests after lock but before forking worker - if possible
2898 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2899 PVE
::QemuConfig-
>check_lock($conf);
2901 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2902 die "unexpected state change\n" if $verify_running != $running;
2904 die "snapshot '$snapname' does not exist\n"
2905 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2907 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2909 die "parameter 'storage' not allowed for linked clones\n"
2910 if defined($storage) && !$full;
2912 die "parameter 'format' not allowed for linked clones\n"
2913 if defined($format) && !$full;
2915 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2917 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2919 die "can't clone VM to node '$target' (VM uses local storage)\n"
2920 if $target && !$sharedvm;
2922 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2923 die "unable to create VM $newid: config file already exists\n"
2926 my $newconf = { lock => 'clone' };
2931 foreach my $opt (keys %$oldconf) {
2932 my $value = $oldconf->{$opt};
2934 # do not copy snapshot related info
2935 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2936 $opt eq 'vmstate' || $opt eq 'snapstate';
2938 # no need to copy unused images, because VMID(owner) changes anyways
2939 next if $opt =~ m/^unused\d+$/;
2941 # always change MAC! address
2942 if ($opt =~ m/^net(\d+)$/) {
2943 my $net = PVE
::QemuServer
::parse_net
($value);
2944 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2945 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2946 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2947 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2948 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2949 die "unable to parse drive options for '$opt'\n" if !$drive;
2950 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2951 $newconf->{$opt} = $value; # simply copy configuration
2953 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2954 die "Full clone feature is not supported for drive '$opt'\n"
2955 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2956 $fullclone->{$opt} = 1;
2958 # not full means clone instead of copy
2959 die "Linked clone feature is not supported for drive '$opt'\n"
2960 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2962 $drives->{$opt} = $drive;
2963 push @$vollist, $drive->{file
};
2966 # copy everything else
2967 $newconf->{$opt} = $value;
2971 # auto generate a new uuid
2972 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2973 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2974 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2975 # auto generate a new vmgenid only if the option was set for template
2976 if ($newconf->{vmgenid
}) {
2977 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2980 delete $newconf->{template
};
2982 if ($param->{name
}) {
2983 $newconf->{name
} = $param->{name
};
2985 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2988 if ($param->{description
}) {
2989 $newconf->{description
} = $param->{description
};
2992 # create empty/temp config - this fails if VM already exists on other node
2993 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2994 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2999 my $newvollist = [];
3006 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3008 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3010 my $bwlimit = extract_param
($param, 'bwlimit');
3012 my $total_jobs = scalar(keys %{$drives});
3015 foreach my $opt (keys %$drives) {
3016 my $drive = $drives->{$opt};
3017 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3018 my $completion = $skipcomplete ?
'skip' : 'complete';
3020 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3021 my $storage_list = [ $src_sid ];
3022 push @$storage_list, $storage if defined($storage);
3023 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3025 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3026 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3027 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3029 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3031 PVE
::QemuConfig-
>write_config($newid, $newconf);
3035 delete $newconf->{lock};
3037 # do not write pending changes
3038 if (my @changes = keys %{$newconf->{pending
}}) {
3039 my $pending = join(',', @changes);
3040 warn "found pending changes for '$pending', discarding for clone\n";
3041 delete $newconf->{pending
};
3044 PVE
::QemuConfig-
>write_config($newid, $newconf);
3047 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3048 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3049 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3051 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3052 die "Failed to move config to node '$target' - rename failed: $!\n"
3053 if !rename($conffile, $newconffile);
3056 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3059 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3060 sleep 1; # some storage like rbd need to wait before release volume - really?
3062 foreach my $volid (@$newvollist) {
3063 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3067 PVE
::Firewall
::remove_vmfw_conf
($newid);
3069 unlink $conffile; # avoid races -> last thing before die
3071 die "clone failed: $err";
3077 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3079 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3082 # Aquire exclusive lock lock for $newid
3083 my $lock_target_vm = sub {
3084 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3087 # exclusive lock if VM is running - else shared lock is enough;
3089 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3091 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3095 __PACKAGE__-
>register_method({
3096 name
=> 'move_vm_disk',
3097 path
=> '{vmid}/move_disk',
3101 description
=> "Move volume to different storage.",
3103 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3105 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3106 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3110 additionalProperties
=> 0,
3112 node
=> get_standard_option
('pve-node'),
3113 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3116 description
=> "The disk you want to move.",
3117 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3119 storage
=> get_standard_option
('pve-storage-id', {
3120 description
=> "Target storage.",
3121 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3125 description
=> "Target Format.",
3126 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3131 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3137 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3142 description
=> "Override I/O bandwidth limit (in KiB/s).",
3146 default => 'move limit from datacenter or storage config',
3152 description
=> "the task ID.",
3157 my $rpcenv = PVE
::RPCEnvironment
::get
();
3158 my $authuser = $rpcenv->get_user();
3160 my $node = extract_param
($param, 'node');
3161 my $vmid = extract_param
($param, 'vmid');
3162 my $digest = extract_param
($param, 'digest');
3163 my $disk = extract_param
($param, 'disk');
3164 my $storeid = extract_param
($param, 'storage');
3165 my $format = extract_param
($param, 'format');
3167 my $storecfg = PVE
::Storage
::config
();
3169 my $updatefn = sub {
3170 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3171 PVE
::QemuConfig-
>check_lock($conf);
3173 die "VM config checksum missmatch (file change by other user?)\n"
3174 if $digest && $digest ne $conf->{digest
};
3176 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3178 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3180 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3181 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3183 my $old_volid = $drive->{file
};
3185 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3186 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3190 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3191 (!$format || !$oldfmt || $oldfmt eq $format);
3193 # this only checks snapshots because $disk is passed!
3194 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3195 die "you can't move a disk with snapshots and delete the source\n"
3196 if $snapshotted && $param->{delete};
3198 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3200 my $running = PVE
::QemuServer
::check_running
($vmid);
3202 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3205 my $newvollist = [];
3211 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3213 warn "moving disk with snapshots, snapshots will not be moved!\n"
3216 my $bwlimit = extract_param
($param, 'bwlimit');
3217 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3219 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3220 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3222 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3224 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3226 # convert moved disk to base if part of template
3227 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3228 if PVE
::QemuConfig-
>is_template($conf);
3230 PVE
::QemuConfig-
>write_config($vmid, $conf);
3232 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3233 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3234 eval { mon_cmd
($vmid, "guest-fstrim") };
3238 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3239 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3245 foreach my $volid (@$newvollist) {
3246 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3249 die "storage migration failed: $err";
3252 if ($param->{delete}) {
3254 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3255 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3261 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3264 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3267 my $check_vm_disks_local = sub {
3268 my ($storecfg, $vmconf, $vmid) = @_;
3270 my $local_disks = {};
3272 # add some more information to the disks e.g. cdrom
3273 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3274 my ($volid, $attr) = @_;
3276 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3278 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3279 return if $scfg->{shared
};
3281 # The shared attr here is just a special case where the vdisk
3282 # is marked as shared manually
3283 return if $attr->{shared
};
3284 return if $attr->{cdrom
} and $volid eq "none";
3286 if (exists $local_disks->{$volid}) {
3287 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3289 $local_disks->{$volid} = $attr;
3290 # ensure volid is present in case it's needed
3291 $local_disks->{$volid}->{volid
} = $volid;
3295 return $local_disks;
3298 __PACKAGE__-
>register_method({
3299 name
=> 'migrate_vm_precondition',
3300 path
=> '{vmid}/migrate',
3304 description
=> "Get preconditions for migration.",
3306 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3309 additionalProperties
=> 0,
3311 node
=> get_standard_option
('pve-node'),
3312 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3313 target
=> get_standard_option
('pve-node', {
3314 description
=> "Target node.",
3315 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3323 running
=> { type
=> 'boolean' },
3327 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3329 not_allowed_nodes
=> {
3332 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3336 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3338 local_resources
=> {
3340 description
=> "List local resources e.g. pci, usb"
3347 my $rpcenv = PVE
::RPCEnvironment
::get
();
3349 my $authuser = $rpcenv->get_user();
3351 PVE
::Cluster
::check_cfs_quorum
();
3355 my $vmid = extract_param
($param, 'vmid');
3356 my $target = extract_param
($param, 'target');
3357 my $localnode = PVE
::INotify
::nodename
();
3361 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3362 my $storecfg = PVE
::Storage
::config
();
3365 # try to detect errors early
3366 PVE
::QemuConfig-
>check_lock($vmconf);
3368 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3370 # if vm is not running, return target nodes where local storage is available
3371 # for offline migration
3372 if (!$res->{running
}) {
3373 $res->{allowed_nodes
} = [];
3374 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3375 delete $checked_nodes->{$localnode};
3377 foreach my $node (keys %$checked_nodes) {
3378 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3379 push @{$res->{allowed_nodes
}}, $node;
3383 $res->{not_allowed_nodes
} = $checked_nodes;
3387 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3388 $res->{local_disks
} = [ values %$local_disks ];;
3390 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3392 $res->{local_resources
} = $local_resources;
3399 __PACKAGE__-
>register_method({
3400 name
=> 'migrate_vm',
3401 path
=> '{vmid}/migrate',
3405 description
=> "Migrate virtual machine. Creates a new migration task.",
3407 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3410 additionalProperties
=> 0,
3412 node
=> get_standard_option
('pve-node'),
3413 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3414 target
=> get_standard_option
('pve-node', {
3415 description
=> "Target node.",
3416 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3420 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3425 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3430 enum
=> ['secure', 'insecure'],
3431 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3434 migration_network
=> {
3435 type
=> 'string', format
=> 'CIDR',
3436 description
=> "CIDR of the (sub) network that is used for migration.",
3439 "with-local-disks" => {
3441 description
=> "Enable live storage migration for local disk",
3444 targetstorage
=> get_standard_option
('pve-targetstorage', {
3445 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3448 description
=> "Override I/O bandwidth limit (in KiB/s).",
3452 default => 'migrate limit from datacenter or storage config',
3458 description
=> "the task ID.",
3463 my $rpcenv = PVE
::RPCEnvironment
::get
();
3464 my $authuser = $rpcenv->get_user();
3466 my $target = extract_param
($param, 'target');
3468 my $localnode = PVE
::INotify
::nodename
();
3469 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3471 PVE
::Cluster
::check_cfs_quorum
();
3473 PVE
::Cluster
::check_node_exists
($target);
3475 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3477 my $vmid = extract_param
($param, 'vmid');
3479 raise_param_exc
({ force
=> "Only root may use this option." })
3480 if $param->{force
} && $authuser ne 'root@pam';
3482 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3483 if $param->{migration_type
} && $authuser ne 'root@pam';
3485 # allow root only until better network permissions are available
3486 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3487 if $param->{migration_network
} && $authuser ne 'root@pam';
3490 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3492 # try to detect errors early
3494 PVE
::QemuConfig-
>check_lock($conf);
3496 if (PVE
::QemuServer
::check_running
($vmid)) {
3497 die "can't migrate running VM without --online\n" if !$param->{online
};
3499 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3500 $param->{online
} = 0;
3503 my $storecfg = PVE
::Storage
::config
();
3505 if (my $targetstorage = $param->{targetstorage
}) {
3506 my $check_storage = sub {
3507 my ($target_sid) = @_;
3508 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3509 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3510 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3511 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3512 if !$scfg->{content
}->{images
};
3515 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3516 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3519 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3520 if !defined($storagemap->{identity
});
3522 foreach my $source (values %{$storagemap->{entries
}}) {
3523 $check_storage->($source);
3526 $check_storage->($storagemap->{default})
3527 if $storagemap->{default};
3529 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3530 if $storagemap->{identity
};
3532 $param->{storagemap
} = $storagemap;
3534 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3537 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3542 print "Requesting HA migration for VM $vmid to node $target\n";
3544 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3545 PVE
::Tools
::run_command
($cmd);
3549 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3554 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3558 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3561 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3566 __PACKAGE__-
>register_method({
3568 path
=> '{vmid}/monitor',
3572 description
=> "Execute Qemu monitor commands.",
3574 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3578 additionalProperties
=> 0,
3580 node
=> get_standard_option
('pve-node'),
3581 vmid
=> get_standard_option
('pve-vmid'),
3584 description
=> "The monitor command.",
3588 returns
=> { type
=> 'string'},
3592 my $rpcenv = PVE
::RPCEnvironment
::get
();
3593 my $authuser = $rpcenv->get_user();
3596 my $command = shift;
3597 return $command =~ m/^\s*info(\s+|$)/
3598 || $command =~ m/^\s*help\s*$/;
3601 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3602 if !&$is_ro($param->{command
});
3604 my $vmid = $param->{vmid
};
3606 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3610 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3612 $res = "ERROR: $@" if $@;
3617 __PACKAGE__-
>register_method({
3618 name
=> 'resize_vm',
3619 path
=> '{vmid}/resize',
3623 description
=> "Extend volume size.",
3625 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3628 additionalProperties
=> 0,
3630 node
=> get_standard_option
('pve-node'),
3631 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3632 skiplock
=> get_standard_option
('skiplock'),
3635 description
=> "The disk you want to resize.",
3636 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3640 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3641 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.",
3645 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3651 returns
=> { type
=> 'null'},
3655 my $rpcenv = PVE
::RPCEnvironment
::get
();
3657 my $authuser = $rpcenv->get_user();
3659 my $node = extract_param
($param, 'node');
3661 my $vmid = extract_param
($param, 'vmid');
3663 my $digest = extract_param
($param, 'digest');
3665 my $disk = extract_param
($param, 'disk');
3667 my $sizestr = extract_param
($param, 'size');
3669 my $skiplock = extract_param
($param, 'skiplock');
3670 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3671 if $skiplock && $authuser ne 'root@pam';
3673 my $storecfg = PVE
::Storage
::config
();
3675 my $updatefn = sub {
3677 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3679 die "checksum missmatch (file change by other user?)\n"
3680 if $digest && $digest ne $conf->{digest
};
3681 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3683 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3685 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3687 my (undef, undef, undef, undef, undef, undef, $format) =
3688 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3690 die "can't resize volume: $disk if snapshot exists\n"
3691 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3693 my $volid = $drive->{file
};
3695 die "disk '$disk' has no associated volume\n" if !$volid;
3697 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3699 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3701 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3703 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3704 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3706 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3708 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3709 my ($ext, $newsize, $unit) = ($1, $2, $4);
3712 $newsize = $newsize * 1024;
3713 } elsif ($unit eq 'M') {
3714 $newsize = $newsize * 1024 * 1024;
3715 } elsif ($unit eq 'G') {
3716 $newsize = $newsize * 1024 * 1024 * 1024;
3717 } elsif ($unit eq 'T') {
3718 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3721 $newsize += $size if $ext;
3722 $newsize = int($newsize);
3724 die "shrinking disks is not supported\n" if $newsize < $size;
3726 return if $size == $newsize;
3728 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3730 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3732 $drive->{size
} = $newsize;
3733 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3735 PVE
::QemuConfig-
>write_config($vmid, $conf);
3738 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3742 __PACKAGE__-
>register_method({
3743 name
=> 'snapshot_list',
3744 path
=> '{vmid}/snapshot',
3746 description
=> "List all snapshots.",
3748 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3751 protected
=> 1, # qemu pid files are only readable by root
3753 additionalProperties
=> 0,
3755 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3756 node
=> get_standard_option
('pve-node'),
3765 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3769 description
=> "Snapshot includes RAM.",
3774 description
=> "Snapshot description.",
3778 description
=> "Snapshot creation time",
3780 renderer
=> 'timestamp',
3784 description
=> "Parent snapshot identifier.",
3790 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3795 my $vmid = $param->{vmid
};
3797 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3798 my $snaphash = $conf->{snapshots
} || {};
3802 foreach my $name (keys %$snaphash) {
3803 my $d = $snaphash->{$name};
3806 snaptime
=> $d->{snaptime
} || 0,
3807 vmstate
=> $d->{vmstate
} ?
1 : 0,
3808 description
=> $d->{description
} || '',
3810 $item->{parent
} = $d->{parent
} if $d->{parent
};
3811 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3815 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3818 digest
=> $conf->{digest
},
3819 running
=> $running,
3820 description
=> "You are here!",
3822 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3824 push @$res, $current;
3829 __PACKAGE__-
>register_method({
3831 path
=> '{vmid}/snapshot',
3835 description
=> "Snapshot a VM.",
3837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3840 additionalProperties
=> 0,
3842 node
=> get_standard_option
('pve-node'),
3843 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3844 snapname
=> get_standard_option
('pve-snapshot-name'),
3848 description
=> "Save the vmstate",
3853 description
=> "A textual description or comment.",
3859 description
=> "the task ID.",
3864 my $rpcenv = PVE
::RPCEnvironment
::get
();
3866 my $authuser = $rpcenv->get_user();
3868 my $node = extract_param
($param, 'node');
3870 my $vmid = extract_param
($param, 'vmid');
3872 my $snapname = extract_param
($param, 'snapname');
3874 die "unable to use snapshot name 'current' (reserved name)\n"
3875 if $snapname eq 'current';
3877 die "unable to use snapshot name 'pending' (reserved name)\n"
3878 if lc($snapname) eq 'pending';
3881 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3882 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3883 $param->{description
});
3886 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3889 __PACKAGE__-
>register_method({
3890 name
=> 'snapshot_cmd_idx',
3891 path
=> '{vmid}/snapshot/{snapname}',
3898 additionalProperties
=> 0,
3900 vmid
=> get_standard_option
('pve-vmid'),
3901 node
=> get_standard_option
('pve-node'),
3902 snapname
=> get_standard_option
('pve-snapshot-name'),
3911 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3918 push @$res, { cmd
=> 'rollback' };
3919 push @$res, { cmd
=> 'config' };
3924 __PACKAGE__-
>register_method({
3925 name
=> 'update_snapshot_config',
3926 path
=> '{vmid}/snapshot/{snapname}/config',
3930 description
=> "Update snapshot metadata.",
3932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3935 additionalProperties
=> 0,
3937 node
=> get_standard_option
('pve-node'),
3938 vmid
=> get_standard_option
('pve-vmid'),
3939 snapname
=> get_standard_option
('pve-snapshot-name'),
3943 description
=> "A textual description or comment.",
3947 returns
=> { type
=> 'null' },
3951 my $rpcenv = PVE
::RPCEnvironment
::get
();
3953 my $authuser = $rpcenv->get_user();
3955 my $vmid = extract_param
($param, 'vmid');
3957 my $snapname = extract_param
($param, 'snapname');
3959 return undef if !defined($param->{description
});
3961 my $updatefn = sub {
3963 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3965 PVE
::QemuConfig-
>check_lock($conf);
3967 my $snap = $conf->{snapshots
}->{$snapname};
3969 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3971 $snap->{description
} = $param->{description
} if defined($param->{description
});
3973 PVE
::QemuConfig-
>write_config($vmid, $conf);
3976 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3981 __PACKAGE__-
>register_method({
3982 name
=> 'get_snapshot_config',
3983 path
=> '{vmid}/snapshot/{snapname}/config',
3986 description
=> "Get snapshot configuration",
3988 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3991 additionalProperties
=> 0,
3993 node
=> get_standard_option
('pve-node'),
3994 vmid
=> get_standard_option
('pve-vmid'),
3995 snapname
=> get_standard_option
('pve-snapshot-name'),
3998 returns
=> { type
=> "object" },
4002 my $rpcenv = PVE
::RPCEnvironment
::get
();
4004 my $authuser = $rpcenv->get_user();
4006 my $vmid = extract_param
($param, 'vmid');
4008 my $snapname = extract_param
($param, 'snapname');
4010 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4012 my $snap = $conf->{snapshots
}->{$snapname};
4014 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4019 __PACKAGE__-
>register_method({
4021 path
=> '{vmid}/snapshot/{snapname}/rollback',
4025 description
=> "Rollback VM state to specified snapshot.",
4027 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4030 additionalProperties
=> 0,
4032 node
=> get_standard_option
('pve-node'),
4033 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4034 snapname
=> get_standard_option
('pve-snapshot-name'),
4039 description
=> "the task ID.",
4044 my $rpcenv = PVE
::RPCEnvironment
::get
();
4046 my $authuser = $rpcenv->get_user();
4048 my $node = extract_param
($param, 'node');
4050 my $vmid = extract_param
($param, 'vmid');
4052 my $snapname = extract_param
($param, 'snapname');
4055 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4056 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4060 # hold migration lock, this makes sure that nobody create replication snapshots
4061 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4064 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4067 __PACKAGE__-
>register_method({
4068 name
=> 'delsnapshot',
4069 path
=> '{vmid}/snapshot/{snapname}',
4073 description
=> "Delete a VM snapshot.",
4075 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4078 additionalProperties
=> 0,
4080 node
=> get_standard_option
('pve-node'),
4081 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4082 snapname
=> get_standard_option
('pve-snapshot-name'),
4086 description
=> "For removal from config file, even if removing disk snapshots fails.",
4092 description
=> "the task ID.",
4097 my $rpcenv = PVE
::RPCEnvironment
::get
();
4099 my $authuser = $rpcenv->get_user();
4101 my $node = extract_param
($param, 'node');
4103 my $vmid = extract_param
($param, 'vmid');
4105 my $snapname = extract_param
($param, 'snapname');
4108 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4109 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4112 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4115 __PACKAGE__-
>register_method({
4117 path
=> '{vmid}/template',
4121 description
=> "Create a Template.",
4123 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4124 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4127 additionalProperties
=> 0,
4129 node
=> get_standard_option
('pve-node'),
4130 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4134 description
=> "If you want to convert only 1 disk to base image.",
4135 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4140 returns
=> { type
=> 'null'},
4144 my $rpcenv = PVE
::RPCEnvironment
::get
();
4146 my $authuser = $rpcenv->get_user();
4148 my $node = extract_param
($param, 'node');
4150 my $vmid = extract_param
($param, 'vmid');
4152 my $disk = extract_param
($param, 'disk');
4154 my $updatefn = sub {
4156 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4158 PVE
::QemuConfig-
>check_lock($conf);
4160 die "unable to create template, because VM contains snapshots\n"
4161 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4163 die "you can't convert a template to a template\n"
4164 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4166 die "you can't convert a VM to template if VM is running\n"
4167 if PVE
::QemuServer
::check_running
($vmid);
4170 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4173 $conf->{template
} = 1;
4174 PVE
::QemuConfig-
>write_config($vmid, $conf);
4176 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4179 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4183 __PACKAGE__-
>register_method({
4184 name
=> 'cloudinit_generated_config_dump',
4185 path
=> '{vmid}/cloudinit/dump',
4188 description
=> "Get automatically generated cloudinit config.",
4190 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4193 additionalProperties
=> 0,
4195 node
=> get_standard_option
('pve-node'),
4196 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4198 description
=> 'Config type.',
4200 enum
=> ['user', 'network', 'meta'],
4210 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4212 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});