1 package PVE
::API2
::Qemu
;
12 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
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 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.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
159 # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
160 my $cloudinit_iso_size = 4; # in MB
161 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, $name, $cloudinit_iso_size*1024);
163 $disk->{file
} = $volid;
164 $disk->{media
} = 'cdrom';
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
168 } elsif ($volid =~ $NEW_DISK_RE) {
169 my ($storeid, $size) = ($2 || $default_storage, $3);
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
172 my $fmt = $disk->{format
} || $defformat;
174 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
177 if ($ds eq 'efidisk0') {
178 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
180 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
182 push @$vollist, $volid;
183 $disk->{file
} = $volid;
184 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
185 delete $disk->{format
}; # no longer needed
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
189 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
191 my $volid_is_new = 1;
194 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
195 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
200 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
304 my $check_vm_modify_config_perm = sub {
305 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
307 return 1 if $authuser eq 'root@pam';
309 foreach my $opt (@$key_list) {
310 # some checks (e.g., disk, serial port, usb) need to be done somewhere
311 # else, as there the permission can be value dependend
312 next if PVE
::QemuServer
::is_valid_drivename
($opt);
313 next if $opt eq 'cdrom';
314 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
317 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
318 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
319 } elsif ($memoryoptions->{$opt}) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
321 } elsif ($hwtypeoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
323 } elsif ($generaloptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
325 # special case for startup since it changes host behaviour
326 if ($opt eq 'startup') {
327 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
329 } elsif ($vmpoweroptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
331 } elsif ($diskoptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
333 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
336 # catches hostpci\d+, args, lock, etc.
337 # new options will be checked here
338 die "only root can set '$opt' config\n";
345 __PACKAGE__-
>register_method({
349 description
=> "Virtual machine index (per node).",
351 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
355 protected
=> 1, # qemu pid files are only readable by root
357 additionalProperties
=> 0,
359 node
=> get_standard_option
('pve-node'),
363 description
=> "Determine the full status of active VMs.",
371 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
373 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
378 my $rpcenv = PVE
::RPCEnvironment
::get
();
379 my $authuser = $rpcenv->get_user();
381 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
384 foreach my $vmid (keys %$vmstatus) {
385 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
387 my $data = $vmstatus->{$vmid};
396 __PACKAGE__-
>register_method({
400 description
=> "Create or restore a virtual machine.",
402 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
403 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
404 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
405 user
=> 'all', # check inside
410 additionalProperties
=> 0,
411 properties
=> PVE
::QemuServer
::json_config_properties
(
413 node
=> get_standard_option
('pve-node'),
414 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
416 description
=> "The backup file.",
420 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
422 storage
=> get_standard_option
('pve-storage-id', {
423 description
=> "Default storage.",
425 completion
=> \
&PVE
::QemuServer
::complete_storage
,
430 description
=> "Allow to overwrite existing VM.",
431 requires
=> 'archive',
436 description
=> "Assign a unique random ethernet address.",
437 requires
=> 'archive',
441 type
=> 'string', format
=> 'pve-poolid',
442 description
=> "Add the VM to the specified pool.",
445 description
=> "Override I/O bandwidth limit (in KiB/s).",
449 default => 'restore limit from datacenter or storage config',
455 description
=> "Start VM after it was created successfully.",
465 my $rpcenv = PVE
::RPCEnvironment
::get
();
467 my $authuser = $rpcenv->get_user();
469 my $node = extract_param
($param, 'node');
471 my $vmid = extract_param
($param, 'vmid');
473 my $archive = extract_param
($param, 'archive');
474 my $is_restore = !!$archive;
476 my $storage = extract_param
($param, 'storage');
478 my $force = extract_param
($param, 'force');
480 my $unique = extract_param
($param, 'unique');
482 my $pool = extract_param
($param, 'pool');
484 my $bwlimit = extract_param
($param, 'bwlimit');
486 my $start_after_create = extract_param
($param, 'start');
488 my $filename = PVE
::QemuConfig-
>config_file($vmid);
490 my $storecfg = PVE
::Storage
::config
();
492 if (defined(my $ssh_keys = $param->{sshkeys
})) {
493 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
494 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
497 PVE
::Cluster
::check_cfs_quorum
();
499 if (defined($pool)) {
500 $rpcenv->check_pool_exist($pool);
503 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
504 if defined($storage);
506 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
508 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
510 } elsif ($archive && $force && (-f
$filename) &&
511 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
512 # OK: user has VM.Backup permissions, and want to restore an existing VM
518 &$resolve_cdrom_alias($param);
520 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
522 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
524 foreach my $opt (keys %$param) {
525 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
526 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
527 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
529 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
530 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
534 PVE
::QemuServer
::add_random_macs
($param);
536 my $keystr = join(' ', keys %$param);
537 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
539 if ($archive eq '-') {
540 die "pipe requires cli environment\n"
541 if $rpcenv->{type
} ne 'cli';
543 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
544 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
548 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
550 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
551 die "$emsg $@" if $@;
553 my $restorefn = sub {
554 my $conf = PVE
::QemuConfig-
>load_config($vmid);
556 PVE
::QemuConfig-
>check_protection($conf, $emsg);
558 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
559 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
566 bwlimit
=> $bwlimit, });
568 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 if ($start_after_create) {
571 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
576 # ensure no old replication state are exists
577 PVE
::ReplicationState
::delete_guest_states
($vmid);
579 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
583 # ensure no old replication state are exists
584 PVE
::ReplicationState
::delete_guest_states
($vmid);
592 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
596 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
598 if (!$conf->{bootdisk
}) {
599 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
600 $conf->{bootdisk
} = $firstdisk if $firstdisk;
603 # auto generate uuid if user did not specify smbios1 option
604 if (!$conf->{smbios1
}) {
605 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
608 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
609 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
612 PVE
::QemuConfig-
>write_config($vmid, $conf);
618 foreach my $volid (@$vollist) {
619 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
625 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
628 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
630 if ($start_after_create) {
631 print "Execute autostart\n";
632 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
637 my ($code, $worker_name);
639 $worker_name = 'qmrestore';
641 eval { $restorefn->() };
643 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
649 $worker_name = 'qmcreate';
651 eval { $createfn->() };
654 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
655 unlink($conffile) or die "failed to remove config file: $!\n";
663 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
666 __PACKAGE__-
>register_method({
671 description
=> "Directory index",
676 additionalProperties
=> 0,
678 node
=> get_standard_option
('pve-node'),
679 vmid
=> get_standard_option
('pve-vmid'),
687 subdir
=> { type
=> 'string' },
690 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
696 { subdir
=> 'config' },
697 { subdir
=> 'pending' },
698 { subdir
=> 'status' },
699 { subdir
=> 'unlink' },
700 { subdir
=> 'vncproxy' },
701 { subdir
=> 'termproxy' },
702 { subdir
=> 'migrate' },
703 { subdir
=> 'resize' },
704 { subdir
=> 'move' },
706 { subdir
=> 'rrddata' },
707 { subdir
=> 'monitor' },
708 { subdir
=> 'agent' },
709 { subdir
=> 'snapshot' },
710 { subdir
=> 'spiceproxy' },
711 { subdir
=> 'sendkey' },
712 { subdir
=> 'firewall' },
718 __PACKAGE__-
>register_method ({
719 subclass
=> "PVE::API2::Firewall::VM",
720 path
=> '{vmid}/firewall',
723 __PACKAGE__-
>register_method ({
724 subclass
=> "PVE::API2::Qemu::Agent",
725 path
=> '{vmid}/agent',
728 __PACKAGE__-
>register_method({
730 path
=> '{vmid}/rrd',
732 protected
=> 1, # fixme: can we avoid that?
734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
736 description
=> "Read VM RRD statistics (returns PNG)",
738 additionalProperties
=> 0,
740 node
=> get_standard_option
('pve-node'),
741 vmid
=> get_standard_option
('pve-vmid'),
743 description
=> "Specify the time frame you are interested in.",
745 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
748 description
=> "The list of datasources you want to display.",
749 type
=> 'string', format
=> 'pve-configid-list',
752 description
=> "The RRD consolidation function",
754 enum
=> [ 'AVERAGE', 'MAX' ],
762 filename
=> { type
=> 'string' },
768 return PVE
::Cluster
::create_rrd_graph
(
769 "pve2-vm/$param->{vmid}", $param->{timeframe
},
770 $param->{ds
}, $param->{cf
});
774 __PACKAGE__-
>register_method({
776 path
=> '{vmid}/rrddata',
778 protected
=> 1, # fixme: can we avoid that?
780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
782 description
=> "Read VM RRD statistics",
784 additionalProperties
=> 0,
786 node
=> get_standard_option
('pve-node'),
787 vmid
=> get_standard_option
('pve-vmid'),
789 description
=> "Specify the time frame you are interested in.",
791 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
794 description
=> "The RRD consolidation function",
796 enum
=> [ 'AVERAGE', 'MAX' ],
811 return PVE
::Cluster
::create_rrd_data
(
812 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
816 __PACKAGE__-
>register_method({
818 path
=> '{vmid}/config',
821 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
823 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
826 additionalProperties
=> 0,
828 node
=> get_standard_option
('pve-node'),
829 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
831 description
=> "Get current values (instead of pending values).",
836 snapshot
=> get_standard_option
('pve-snapshot-name', {
837 description
=> "Fetch config values from given snapshot.",
840 my ($cmd, $pname, $cur, $args) = @_;
841 PVE
::QemuConfig-
>snapshot_list($args->[0]);
847 description
=> "The current VM configuration.",
849 properties
=> PVE
::QemuServer
::json_config_properties
({
852 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
859 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
861 if (my $snapname = $param->{snapshot
}) {
862 my $snapshot = $conf->{snapshots
}->{$snapname};
863 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
865 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
870 delete $conf->{snapshots
};
872 if (!$param->{current
}) {
873 foreach my $opt (keys %{$conf->{pending
}}) {
874 next if $opt eq 'delete';
875 my $value = $conf->{pending
}->{$opt};
876 next if ref($value); # just to be sure
877 $conf->{$opt} = $value;
879 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
880 foreach my $opt (keys %$pending_delete_hash) {
881 delete $conf->{$opt} if $conf->{$opt};
885 delete $conf->{pending
};
887 # hide cloudinit password
888 if ($conf->{cipassword
}) {
889 $conf->{cipassword
} = '**********';
895 __PACKAGE__-
>register_method({
896 name
=> 'vm_pending',
897 path
=> '{vmid}/pending',
900 description
=> "Get virtual machine configuration, including pending changes.",
902 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
905 additionalProperties
=> 0,
907 node
=> get_standard_option
('pve-node'),
908 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
917 description
=> "Configuration option name.",
921 description
=> "Current value.",
926 description
=> "Pending value.",
931 description
=> "Indicates a pending delete request if present and not 0. " .
932 "The value 2 indicates a force-delete request.",
944 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
946 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
950 foreach my $opt (keys %$conf) {
951 next if ref($conf->{$opt});
952 my $item = { key
=> $opt };
953 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
954 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
955 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
957 # hide cloudinit password
958 if ($opt eq 'cipassword') {
959 $item->{value
} = '**********' if defined($item->{value
});
960 # the trailing space so that the pending string is different
961 $item->{pending
} = '********** ' if defined($item->{pending
});
966 foreach my $opt (keys %{$conf->{pending
}}) {
967 next if $opt eq 'delete';
968 next if ref($conf->{pending
}->{$opt}); # just to be sure
969 next if defined($conf->{$opt});
970 my $item = { key
=> $opt };
971 $item->{pending
} = $conf->{pending
}->{$opt};
973 # hide cloudinit password
974 if ($opt eq 'cipassword') {
975 $item->{pending
} = '**********' if defined($item->{pending
});
980 while (my ($opt, $force) = each %$pending_delete_hash) {
981 next if $conf->{pending
}->{$opt}; # just to be sure
982 next if $conf->{$opt};
983 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
990 # POST/PUT {vmid}/config implementation
992 # The original API used PUT (idempotent) an we assumed that all operations
993 # are fast. But it turned out that almost any configuration change can
994 # involve hot-plug actions, or disk alloc/free. Such actions can take long
995 # time to complete and have side effects (not idempotent).
997 # The new implementation uses POST and forks a worker process. We added
998 # a new option 'background_delay'. If specified we wait up to
999 # 'background_delay' second for the worker task to complete. It returns null
1000 # if the task is finished within that time, else we return the UPID.
1002 my $update_vm_api = sub {
1003 my ($param, $sync) = @_;
1005 my $rpcenv = PVE
::RPCEnvironment
::get
();
1007 my $authuser = $rpcenv->get_user();
1009 my $node = extract_param
($param, 'node');
1011 my $vmid = extract_param
($param, 'vmid');
1013 my $digest = extract_param
($param, 'digest');
1015 my $background_delay = extract_param
($param, 'background_delay');
1017 if (defined(my $cipassword = $param->{cipassword
})) {
1018 # Same logic as in cloud-init (but with the regex fixed...)
1019 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1020 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1023 my @paramarr = (); # used for log message
1024 foreach my $key (sort keys %$param) {
1025 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1026 push @paramarr, "-$key", $value;
1029 my $skiplock = extract_param
($param, 'skiplock');
1030 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1031 if $skiplock && $authuser ne 'root@pam';
1033 my $delete_str = extract_param
($param, 'delete');
1035 my $revert_str = extract_param
($param, 'revert');
1037 my $force = extract_param
($param, 'force');
1039 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1040 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1041 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1044 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1046 my $storecfg = PVE
::Storage
::config
();
1048 my $defaults = PVE
::QemuServer
::load_defaults
();
1050 &$resolve_cdrom_alias($param);
1052 # now try to verify all parameters
1055 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1056 if (!PVE
::QemuServer
::option_exists
($opt)) {
1057 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1060 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1061 "-revert $opt' at the same time" })
1062 if defined($param->{$opt});
1064 $revert->{$opt} = 1;
1068 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1069 $opt = 'ide2' if $opt eq 'cdrom';
1071 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1072 "-delete $opt' at the same time" })
1073 if defined($param->{$opt});
1075 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1076 "-revert $opt' at the same time" })
1079 if (!PVE
::QemuServer
::option_exists
($opt)) {
1080 raise_param_exc
({ delete => "unknown option '$opt'" });
1086 my $repl_conf = PVE
::ReplicationConfig-
>new();
1087 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1088 my $check_replication = sub {
1090 return if !$is_replicated;
1091 my $volid = $drive->{file
};
1092 return if !$volid || !($drive->{replicate
}//1);
1093 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1094 my ($storeid, $format);
1095 if ($volid =~ $NEW_DISK_RE) {
1097 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1099 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1100 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1102 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1104 return if $scfg->{shared
};
1105 die "cannot add non-replicatable volume to a replicated VM\n";
1108 foreach my $opt (keys %$param) {
1109 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1110 # cleanup drive path
1111 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1112 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1113 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1114 $check_replication->($drive);
1115 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1116 } elsif ($opt =~ m/^net(\d+)$/) {
1118 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1119 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1120 } elsif ($opt eq 'vmgenid') {
1121 if ($param->{$opt} eq '1') {
1122 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1124 } elsif ($opt eq 'hookscript') {
1125 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1126 raise_param_exc
({ $opt => $@ }) if $@;
1130 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1132 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1134 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1136 my $updatefn = sub {
1138 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1140 die "checksum missmatch (file change by other user?)\n"
1141 if $digest && $digest ne $conf->{digest
};
1143 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1145 foreach my $opt (keys %$revert) {
1146 if (defined($conf->{$opt})) {
1147 $param->{$opt} = $conf->{$opt};
1148 } elsif (defined($conf->{pending
}->{$opt})) {
1153 if ($param->{memory
} || defined($param->{balloon
})) {
1154 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1155 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1157 die "balloon value too large (must be smaller than assigned memory)\n"
1158 if $balloon && $balloon > $maxmem;
1161 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1165 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1167 # write updates to pending section
1169 my $modified = {}; # record what $option we modify
1171 foreach my $opt (@delete) {
1172 $modified->{$opt} = 1;
1173 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1174 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1175 warn "cannot delete '$opt' - not set in current configuration!\n";
1176 $modified->{$opt} = 0;
1180 if ($opt =~ m/^unused/) {
1181 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1182 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1183 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1184 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1185 delete $conf->{$opt};
1186 PVE
::QemuConfig-
>write_config($vmid, $conf);
1188 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1189 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1190 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1191 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1192 if defined($conf->{pending
}->{$opt});
1193 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1194 PVE
::QemuConfig-
>write_config($vmid, $conf);
1195 } elsif ($opt =~ m/^serial\d+$/) {
1196 if ($conf->{$opt} eq 'socket') {
1197 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1198 } elsif ($authuser ne 'root@pam') {
1199 die "only root can delete '$opt' config for real devices\n";
1201 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1202 PVE
::QemuConfig-
>write_config($vmid, $conf);
1203 } elsif ($opt =~ m/^usb\d+$/) {
1204 if ($conf->{$opt} =~ m/spice/) {
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1206 } elsif ($authuser ne 'root@pam') {
1207 die "only root can delete '$opt' config for real devices\n";
1209 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1210 PVE
::QemuConfig-
>write_config($vmid, $conf);
1212 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf);
1217 foreach my $opt (keys %$param) { # add/change
1218 $modified->{$opt} = 1;
1219 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1220 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1222 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1224 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1225 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1226 # FIXME: cloudinit: CDROM or Disk?
1227 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1228 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1232 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1233 if defined($conf->{pending
}->{$opt});
1235 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1236 } elsif ($opt =~ m/^serial\d+/) {
1237 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1239 } elsif ($authuser ne 'root@pam') {
1240 die "only root can modify '$opt' config for real devices\n";
1242 $conf->{pending
}->{$opt} = $param->{$opt};
1243 } elsif ($opt =~ m/^usb\d+/) {
1244 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1245 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1246 } elsif ($authuser ne 'root@pam') {
1247 die "only root can modify '$opt' config for real devices\n";
1249 $conf->{pending
}->{$opt} = $param->{$opt};
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1254 PVE
::QemuConfig-
>write_config($vmid, $conf);
1257 # remove pending changes when nothing changed
1258 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1259 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1260 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1262 return if !scalar(keys %{$conf->{pending
}});
1264 my $running = PVE
::QemuServer
::check_running
($vmid);
1266 # apply pending changes
1268 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1272 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1273 raise_param_exc
($errors) if scalar(keys %$errors);
1275 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1285 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1287 if ($background_delay) {
1289 # Note: It would be better to do that in the Event based HTTPServer
1290 # to avoid blocking call to sleep.
1292 my $end_time = time() + $background_delay;
1294 my $task = PVE
::Tools
::upid_decode
($upid);
1297 while (time() < $end_time) {
1298 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1300 sleep(1); # this gets interrupted when child process ends
1304 my $status = PVE
::Tools
::upid_read_status
($upid);
1305 return undef if $status eq 'OK';
1314 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1317 my $vm_config_perm_list = [
1322 'VM.Config.Network',
1324 'VM.Config.Options',
1327 __PACKAGE__-
>register_method({
1328 name
=> 'update_vm_async',
1329 path
=> '{vmid}/config',
1333 description
=> "Set virtual machine options (asynchrounous API).",
1335 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1338 additionalProperties
=> 0,
1339 properties
=> PVE
::QemuServer
::json_config_properties
(
1341 node
=> get_standard_option
('pve-node'),
1342 vmid
=> get_standard_option
('pve-vmid'),
1343 skiplock
=> get_standard_option
('skiplock'),
1345 type
=> 'string', format
=> 'pve-configid-list',
1346 description
=> "A list of settings you want to delete.",
1350 type
=> 'string', format
=> 'pve-configid-list',
1351 description
=> "Revert a pending change.",
1356 description
=> $opt_force_description,
1358 requires
=> 'delete',
1362 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1366 background_delay
=> {
1368 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1379 code
=> $update_vm_api,
1382 __PACKAGE__-
>register_method({
1383 name
=> 'update_vm',
1384 path
=> '{vmid}/config',
1388 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1390 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1393 additionalProperties
=> 0,
1394 properties
=> PVE
::QemuServer
::json_config_properties
(
1396 node
=> get_standard_option
('pve-node'),
1397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1398 skiplock
=> get_standard_option
('skiplock'),
1400 type
=> 'string', format
=> 'pve-configid-list',
1401 description
=> "A list of settings you want to delete.",
1405 type
=> 'string', format
=> 'pve-configid-list',
1406 description
=> "Revert a pending change.",
1411 description
=> $opt_force_description,
1413 requires
=> 'delete',
1417 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1423 returns
=> { type
=> 'null' },
1426 &$update_vm_api($param, 1);
1432 __PACKAGE__-
>register_method({
1433 name
=> 'destroy_vm',
1438 description
=> "Destroy the vm (also delete all used/owned volumes).",
1440 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1443 additionalProperties
=> 0,
1445 node
=> get_standard_option
('pve-node'),
1446 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1447 skiplock
=> get_standard_option
('skiplock'),
1456 my $rpcenv = PVE
::RPCEnvironment
::get
();
1458 my $authuser = $rpcenv->get_user();
1460 my $vmid = $param->{vmid
};
1462 my $skiplock = $param->{skiplock
};
1463 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1464 if $skiplock && $authuser ne 'root@pam';
1467 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1469 my $storecfg = PVE
::Storage
::config
();
1471 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1473 die "unable to remove VM $vmid - used in HA resources\n"
1474 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1476 # do not allow destroy if there are replication jobs
1477 my $repl_conf = PVE
::ReplicationConfig-
>new();
1478 $repl_conf->check_for_existing_jobs($vmid);
1480 # early tests (repeat after locking)
1481 die "VM $vmid is running - destroy failed\n"
1482 if PVE
::QemuServer
::check_running
($vmid);
1487 syslog
('info', "destroy VM $vmid: $upid\n");
1489 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1491 PVE
::AccessControl
::remove_vm_access
($vmid);
1493 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1496 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1499 __PACKAGE__-
>register_method({
1501 path
=> '{vmid}/unlink',
1505 description
=> "Unlink/delete disk images.",
1507 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1515 type
=> 'string', format
=> 'pve-configid-list',
1516 description
=> "A list of disk IDs you want to delete.",
1520 description
=> $opt_force_description,
1525 returns
=> { type
=> 'null'},
1529 $param->{delete} = extract_param
($param, 'idlist');
1531 __PACKAGE__-
>update_vm($param);
1538 __PACKAGE__-
>register_method({
1540 path
=> '{vmid}/vncproxy',
1544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1546 description
=> "Creates a TCP VNC proxy connections.",
1548 additionalProperties
=> 0,
1550 node
=> get_standard_option
('pve-node'),
1551 vmid
=> get_standard_option
('pve-vmid'),
1555 description
=> "starts websockify instead of vncproxy",
1560 additionalProperties
=> 0,
1562 user
=> { type
=> 'string' },
1563 ticket
=> { type
=> 'string' },
1564 cert
=> { type
=> 'string' },
1565 port
=> { type
=> 'integer' },
1566 upid
=> { type
=> 'string' },
1572 my $rpcenv = PVE
::RPCEnvironment
::get
();
1574 my $authuser = $rpcenv->get_user();
1576 my $vmid = $param->{vmid
};
1577 my $node = $param->{node
};
1578 my $websocket = $param->{websocket
};
1580 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1581 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1583 my $authpath = "/vms/$vmid";
1585 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1587 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1593 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1594 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1595 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1596 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1597 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1599 $family = PVE
::Tools
::get_host_address_family
($node);
1602 my $port = PVE
::Tools
::next_vnc_port
($family);
1609 syslog
('info', "starting vnc proxy $upid\n");
1615 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1617 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1618 '-timeout', $timeout, '-authpath', $authpath,
1619 '-perm', 'Sys.Console'];
1621 if ($param->{websocket
}) {
1622 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1623 push @$cmd, '-notls', '-listen', 'localhost';
1626 push @$cmd, '-c', @$remcmd, @$termcmd;
1628 PVE
::Tools
::run_command
($cmd);
1632 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1634 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1636 my $sock = IO
::Socket
::IP-
>new(
1641 GetAddrInfoFlags
=> 0,
1642 ) or die "failed to create socket: $!\n";
1643 # Inside the worker we shouldn't have any previous alarms
1644 # running anyway...:
1646 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1648 accept(my $cli, $sock) or die "connection failed: $!\n";
1651 if (PVE
::Tools
::run_command
($cmd,
1652 output
=> '>&'.fileno($cli),
1653 input
=> '<&'.fileno($cli),
1656 die "Failed to run vncproxy.\n";
1663 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1665 PVE
::Tools
::wait_for_vnc_port
($port);
1676 __PACKAGE__-
>register_method({
1677 name
=> 'termproxy',
1678 path
=> '{vmid}/termproxy',
1682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1684 description
=> "Creates a TCP proxy connections.",
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid'),
1693 enum
=> [qw(serial0 serial1 serial2 serial3)],
1694 description
=> "opens a serial terminal (defaults to display)",
1699 additionalProperties
=> 0,
1701 user
=> { type
=> 'string' },
1702 ticket
=> { type
=> 'string' },
1703 port
=> { type
=> 'integer' },
1704 upid
=> { type
=> 'string' },
1710 my $rpcenv = PVE
::RPCEnvironment
::get
();
1712 my $authuser = $rpcenv->get_user();
1714 my $vmid = $param->{vmid
};
1715 my $node = $param->{node
};
1716 my $serial = $param->{serial
};
1718 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1720 if (!defined($serial)) {
1721 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1722 $serial = $conf->{vga
};
1726 my $authpath = "/vms/$vmid";
1728 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1733 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1734 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1735 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1736 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1737 push @$remcmd, '--';
1739 $family = PVE
::Tools
::get_host_address_family
($node);
1742 my $port = PVE
::Tools
::next_vnc_port
($family);
1744 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1745 push @$termcmd, '-iface', $serial if $serial;
1750 syslog
('info', "starting qemu termproxy $upid\n");
1752 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1753 '--perm', 'VM.Console', '--'];
1754 push @$cmd, @$remcmd, @$termcmd;
1756 PVE
::Tools
::run_command
($cmd);
1759 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1761 PVE
::Tools
::wait_for_vnc_port
($port);
1771 __PACKAGE__-
>register_method({
1772 name
=> 'vncwebsocket',
1773 path
=> '{vmid}/vncwebsocket',
1776 description
=> "You also need to pass a valid ticket (vncticket).",
1777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1779 description
=> "Opens a weksocket for VNC traffic.",
1781 additionalProperties
=> 0,
1783 node
=> get_standard_option
('pve-node'),
1784 vmid
=> get_standard_option
('pve-vmid'),
1786 description
=> "Ticket from previous call to vncproxy.",
1791 description
=> "Port number returned by previous vncproxy call.",
1801 port
=> { type
=> 'string' },
1807 my $rpcenv = PVE
::RPCEnvironment
::get
();
1809 my $authuser = $rpcenv->get_user();
1811 my $vmid = $param->{vmid
};
1812 my $node = $param->{node
};
1814 my $authpath = "/vms/$vmid";
1816 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1818 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1820 # Note: VNC ports are acessible from outside, so we do not gain any
1821 # security if we verify that $param->{port} belongs to VM $vmid. This
1822 # check is done by verifying the VNC ticket (inside VNC protocol).
1824 my $port = $param->{port
};
1826 return { port
=> $port };
1829 __PACKAGE__-
>register_method({
1830 name
=> 'spiceproxy',
1831 path
=> '{vmid}/spiceproxy',
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1838 description
=> "Returns a SPICE configuration to connect to the VM.",
1840 additionalProperties
=> 0,
1842 node
=> get_standard_option
('pve-node'),
1843 vmid
=> get_standard_option
('pve-vmid'),
1844 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1847 returns
=> get_standard_option
('remote-viewer-config'),
1851 my $rpcenv = PVE
::RPCEnvironment
::get
();
1853 my $authuser = $rpcenv->get_user();
1855 my $vmid = $param->{vmid
};
1856 my $node = $param->{node
};
1857 my $proxy = $param->{proxy
};
1859 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1860 my $title = "VM $vmid";
1861 $title .= " - ". $conf->{name
} if $conf->{name
};
1863 my $port = PVE
::QemuServer
::spice_port
($vmid);
1865 my ($ticket, undef, $remote_viewer_config) =
1866 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1868 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1869 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1871 return $remote_viewer_config;
1874 __PACKAGE__-
>register_method({
1876 path
=> '{vmid}/status',
1879 description
=> "Directory index",
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid'),
1895 subdir
=> { type
=> 'string' },
1898 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1904 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1907 { subdir
=> 'current' },
1908 { subdir
=> 'start' },
1909 { subdir
=> 'stop' },
1915 __PACKAGE__-
>register_method({
1916 name
=> 'vm_status',
1917 path
=> '{vmid}/status/current',
1920 protected
=> 1, # qemu pid files are only readable by root
1921 description
=> "Get virtual machine status.",
1923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1926 additionalProperties
=> 0,
1928 node
=> get_standard_option
('pve-node'),
1929 vmid
=> get_standard_option
('pve-vmid'),
1935 %$PVE::QemuServer
::vmstatus_return_properties
,
1937 description
=> "HA manager service status.",
1941 description
=> "Qemu VGA configuration supports spice.",
1946 description
=> "Qemu GuestAgent enabled in config.",
1956 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1958 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1959 my $status = $vmstatus->{$param->{vmid
}};
1961 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1963 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1964 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1969 __PACKAGE__-
>register_method({
1971 path
=> '{vmid}/status/start',
1975 description
=> "Start virtual machine.",
1977 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1980 additionalProperties
=> 0,
1982 node
=> get_standard_option
('pve-node'),
1983 vmid
=> get_standard_option
('pve-vmid',
1984 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1985 skiplock
=> get_standard_option
('skiplock'),
1986 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1987 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1990 enum
=> ['secure', 'insecure'],
1991 description
=> "Migration traffic is encrypted using an SSH " .
1992 "tunnel by default. On secure, completely private networks " .
1993 "this can be disabled to increase performance.",
1996 migration_network
=> {
1997 type
=> 'string', format
=> 'CIDR',
1998 description
=> "CIDR of the (sub) network that is used for migration.",
2001 machine
=> get_standard_option
('pve-qm-machine'),
2003 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2015 my $rpcenv = PVE
::RPCEnvironment
::get
();
2017 my $authuser = $rpcenv->get_user();
2019 my $node = extract_param
($param, 'node');
2021 my $vmid = extract_param
($param, 'vmid');
2023 my $machine = extract_param
($param, 'machine');
2025 my $stateuri = extract_param
($param, 'stateuri');
2026 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2027 if $stateuri && $authuser ne 'root@pam';
2029 my $skiplock = extract_param
($param, 'skiplock');
2030 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2031 if $skiplock && $authuser ne 'root@pam';
2033 my $migratedfrom = extract_param
($param, 'migratedfrom');
2034 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2035 if $migratedfrom && $authuser ne 'root@pam';
2037 my $migration_type = extract_param
($param, 'migration_type');
2038 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2039 if $migration_type && $authuser ne 'root@pam';
2041 my $migration_network = extract_param
($param, 'migration_network');
2042 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2043 if $migration_network && $authuser ne 'root@pam';
2045 my $targetstorage = extract_param
($param, 'targetstorage');
2046 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2047 if $targetstorage && $authuser ne 'root@pam';
2049 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2050 if $targetstorage && !$migratedfrom;
2052 # read spice ticket from STDIN
2054 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2055 if (defined(my $line = <STDIN
>)) {
2057 $spice_ticket = $line;
2061 PVE
::Cluster
::check_cfs_quorum
();
2063 my $storecfg = PVE
::Storage
::config
();
2065 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2066 $rpcenv->{type
} ne 'ha') {
2071 my $service = "vm:$vmid";
2073 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2075 print "Requesting HA start for VM $vmid\n";
2077 PVE
::Tools
::run_command
($cmd);
2082 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2089 syslog
('info', "start VM $vmid: $upid\n");
2091 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2092 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2097 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2101 __PACKAGE__-
>register_method({
2103 path
=> '{vmid}/status/stop',
2107 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2108 "is akin to pulling the power plug of a running computer and may damage the VM data",
2110 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2113 additionalProperties
=> 0,
2115 node
=> get_standard_option
('pve-node'),
2116 vmid
=> get_standard_option
('pve-vmid',
2117 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2118 skiplock
=> get_standard_option
('skiplock'),
2119 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2121 description
=> "Wait maximal timeout seconds.",
2127 description
=> "Do not deactivate storage volumes.",
2140 my $rpcenv = PVE
::RPCEnvironment
::get
();
2142 my $authuser = $rpcenv->get_user();
2144 my $node = extract_param
($param, 'node');
2146 my $vmid = extract_param
($param, 'vmid');
2148 my $skiplock = extract_param
($param, 'skiplock');
2149 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2150 if $skiplock && $authuser ne 'root@pam';
2152 my $keepActive = extract_param
($param, 'keepActive');
2153 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2154 if $keepActive && $authuser ne 'root@pam';
2156 my $migratedfrom = extract_param
($param, 'migratedfrom');
2157 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2158 if $migratedfrom && $authuser ne 'root@pam';
2161 my $storecfg = PVE
::Storage
::config
();
2163 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2168 my $service = "vm:$vmid";
2170 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2172 print "Requesting HA stop for VM $vmid\n";
2174 PVE
::Tools
::run_command
($cmd);
2179 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2185 syslog
('info', "stop VM $vmid: $upid\n");
2187 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2188 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2193 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2197 __PACKAGE__-
>register_method({
2199 path
=> '{vmid}/status/reset',
2203 description
=> "Reset virtual machine.",
2205 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2208 additionalProperties
=> 0,
2210 node
=> get_standard_option
('pve-node'),
2211 vmid
=> get_standard_option
('pve-vmid',
2212 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2213 skiplock
=> get_standard_option
('skiplock'),
2222 my $rpcenv = PVE
::RPCEnvironment
::get
();
2224 my $authuser = $rpcenv->get_user();
2226 my $node = extract_param
($param, 'node');
2228 my $vmid = extract_param
($param, 'vmid');
2230 my $skiplock = extract_param
($param, 'skiplock');
2231 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2232 if $skiplock && $authuser ne 'root@pam';
2234 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2239 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2244 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2247 __PACKAGE__-
>register_method({
2248 name
=> 'vm_shutdown',
2249 path
=> '{vmid}/status/shutdown',
2253 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2254 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2256 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2259 additionalProperties
=> 0,
2261 node
=> get_standard_option
('pve-node'),
2262 vmid
=> get_standard_option
('pve-vmid',
2263 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2264 skiplock
=> get_standard_option
('skiplock'),
2266 description
=> "Wait maximal timeout seconds.",
2272 description
=> "Make sure the VM stops.",
2278 description
=> "Do not deactivate storage volumes.",
2291 my $rpcenv = PVE
::RPCEnvironment
::get
();
2293 my $authuser = $rpcenv->get_user();
2295 my $node = extract_param
($param, 'node');
2297 my $vmid = extract_param
($param, 'vmid');
2299 my $skiplock = extract_param
($param, 'skiplock');
2300 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2301 if $skiplock && $authuser ne 'root@pam';
2303 my $keepActive = extract_param
($param, 'keepActive');
2304 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2305 if $keepActive && $authuser ne 'root@pam';
2307 my $storecfg = PVE
::Storage
::config
();
2311 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2312 # otherwise, we will infer a shutdown command, but run into the timeout,
2313 # then when the vm is resumed, it will instantly shutdown
2315 # checking the qmp status here to get feedback to the gui/cli/api
2316 # and the status query should not take too long
2319 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2323 if (!$err && $qmpstatus->{status
} eq "paused") {
2324 if ($param->{forceStop
}) {
2325 warn "VM is paused - stop instead of shutdown\n";
2328 die "VM is paused - cannot shutdown\n";
2332 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2333 ($rpcenv->{type
} ne 'ha')) {
2338 my $service = "vm:$vmid";
2340 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2342 print "Requesting HA stop for VM $vmid\n";
2344 PVE
::Tools
::run_command
($cmd);
2349 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2356 syslog
('info', "shutdown VM $vmid: $upid\n");
2358 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2359 $shutdown, $param->{forceStop
}, $keepActive);
2364 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2368 __PACKAGE__-
>register_method({
2369 name
=> 'vm_suspend',
2370 path
=> '{vmid}/status/suspend',
2374 description
=> "Suspend virtual machine.",
2376 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2379 additionalProperties
=> 0,
2381 node
=> get_standard_option
('pve-node'),
2382 vmid
=> get_standard_option
('pve-vmid',
2383 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2384 skiplock
=> get_standard_option
('skiplock'),
2389 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2391 statestorage
=> get_standard_option
('pve-storage-id', {
2392 description
=> "The storage for the VM state",
2393 requires
=> 'todisk',
2395 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2405 my $rpcenv = PVE
::RPCEnvironment
::get
();
2407 my $authuser = $rpcenv->get_user();
2409 my $node = extract_param
($param, 'node');
2411 my $vmid = extract_param
($param, 'vmid');
2413 my $todisk = extract_param
($param, 'todisk') // 0;
2415 my $statestorage = extract_param
($param, 'statestorage');
2417 my $skiplock = extract_param
($param, 'skiplock');
2418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2419 if $skiplock && $authuser ne 'root@pam';
2421 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2423 die "Cannot suspend HA managed VM to disk\n"
2424 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2426 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2431 syslog
('info', "suspend VM $vmid: $upid\n");
2433 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2438 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2441 __PACKAGE__-
>register_method({
2442 name
=> 'vm_resume',
2443 path
=> '{vmid}/status/resume',
2447 description
=> "Resume virtual machine.",
2449 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2452 additionalProperties
=> 0,
2454 node
=> get_standard_option
('pve-node'),
2455 vmid
=> get_standard_option
('pve-vmid',
2456 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2457 skiplock
=> get_standard_option
('skiplock'),
2458 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2468 my $rpcenv = PVE
::RPCEnvironment
::get
();
2470 my $authuser = $rpcenv->get_user();
2472 my $node = extract_param
($param, 'node');
2474 my $vmid = extract_param
($param, 'vmid');
2476 my $skiplock = extract_param
($param, 'skiplock');
2477 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2478 if $skiplock && $authuser ne 'root@pam';
2480 my $nocheck = extract_param
($param, 'nocheck');
2482 my $to_disk_suspended;
2484 PVE
::QemuConfig-
>lock_config($vmid, sub {
2485 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2486 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2490 die "VM $vmid not running\n"
2491 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2496 syslog
('info', "resume VM $vmid: $upid\n");
2498 if (!$to_disk_suspended) {
2499 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2501 my $storecfg = PVE
::Storage
::config
();
2502 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2508 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2511 __PACKAGE__-
>register_method({
2512 name
=> 'vm_sendkey',
2513 path
=> '{vmid}/sendkey',
2517 description
=> "Send key event to virtual machine.",
2519 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2522 additionalProperties
=> 0,
2524 node
=> get_standard_option
('pve-node'),
2525 vmid
=> get_standard_option
('pve-vmid',
2526 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2527 skiplock
=> get_standard_option
('skiplock'),
2529 description
=> "The key (qemu monitor encoding).",
2534 returns
=> { type
=> 'null'},
2538 my $rpcenv = PVE
::RPCEnvironment
::get
();
2540 my $authuser = $rpcenv->get_user();
2542 my $node = extract_param
($param, 'node');
2544 my $vmid = extract_param
($param, 'vmid');
2546 my $skiplock = extract_param
($param, 'skiplock');
2547 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2548 if $skiplock && $authuser ne 'root@pam';
2550 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2555 __PACKAGE__-
>register_method({
2556 name
=> 'vm_feature',
2557 path
=> '{vmid}/feature',
2561 description
=> "Check if feature for virtual machine is available.",
2563 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2566 additionalProperties
=> 0,
2568 node
=> get_standard_option
('pve-node'),
2569 vmid
=> get_standard_option
('pve-vmid'),
2571 description
=> "Feature to check.",
2573 enum
=> [ 'snapshot', 'clone', 'copy' ],
2575 snapname
=> get_standard_option
('pve-snapshot-name', {
2583 hasFeature
=> { type
=> 'boolean' },
2586 items
=> { type
=> 'string' },
2593 my $node = extract_param
($param, 'node');
2595 my $vmid = extract_param
($param, 'vmid');
2597 my $snapname = extract_param
($param, 'snapname');
2599 my $feature = extract_param
($param, 'feature');
2601 my $running = PVE
::QemuServer
::check_running
($vmid);
2603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2606 my $snap = $conf->{snapshots
}->{$snapname};
2607 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2610 my $storecfg = PVE
::Storage
::config
();
2612 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2613 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2616 hasFeature
=> $hasFeature,
2617 nodes
=> [ keys %$nodelist ],
2621 __PACKAGE__-
>register_method({
2623 path
=> '{vmid}/clone',
2627 description
=> "Create a copy of virtual machine/template.",
2629 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2630 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2631 "'Datastore.AllocateSpace' on any used storage.",
2634 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2636 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2637 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2642 additionalProperties
=> 0,
2644 node
=> get_standard_option
('pve-node'),
2645 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2646 newid
=> get_standard_option
('pve-vmid', {
2647 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2648 description
=> 'VMID for the clone.' }),
2651 type
=> 'string', format
=> 'dns-name',
2652 description
=> "Set a name for the new VM.",
2657 description
=> "Description for the new VM.",
2661 type
=> 'string', format
=> 'pve-poolid',
2662 description
=> "Add the new VM to the specified pool.",
2664 snapname
=> get_standard_option
('pve-snapshot-name', {
2667 storage
=> get_standard_option
('pve-storage-id', {
2668 description
=> "Target storage for full clone.",
2672 description
=> "Target format for file storage. Only valid for full clone.",
2675 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2680 description
=> "Create a full copy of all disks. This is always done when " .
2681 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2683 target
=> get_standard_option
('pve-node', {
2684 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2688 description
=> "Override I/O bandwidth limit (in KiB/s).",
2692 default => 'clone limit from datacenter or storage config',
2702 my $rpcenv = PVE
::RPCEnvironment
::get
();
2704 my $authuser = $rpcenv->get_user();
2706 my $node = extract_param
($param, 'node');
2708 my $vmid = extract_param
($param, 'vmid');
2710 my $newid = extract_param
($param, 'newid');
2712 my $pool = extract_param
($param, 'pool');
2714 if (defined($pool)) {
2715 $rpcenv->check_pool_exist($pool);
2718 my $snapname = extract_param
($param, 'snapname');
2720 my $storage = extract_param
($param, 'storage');
2722 my $format = extract_param
($param, 'format');
2724 my $target = extract_param
($param, 'target');
2726 my $localnode = PVE
::INotify
::nodename
();
2728 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2730 PVE
::Cluster
::check_node_exists
($target) if $target;
2732 my $storecfg = PVE
::Storage
::config
();
2735 # check if storage is enabled on local node
2736 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2738 # check if storage is available on target node
2739 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2740 # clone only works if target storage is shared
2741 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2742 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2746 PVE
::Cluster
::check_cfs_quorum
();
2748 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2750 # exclusive lock if VM is running - else shared lock is enough;
2751 my $shared_lock = $running ?
0 : 1;
2755 # do all tests after lock
2756 # we also try to do all tests before we fork the worker
2758 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2760 PVE
::QemuConfig-
>check_lock($conf);
2762 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2764 die "unexpected state change\n" if $verify_running != $running;
2766 die "snapshot '$snapname' does not exist\n"
2767 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2769 my $full = extract_param
($param, 'full');
2770 if (!defined($full)) {
2771 $full = !PVE
::QemuConfig-
>is_template($conf);
2774 die "parameter 'storage' not allowed for linked clones\n"
2775 if defined($storage) && !$full;
2777 die "parameter 'format' not allowed for linked clones\n"
2778 if defined($format) && !$full;
2780 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2782 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2784 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2786 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2788 die "unable to create VM $newid: config file already exists\n"
2791 my $newconf = { lock => 'clone' };
2796 foreach my $opt (keys %$oldconf) {
2797 my $value = $oldconf->{$opt};
2799 # do not copy snapshot related info
2800 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2801 $opt eq 'vmstate' || $opt eq 'snapstate';
2803 # no need to copy unused images, because VMID(owner) changes anyways
2804 next if $opt =~ m/^unused\d+$/;
2806 # always change MAC! address
2807 if ($opt =~ m/^net(\d+)$/) {
2808 my $net = PVE
::QemuServer
::parse_net
($value);
2809 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2810 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2811 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2812 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2813 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2814 die "unable to parse drive options for '$opt'\n" if !$drive;
2815 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2816 $newconf->{$opt} = $value; # simply copy configuration
2818 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2819 die "Full clone feature is not supported for drive '$opt'\n"
2820 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2821 $fullclone->{$opt} = 1;
2823 # not full means clone instead of copy
2824 die "Linked clone feature is not supported for drive '$opt'\n"
2825 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2827 $drives->{$opt} = $drive;
2828 push @$vollist, $drive->{file
};
2831 # copy everything else
2832 $newconf->{$opt} = $value;
2836 # auto generate a new uuid
2837 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2838 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2839 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2841 # auto generate a new vmgenid if the option was set
2842 if ($newconf->{vmgenid
}) {
2843 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2846 delete $newconf->{template
};
2848 if ($param->{name
}) {
2849 $newconf->{name
} = $param->{name
};
2851 if ($oldconf->{name
}) {
2852 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2854 $newconf->{name
} = "Copy-of-VM-$vmid";
2858 if ($param->{description
}) {
2859 $newconf->{description
} = $param->{description
};
2862 # create empty/temp config - this fails if VM already exists on other node
2863 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2868 my $newvollist = [];
2875 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2877 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2879 my $bwlimit = extract_param
($param, 'bwlimit');
2881 my $total_jobs = scalar(keys %{$drives});
2884 foreach my $opt (keys %$drives) {
2885 my $drive = $drives->{$opt};
2886 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2888 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2889 my $storage_list = [ $src_sid ];
2890 push @$storage_list, $storage if defined($storage);
2891 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2893 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2894 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2895 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2897 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2899 PVE
::QemuConfig-
>write_config($newid, $newconf);
2903 delete $newconf->{lock};
2905 # do not write pending changes
2906 if (my @changes = keys %{$newconf->{pending
}}) {
2907 my $pending = join(',', @changes);
2908 warn "found pending changes for '$pending', discarding for clone\n";
2909 delete $newconf->{pending
};
2912 PVE
::QemuConfig-
>write_config($newid, $newconf);
2915 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2916 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2917 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2919 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2920 die "Failed to move config to node '$target' - rename failed: $!\n"
2921 if !rename($conffile, $newconffile);
2924 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2929 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2931 sleep 1; # some storage like rbd need to wait before release volume - really?
2933 foreach my $volid (@$newvollist) {
2934 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2937 die "clone failed: $err";
2943 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2945 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2948 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2949 # Aquire exclusive lock lock for $newid
2950 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2955 __PACKAGE__-
>register_method({
2956 name
=> 'move_vm_disk',
2957 path
=> '{vmid}/move_disk',
2961 description
=> "Move volume to different storage.",
2963 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2965 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2966 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2970 additionalProperties
=> 0,
2972 node
=> get_standard_option
('pve-node'),
2973 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2976 description
=> "The disk you want to move.",
2977 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2979 storage
=> get_standard_option
('pve-storage-id', {
2980 description
=> "Target storage.",
2981 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2985 description
=> "Target Format.",
2986 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2991 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2997 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3002 description
=> "Override I/O bandwidth limit (in KiB/s).",
3006 default => 'move limit from datacenter or storage config',
3012 description
=> "the task ID.",
3017 my $rpcenv = PVE
::RPCEnvironment
::get
();
3019 my $authuser = $rpcenv->get_user();
3021 my $node = extract_param
($param, 'node');
3023 my $vmid = extract_param
($param, 'vmid');
3025 my $digest = extract_param
($param, 'digest');
3027 my $disk = extract_param
($param, 'disk');
3029 my $storeid = extract_param
($param, 'storage');
3031 my $format = extract_param
($param, 'format');
3033 my $storecfg = PVE
::Storage
::config
();
3035 my $updatefn = sub {
3037 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3039 PVE
::QemuConfig-
>check_lock($conf);
3041 die "checksum missmatch (file change by other user?)\n"
3042 if $digest && $digest ne $conf->{digest
};
3044 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3046 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3048 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3050 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3053 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3054 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3058 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3059 (!$format || !$oldfmt || $oldfmt eq $format);
3061 # this only checks snapshots because $disk is passed!
3062 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3063 die "you can't move a disk with snapshots and delete the source\n"
3064 if $snapshotted && $param->{delete};
3066 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3068 my $running = PVE
::QemuServer
::check_running
($vmid);
3070 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3074 my $newvollist = [];
3080 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3082 warn "moving disk with snapshots, snapshots will not be moved!\n"
3085 my $bwlimit = extract_param
($param, 'bwlimit');
3086 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3088 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3089 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3091 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3093 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3095 # convert moved disk to base if part of template
3096 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3097 if PVE
::QemuConfig-
>is_template($conf);
3099 PVE
::QemuConfig-
>write_config($vmid, $conf);
3101 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3102 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3106 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3107 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3114 foreach my $volid (@$newvollist) {
3115 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3118 die "storage migration failed: $err";
3121 if ($param->{delete}) {
3123 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3124 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3130 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3133 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3136 __PACKAGE__-
>register_method({
3137 name
=> 'migrate_vm',
3138 path
=> '{vmid}/migrate',
3142 description
=> "Migrate virtual machine. Creates a new migration task.",
3144 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3147 additionalProperties
=> 0,
3149 node
=> get_standard_option
('pve-node'),
3150 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3151 target
=> get_standard_option
('pve-node', {
3152 description
=> "Target node.",
3153 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3157 description
=> "Use online/live migration.",
3162 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3167 enum
=> ['secure', 'insecure'],
3168 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3171 migration_network
=> {
3172 type
=> 'string', format
=> 'CIDR',
3173 description
=> "CIDR of the (sub) network that is used for migration.",
3176 "with-local-disks" => {
3178 description
=> "Enable live storage migration for local disk",
3181 targetstorage
=> get_standard_option
('pve-storage-id', {
3182 description
=> "Default target storage.",
3184 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3187 description
=> "Override I/O bandwidth limit (in KiB/s).",
3191 default => 'migrate limit from datacenter or storage config',
3197 description
=> "the task ID.",
3202 my $rpcenv = PVE
::RPCEnvironment
::get
();
3204 my $authuser = $rpcenv->get_user();
3206 my $target = extract_param
($param, 'target');
3208 my $localnode = PVE
::INotify
::nodename
();
3209 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3211 PVE
::Cluster
::check_cfs_quorum
();
3213 PVE
::Cluster
::check_node_exists
($target);
3215 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3217 my $vmid = extract_param
($param, 'vmid');
3219 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3220 if !$param->{online
} && $param->{targetstorage
};
3222 raise_param_exc
({ force
=> "Only root may use this option." })
3223 if $param->{force
} && $authuser ne 'root@pam';
3225 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3226 if $param->{migration_type
} && $authuser ne 'root@pam';
3228 # allow root only until better network permissions are available
3229 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3230 if $param->{migration_network
} && $authuser ne 'root@pam';
3233 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3235 # try to detect errors early
3237 PVE
::QemuConfig-
>check_lock($conf);
3239 if (PVE
::QemuServer
::check_running
($vmid)) {
3240 die "cant migrate running VM without --online\n"
3241 if !$param->{online
};
3244 my $storecfg = PVE
::Storage
::config
();
3246 if( $param->{targetstorage
}) {
3247 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3249 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3252 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3257 my $service = "vm:$vmid";
3259 my $cmd = ['ha-manager', 'migrate', $service, $target];
3261 print "Requesting HA migration for VM $vmid to node $target\n";
3263 PVE
::Tools
::run_command
($cmd);
3268 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3273 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3277 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3280 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3285 __PACKAGE__-
>register_method({
3287 path
=> '{vmid}/monitor',
3291 description
=> "Execute Qemu monitor commands.",
3293 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3294 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3297 additionalProperties
=> 0,
3299 node
=> get_standard_option
('pve-node'),
3300 vmid
=> get_standard_option
('pve-vmid'),
3303 description
=> "The monitor command.",
3307 returns
=> { type
=> 'string'},
3311 my $rpcenv = PVE
::RPCEnvironment
::get
();
3312 my $authuser = $rpcenv->get_user();
3315 my $command = shift;
3316 return $command =~ m/^\s*info(\s+|$)/
3317 || $command =~ m/^\s*help\s*$/;
3320 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3321 if !&$is_ro($param->{command
});
3323 my $vmid = $param->{vmid
};
3325 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3329 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3331 $res = "ERROR: $@" if $@;
3336 __PACKAGE__-
>register_method({
3337 name
=> 'resize_vm',
3338 path
=> '{vmid}/resize',
3342 description
=> "Extend volume size.",
3344 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3347 additionalProperties
=> 0,
3349 node
=> get_standard_option
('pve-node'),
3350 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3351 skiplock
=> get_standard_option
('skiplock'),
3354 description
=> "The disk you want to resize.",
3355 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3359 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3360 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.",
3364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3370 returns
=> { type
=> 'null'},
3374 my $rpcenv = PVE
::RPCEnvironment
::get
();
3376 my $authuser = $rpcenv->get_user();
3378 my $node = extract_param
($param, 'node');
3380 my $vmid = extract_param
($param, 'vmid');
3382 my $digest = extract_param
($param, 'digest');
3384 my $disk = extract_param
($param, 'disk');
3386 my $sizestr = extract_param
($param, 'size');
3388 my $skiplock = extract_param
($param, 'skiplock');
3389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3390 if $skiplock && $authuser ne 'root@pam';
3392 my $storecfg = PVE
::Storage
::config
();
3394 my $updatefn = sub {
3396 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3398 die "checksum missmatch (file change by other user?)\n"
3399 if $digest && $digest ne $conf->{digest
};
3400 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3402 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3404 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3406 my (undef, undef, undef, undef, undef, undef, $format) =
3407 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3409 die "can't resize volume: $disk if snapshot exists\n"
3410 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3412 my $volid = $drive->{file
};
3414 die "disk '$disk' has no associated volume\n" if !$volid;
3416 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3418 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3420 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3422 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3423 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3425 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3426 my ($ext, $newsize, $unit) = ($1, $2, $4);
3429 $newsize = $newsize * 1024;
3430 } elsif ($unit eq 'M') {
3431 $newsize = $newsize * 1024 * 1024;
3432 } elsif ($unit eq 'G') {
3433 $newsize = $newsize * 1024 * 1024 * 1024;
3434 } elsif ($unit eq 'T') {
3435 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3438 $newsize += $size if $ext;
3439 $newsize = int($newsize);
3441 die "shrinking disks is not supported\n" if $newsize < $size;
3443 return if $size == $newsize;
3445 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3447 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3449 $drive->{size
} = $newsize;
3450 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3452 PVE
::QemuConfig-
>write_config($vmid, $conf);
3455 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3459 __PACKAGE__-
>register_method({
3460 name
=> 'snapshot_list',
3461 path
=> '{vmid}/snapshot',
3463 description
=> "List all snapshots.",
3465 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3468 protected
=> 1, # qemu pid files are only readable by root
3470 additionalProperties
=> 0,
3472 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3473 node
=> get_standard_option
('pve-node'),
3482 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3486 description
=> "Snapshot includes RAM.",
3491 description
=> "Snapshot description.",
3495 description
=> "Snapshot creation time",
3497 renderer
=> 'timestamp',
3501 description
=> "Parent snapshot identifier.",
3507 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3512 my $vmid = $param->{vmid
};
3514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3515 my $snaphash = $conf->{snapshots
} || {};
3519 foreach my $name (keys %$snaphash) {
3520 my $d = $snaphash->{$name};
3523 snaptime
=> $d->{snaptime
} || 0,
3524 vmstate
=> $d->{vmstate
} ?
1 : 0,
3525 description
=> $d->{description
} || '',
3527 $item->{parent
} = $d->{parent
} if $d->{parent
};
3528 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3532 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3535 digest
=> $conf->{digest
},
3536 running
=> $running,
3537 description
=> "You are here!",
3539 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3541 push @$res, $current;
3546 __PACKAGE__-
>register_method({
3548 path
=> '{vmid}/snapshot',
3552 description
=> "Snapshot a VM.",
3554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3557 additionalProperties
=> 0,
3559 node
=> get_standard_option
('pve-node'),
3560 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3561 snapname
=> get_standard_option
('pve-snapshot-name'),
3565 description
=> "Save the vmstate",
3570 description
=> "A textual description or comment.",
3576 description
=> "the task ID.",
3581 my $rpcenv = PVE
::RPCEnvironment
::get
();
3583 my $authuser = $rpcenv->get_user();
3585 my $node = extract_param
($param, 'node');
3587 my $vmid = extract_param
($param, 'vmid');
3589 my $snapname = extract_param
($param, 'snapname');
3591 die "unable to use snapshot name 'current' (reserved name)\n"
3592 if $snapname eq 'current';
3595 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3596 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3597 $param->{description
});
3600 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3603 __PACKAGE__-
>register_method({
3604 name
=> 'snapshot_cmd_idx',
3605 path
=> '{vmid}/snapshot/{snapname}',
3612 additionalProperties
=> 0,
3614 vmid
=> get_standard_option
('pve-vmid'),
3615 node
=> get_standard_option
('pve-node'),
3616 snapname
=> get_standard_option
('pve-snapshot-name'),
3625 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3632 push @$res, { cmd
=> 'rollback' };
3633 push @$res, { cmd
=> 'config' };
3638 __PACKAGE__-
>register_method({
3639 name
=> 'update_snapshot_config',
3640 path
=> '{vmid}/snapshot/{snapname}/config',
3644 description
=> "Update snapshot metadata.",
3646 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3649 additionalProperties
=> 0,
3651 node
=> get_standard_option
('pve-node'),
3652 vmid
=> get_standard_option
('pve-vmid'),
3653 snapname
=> get_standard_option
('pve-snapshot-name'),
3657 description
=> "A textual description or comment.",
3661 returns
=> { type
=> 'null' },
3665 my $rpcenv = PVE
::RPCEnvironment
::get
();
3667 my $authuser = $rpcenv->get_user();
3669 my $vmid = extract_param
($param, 'vmid');
3671 my $snapname = extract_param
($param, 'snapname');
3673 return undef if !defined($param->{description
});
3675 my $updatefn = sub {
3677 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3679 PVE
::QemuConfig-
>check_lock($conf);
3681 my $snap = $conf->{snapshots
}->{$snapname};
3683 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3685 $snap->{description
} = $param->{description
} if defined($param->{description
});
3687 PVE
::QemuConfig-
>write_config($vmid, $conf);
3690 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3695 __PACKAGE__-
>register_method({
3696 name
=> 'get_snapshot_config',
3697 path
=> '{vmid}/snapshot/{snapname}/config',
3700 description
=> "Get snapshot configuration",
3702 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3705 additionalProperties
=> 0,
3707 node
=> get_standard_option
('pve-node'),
3708 vmid
=> get_standard_option
('pve-vmid'),
3709 snapname
=> get_standard_option
('pve-snapshot-name'),
3712 returns
=> { type
=> "object" },
3716 my $rpcenv = PVE
::RPCEnvironment
::get
();
3718 my $authuser = $rpcenv->get_user();
3720 my $vmid = extract_param
($param, 'vmid');
3722 my $snapname = extract_param
($param, 'snapname');
3724 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3726 my $snap = $conf->{snapshots
}->{$snapname};
3728 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3733 __PACKAGE__-
>register_method({
3735 path
=> '{vmid}/snapshot/{snapname}/rollback',
3739 description
=> "Rollback VM state to specified snapshot.",
3741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3744 additionalProperties
=> 0,
3746 node
=> get_standard_option
('pve-node'),
3747 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3748 snapname
=> get_standard_option
('pve-snapshot-name'),
3753 description
=> "the task ID.",
3758 my $rpcenv = PVE
::RPCEnvironment
::get
();
3760 my $authuser = $rpcenv->get_user();
3762 my $node = extract_param
($param, 'node');
3764 my $vmid = extract_param
($param, 'vmid');
3766 my $snapname = extract_param
($param, 'snapname');
3769 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3770 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3774 # hold migration lock, this makes sure that nobody create replication snapshots
3775 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3778 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3781 __PACKAGE__-
>register_method({
3782 name
=> 'delsnapshot',
3783 path
=> '{vmid}/snapshot/{snapname}',
3787 description
=> "Delete a VM snapshot.",
3789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3792 additionalProperties
=> 0,
3794 node
=> get_standard_option
('pve-node'),
3795 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3796 snapname
=> get_standard_option
('pve-snapshot-name'),
3800 description
=> "For removal from config file, even if removing disk snapshots fails.",
3806 description
=> "the task ID.",
3811 my $rpcenv = PVE
::RPCEnvironment
::get
();
3813 my $authuser = $rpcenv->get_user();
3815 my $node = extract_param
($param, 'node');
3817 my $vmid = extract_param
($param, 'vmid');
3819 my $snapname = extract_param
($param, 'snapname');
3822 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3823 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3826 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3829 __PACKAGE__-
>register_method({
3831 path
=> '{vmid}/template',
3835 description
=> "Create a Template.",
3837 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3838 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3841 additionalProperties
=> 0,
3843 node
=> get_standard_option
('pve-node'),
3844 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3848 description
=> "If you want to convert only 1 disk to base image.",
3849 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3854 returns
=> { type
=> 'null'},
3858 my $rpcenv = PVE
::RPCEnvironment
::get
();
3860 my $authuser = $rpcenv->get_user();
3862 my $node = extract_param
($param, 'node');
3864 my $vmid = extract_param
($param, 'vmid');
3866 my $disk = extract_param
($param, 'disk');
3868 my $updatefn = sub {
3870 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3872 PVE
::QemuConfig-
>check_lock($conf);
3874 die "unable to create template, because VM contains snapshots\n"
3875 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3877 die "you can't convert a template to a template\n"
3878 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3880 die "you can't convert a VM to template if VM is running\n"
3881 if PVE
::QemuServer
::check_running
($vmid);
3884 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3887 $conf->{template
} = 1;
3888 PVE
::QemuConfig-
>write_config($vmid, $conf);
3890 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3893 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);