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";
155 $fmt = $disk->{format
} ?
$disk->{format
} : "qcow2";
158 $fmt = $disk->{format
};
161 # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
162 my $cloudinit_iso_size = 4; # in MB
163 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
164 $fmt, $name, $cloudinit_iso_size*1024);
165 $disk->{file
} = $volid;
166 $disk->{media
} = 'cdrom';
167 push @$vollist, $volid;
168 delete $disk->{format
}; # no longer needed
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
170 } elsif ($volid =~ $NEW_DISK_RE) {
171 my ($storeid, $size) = ($2 || $default_storage, $3);
172 die "no storage ID specified (and no default storage)\n" if !$storeid;
173 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
174 my $fmt = $disk->{format
} || $defformat;
176 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
179 if ($ds eq 'efidisk0') {
180 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
182 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
184 push @$vollist, $volid;
185 $disk->{file
} = $volid;
186 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
187 delete $disk->{format
}; # no longer needed
188 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
191 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
193 my $volid_is_new = 1;
196 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
197 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
202 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
204 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
206 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
208 die "volume $volid does not exists\n" if !$size;
210 $disk->{size
} = $size;
213 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
217 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
219 # free allocated images on error
221 syslog
('err', "VM $vmid creating disks failed");
222 foreach my $volid (@$vollist) {
223 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
229 # modify vm config if everything went well
230 foreach my $ds (keys %$res) {
231 $conf->{$ds} = $res->{$ds};
248 my $memoryoptions = {
254 my $hwtypeoptions = {
266 my $generaloptions = {
273 'migrate_downtime' => 1,
274 'migrate_speed' => 1,
286 my $vmpoweroptions = {
293 'vmstatestorage' => 1,
296 my $cloudinitoptions = {
306 my $check_vm_modify_config_perm = sub {
307 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
309 return 1 if $authuser eq 'root@pam';
311 foreach my $opt (@$key_list) {
312 # some checks (e.g., disk, serial port, usb) need to be done somewhere
313 # else, as there the permission can be value dependend
314 next if PVE
::QemuServer
::is_valid_drivename
($opt);
315 next if $opt eq 'cdrom';
316 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
319 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
321 } elsif ($memoryoptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
323 } elsif ($hwtypeoptions->{$opt}) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
325 } elsif ($generaloptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
327 # special case for startup since it changes host behaviour
328 if ($opt eq 'startup') {
329 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
331 } elsif ($vmpoweroptions->{$opt}) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
333 } elsif ($diskoptions->{$opt}) {
334 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
335 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
338 # catches hostpci\d+, args, lock, etc.
339 # new options will be checked here
340 die "only root can set '$opt' config\n";
347 __PACKAGE__-
>register_method({
351 description
=> "Virtual machine index (per node).",
353 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
357 protected
=> 1, # qemu pid files are only readable by root
359 additionalProperties
=> 0,
361 node
=> get_standard_option
('pve-node'),
365 description
=> "Determine the full status of active VMs.",
373 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
375 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
380 my $rpcenv = PVE
::RPCEnvironment
::get
();
381 my $authuser = $rpcenv->get_user();
383 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
386 foreach my $vmid (keys %$vmstatus) {
387 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
389 my $data = $vmstatus->{$vmid};
398 __PACKAGE__-
>register_method({
402 description
=> "Create or restore a virtual machine.",
404 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
405 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
406 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
407 user
=> 'all', # check inside
412 additionalProperties
=> 0,
413 properties
=> PVE
::QemuServer
::json_config_properties
(
415 node
=> get_standard_option
('pve-node'),
416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
418 description
=> "The backup file.",
422 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
424 storage
=> get_standard_option
('pve-storage-id', {
425 description
=> "Default storage.",
427 completion
=> \
&PVE
::QemuServer
::complete_storage
,
432 description
=> "Allow to overwrite existing VM.",
433 requires
=> 'archive',
438 description
=> "Assign a unique random ethernet address.",
439 requires
=> 'archive',
443 type
=> 'string', format
=> 'pve-poolid',
444 description
=> "Add the VM to the specified pool.",
447 description
=> "Override I/O bandwidth limit (in KiB/s).",
451 default => 'restore limit from datacenter or storage config',
457 description
=> "Start VM after it was created successfully.",
467 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $node = extract_param
($param, 'node');
473 my $vmid = extract_param
($param, 'vmid');
475 my $archive = extract_param
($param, 'archive');
476 my $is_restore = !!$archive;
478 my $storage = extract_param
($param, 'storage');
480 my $force = extract_param
($param, 'force');
482 my $unique = extract_param
($param, 'unique');
484 my $pool = extract_param
($param, 'pool');
486 my $bwlimit = extract_param
($param, 'bwlimit');
488 my $start_after_create = extract_param
($param, 'start');
490 my $filename = PVE
::QemuConfig-
>config_file($vmid);
492 my $storecfg = PVE
::Storage
::config
();
494 if (defined(my $ssh_keys = $param->{sshkeys
})) {
495 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
496 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
499 PVE
::Cluster
::check_cfs_quorum
();
501 if (defined($pool)) {
502 $rpcenv->check_pool_exist($pool);
505 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
506 if defined($storage);
508 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
510 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
512 } elsif ($archive && $force && (-f
$filename) &&
513 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
514 # OK: user has VM.Backup permissions, and want to restore an existing VM
520 &$resolve_cdrom_alias($param);
522 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
524 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
526 foreach my $opt (keys %$param) {
527 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
528 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
529 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
531 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
532 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
536 PVE
::QemuServer
::add_random_macs
($param);
538 my $keystr = join(' ', keys %$param);
539 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
541 if ($archive eq '-') {
542 die "pipe requires cli environment\n"
543 if $rpcenv->{type
} ne 'cli';
545 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
546 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
550 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
552 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
553 die "$emsg $@" if $@;
555 my $restorefn = sub {
556 my $conf = PVE
::QemuConfig-
>load_config($vmid);
558 PVE
::QemuConfig-
>check_protection($conf, $emsg);
560 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
561 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
564 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 bwlimit
=> $bwlimit, });
570 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
572 if ($start_after_create) {
573 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
578 # ensure no old replication state are exists
579 PVE
::ReplicationState
::delete_guest_states
($vmid);
581 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
585 # ensure no old replication state are exists
586 PVE
::ReplicationState
::delete_guest_states
($vmid);
594 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
598 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
600 if (!$conf->{bootdisk
}) {
601 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
602 $conf->{bootdisk
} = $firstdisk if $firstdisk;
605 # auto generate uuid if user did not specify smbios1 option
606 if (!$conf->{smbios1
}) {
607 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
610 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
611 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
614 PVE
::QemuConfig-
>write_config($vmid, $conf);
620 foreach my $volid (@$vollist) {
621 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
627 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
630 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
632 if ($start_after_create) {
633 print "Execute autostart\n";
634 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
639 my ($code, $worker_name);
641 $worker_name = 'qmrestore';
643 eval { $restorefn->() };
645 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
651 $worker_name = 'qmcreate';
653 eval { $createfn->() };
656 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
657 unlink($conffile) or die "failed to remove config file: $!\n";
665 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
668 __PACKAGE__-
>register_method({
673 description
=> "Directory index",
678 additionalProperties
=> 0,
680 node
=> get_standard_option
('pve-node'),
681 vmid
=> get_standard_option
('pve-vmid'),
689 subdir
=> { type
=> 'string' },
692 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
698 { subdir
=> 'config' },
699 { subdir
=> 'pending' },
700 { subdir
=> 'status' },
701 { subdir
=> 'unlink' },
702 { subdir
=> 'vncproxy' },
703 { subdir
=> 'termproxy' },
704 { subdir
=> 'migrate' },
705 { subdir
=> 'resize' },
706 { subdir
=> 'move' },
708 { subdir
=> 'rrddata' },
709 { subdir
=> 'monitor' },
710 { subdir
=> 'agent' },
711 { subdir
=> 'snapshot' },
712 { subdir
=> 'spiceproxy' },
713 { subdir
=> 'sendkey' },
714 { subdir
=> 'firewall' },
720 __PACKAGE__-
>register_method ({
721 subclass
=> "PVE::API2::Firewall::VM",
722 path
=> '{vmid}/firewall',
725 __PACKAGE__-
>register_method ({
726 subclass
=> "PVE::API2::Qemu::Agent",
727 path
=> '{vmid}/agent',
730 __PACKAGE__-
>register_method({
732 path
=> '{vmid}/rrd',
734 protected
=> 1, # fixme: can we avoid that?
736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
738 description
=> "Read VM RRD statistics (returns PNG)",
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid'),
745 description
=> "Specify the time frame you are interested in.",
747 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
750 description
=> "The list of datasources you want to display.",
751 type
=> 'string', format
=> 'pve-configid-list',
754 description
=> "The RRD consolidation function",
756 enum
=> [ 'AVERAGE', 'MAX' ],
764 filename
=> { type
=> 'string' },
770 return PVE
::Cluster
::create_rrd_graph
(
771 "pve2-vm/$param->{vmid}", $param->{timeframe
},
772 $param->{ds
}, $param->{cf
});
776 __PACKAGE__-
>register_method({
778 path
=> '{vmid}/rrddata',
780 protected
=> 1, # fixme: can we avoid that?
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
784 description
=> "Read VM RRD statistics",
786 additionalProperties
=> 0,
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid'),
791 description
=> "Specify the time frame you are interested in.",
793 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
796 description
=> "The RRD consolidation function",
798 enum
=> [ 'AVERAGE', 'MAX' ],
813 return PVE
::Cluster
::create_rrd_data
(
814 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
818 __PACKAGE__-
>register_method({
820 path
=> '{vmid}/config',
823 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
828 additionalProperties
=> 0,
830 node
=> get_standard_option
('pve-node'),
831 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
833 description
=> "Get current values (instead of pending values).",
838 snapshot
=> get_standard_option
('pve-snapshot-name', {
839 description
=> "Fetch config values from given snapshot.",
842 my ($cmd, $pname, $cur, $args) = @_;
843 PVE
::QemuConfig-
>snapshot_list($args->[0]);
849 description
=> "The current VM configuration.",
851 properties
=> PVE
::QemuServer
::json_config_properties
({
854 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
861 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
863 if (my $snapname = $param->{snapshot
}) {
864 my $snapshot = $conf->{snapshots
}->{$snapname};
865 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
867 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
872 delete $conf->{snapshots
};
874 if (!$param->{current
}) {
875 foreach my $opt (keys %{$conf->{pending
}}) {
876 next if $opt eq 'delete';
877 my $value = $conf->{pending
}->{$opt};
878 next if ref($value); # just to be sure
879 $conf->{$opt} = $value;
881 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
882 foreach my $opt (keys %$pending_delete_hash) {
883 delete $conf->{$opt} if $conf->{$opt};
887 delete $conf->{pending
};
889 # hide cloudinit password
890 if ($conf->{cipassword
}) {
891 $conf->{cipassword
} = '**********';
897 __PACKAGE__-
>register_method({
898 name
=> 'vm_pending',
899 path
=> '{vmid}/pending',
902 description
=> "Get virtual machine configuration, including pending changes.",
904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
907 additionalProperties
=> 0,
909 node
=> get_standard_option
('pve-node'),
910 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
919 description
=> "Configuration option name.",
923 description
=> "Current value.",
928 description
=> "Pending value.",
933 description
=> "Indicates a pending delete request if present and not 0. " .
934 "The value 2 indicates a force-delete request.",
946 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
948 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
952 foreach my $opt (keys %$conf) {
953 next if ref($conf->{$opt});
954 my $item = { key
=> $opt };
955 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
956 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
957 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
959 # hide cloudinit password
960 if ($opt eq 'cipassword') {
961 $item->{value
} = '**********' if defined($item->{value
});
962 # the trailing space so that the pending string is different
963 $item->{pending
} = '********** ' if defined($item->{pending
});
968 foreach my $opt (keys %{$conf->{pending
}}) {
969 next if $opt eq 'delete';
970 next if ref($conf->{pending
}->{$opt}); # just to be sure
971 next if defined($conf->{$opt});
972 my $item = { key
=> $opt };
973 $item->{pending
} = $conf->{pending
}->{$opt};
975 # hide cloudinit password
976 if ($opt eq 'cipassword') {
977 $item->{pending
} = '**********' if defined($item->{pending
});
982 while (my ($opt, $force) = each %$pending_delete_hash) {
983 next if $conf->{pending
}->{$opt}; # just to be sure
984 next if $conf->{$opt};
985 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
992 # POST/PUT {vmid}/config implementation
994 # The original API used PUT (idempotent) an we assumed that all operations
995 # are fast. But it turned out that almost any configuration change can
996 # involve hot-plug actions, or disk alloc/free. Such actions can take long
997 # time to complete and have side effects (not idempotent).
999 # The new implementation uses POST and forks a worker process. We added
1000 # a new option 'background_delay'. If specified we wait up to
1001 # 'background_delay' second for the worker task to complete. It returns null
1002 # if the task is finished within that time, else we return the UPID.
1004 my $update_vm_api = sub {
1005 my ($param, $sync) = @_;
1007 my $rpcenv = PVE
::RPCEnvironment
::get
();
1009 my $authuser = $rpcenv->get_user();
1011 my $node = extract_param
($param, 'node');
1013 my $vmid = extract_param
($param, 'vmid');
1015 my $digest = extract_param
($param, 'digest');
1017 my $background_delay = extract_param
($param, 'background_delay');
1019 if (defined(my $cipassword = $param->{cipassword
})) {
1020 # Same logic as in cloud-init (but with the regex fixed...)
1021 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1022 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1025 my @paramarr = (); # used for log message
1026 foreach my $key (sort keys %$param) {
1027 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1028 push @paramarr, "-$key", $value;
1031 my $skiplock = extract_param
($param, 'skiplock');
1032 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1033 if $skiplock && $authuser ne 'root@pam';
1035 my $delete_str = extract_param
($param, 'delete');
1037 my $revert_str = extract_param
($param, 'revert');
1039 my $force = extract_param
($param, 'force');
1041 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1042 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1043 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1046 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1048 my $storecfg = PVE
::Storage
::config
();
1050 my $defaults = PVE
::QemuServer
::load_defaults
();
1052 &$resolve_cdrom_alias($param);
1054 # now try to verify all parameters
1057 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1062 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1063 "-revert $opt' at the same time" })
1064 if defined($param->{$opt});
1066 $revert->{$opt} = 1;
1070 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1071 $opt = 'ide2' if $opt eq 'cdrom';
1073 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1074 "-delete $opt' at the same time" })
1075 if defined($param->{$opt});
1077 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1078 "-revert $opt' at the same time" })
1081 if (!PVE
::QemuServer
::option_exists
($opt)) {
1082 raise_param_exc
({ delete => "unknown option '$opt'" });
1088 my $repl_conf = PVE
::ReplicationConfig-
>new();
1089 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1090 my $check_replication = sub {
1092 return if !$is_replicated;
1093 my $volid = $drive->{file
};
1094 return if !$volid || !($drive->{replicate
}//1);
1095 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1096 my ($storeid, $format);
1097 if ($volid =~ $NEW_DISK_RE) {
1099 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1101 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1102 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1104 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1105 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1106 return if $scfg->{shared
};
1107 die "cannot add non-replicatable volume to a replicated VM\n";
1110 foreach my $opt (keys %$param) {
1111 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1112 # cleanup drive path
1113 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1114 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1115 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1116 $check_replication->($drive);
1117 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1118 } elsif ($opt =~ m/^net(\d+)$/) {
1120 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1121 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1122 } elsif ($opt eq 'vmgenid') {
1123 if ($param->{$opt} eq '1') {
1124 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1126 } elsif ($opt eq 'hookscript') {
1127 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1128 raise_param_exc
({ $opt => $@ }) if $@;
1132 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1134 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1136 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1138 my $updatefn = sub {
1140 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1142 die "checksum missmatch (file change by other user?)\n"
1143 if $digest && $digest ne $conf->{digest
};
1145 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1147 foreach my $opt (keys %$revert) {
1148 if (defined($conf->{$opt})) {
1149 $param->{$opt} = $conf->{$opt};
1150 } elsif (defined($conf->{pending
}->{$opt})) {
1155 if ($param->{memory
} || defined($param->{balloon
})) {
1156 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1157 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1159 die "balloon value too large (must be smaller than assigned memory)\n"
1160 if $balloon && $balloon > $maxmem;
1163 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1167 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1169 # write updates to pending section
1171 my $modified = {}; # record what $option we modify
1173 foreach my $opt (@delete) {
1174 $modified->{$opt} = 1;
1175 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1176 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1177 warn "cannot delete '$opt' - not set in current configuration!\n";
1178 $modified->{$opt} = 0;
1182 if ($opt =~ m/^unused/) {
1183 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1184 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1185 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1194 if defined($conf->{pending
}->{$opt});
1195 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($conf->{$opt} eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($conf->{$opt} =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1275 raise_param_exc
($errors) if scalar(keys %$errors);
1277 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'destroy_vm',
1440 description
=> "Destroy the vm (also delete all used/owned volumes).",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1449 skiplock
=> get_standard_option
('skiplock'),
1458 my $rpcenv = PVE
::RPCEnvironment
::get
();
1460 my $authuser = $rpcenv->get_user();
1462 my $vmid = $param->{vmid
};
1464 my $skiplock = $param->{skiplock
};
1465 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1466 if $skiplock && $authuser ne 'root@pam';
1469 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1471 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1475 die "unable to remove VM $vmid - used in HA resources\n"
1476 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1478 # do not allow destroy if there are replication jobs
1479 my $repl_conf = PVE
::ReplicationConfig-
>new();
1480 $repl_conf->check_for_existing_jobs($vmid);
1482 # early tests (repeat after locking)
1483 die "VM $vmid is running - destroy failed\n"
1484 if PVE
::QemuServer
::check_running
($vmid);
1489 syslog
('info', "destroy VM $vmid: $upid\n");
1491 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1493 PVE
::AccessControl
::remove_vm_access
($vmid);
1495 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1498 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1501 __PACKAGE__-
>register_method({
1503 path
=> '{vmid}/unlink',
1507 description
=> "Unlink/delete disk images.",
1509 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1512 additionalProperties
=> 0,
1514 node
=> get_standard_option
('pve-node'),
1515 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1517 type
=> 'string', format
=> 'pve-configid-list',
1518 description
=> "A list of disk IDs you want to delete.",
1522 description
=> $opt_force_description,
1527 returns
=> { type
=> 'null'},
1531 $param->{delete} = extract_param
($param, 'idlist');
1533 __PACKAGE__-
>update_vm($param);
1540 __PACKAGE__-
>register_method({
1542 path
=> '{vmid}/vncproxy',
1546 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1548 description
=> "Creates a TCP VNC proxy connections.",
1550 additionalProperties
=> 0,
1552 node
=> get_standard_option
('pve-node'),
1553 vmid
=> get_standard_option
('pve-vmid'),
1557 description
=> "starts websockify instead of vncproxy",
1562 additionalProperties
=> 0,
1564 user
=> { type
=> 'string' },
1565 ticket
=> { type
=> 'string' },
1566 cert
=> { type
=> 'string' },
1567 port
=> { type
=> 'integer' },
1568 upid
=> { type
=> 'string' },
1574 my $rpcenv = PVE
::RPCEnvironment
::get
();
1576 my $authuser = $rpcenv->get_user();
1578 my $vmid = $param->{vmid
};
1579 my $node = $param->{node
};
1580 my $websocket = $param->{websocket
};
1582 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1583 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1585 my $authpath = "/vms/$vmid";
1587 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1589 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1595 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1596 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1597 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1598 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1599 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1601 $family = PVE
::Tools
::get_host_address_family
($node);
1604 my $port = PVE
::Tools
::next_vnc_port
($family);
1611 syslog
('info', "starting vnc proxy $upid\n");
1617 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1619 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1620 '-timeout', $timeout, '-authpath', $authpath,
1621 '-perm', 'Sys.Console'];
1623 if ($param->{websocket
}) {
1624 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1625 push @$cmd, '-notls', '-listen', 'localhost';
1628 push @$cmd, '-c', @$remcmd, @$termcmd;
1630 PVE
::Tools
::run_command
($cmd);
1634 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1636 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1638 my $sock = IO
::Socket
::IP-
>new(
1643 GetAddrInfoFlags
=> 0,
1644 ) or die "failed to create socket: $!\n";
1645 # Inside the worker we shouldn't have any previous alarms
1646 # running anyway...:
1648 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1650 accept(my $cli, $sock) or die "connection failed: $!\n";
1653 if (PVE
::Tools
::run_command
($cmd,
1654 output
=> '>&'.fileno($cli),
1655 input
=> '<&'.fileno($cli),
1658 die "Failed to run vncproxy.\n";
1665 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1667 PVE
::Tools
::wait_for_vnc_port
($port);
1678 __PACKAGE__-
>register_method({
1679 name
=> 'termproxy',
1680 path
=> '{vmid}/termproxy',
1684 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1686 description
=> "Creates a TCP proxy connections.",
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid'),
1695 enum
=> [qw(serial0 serial1 serial2 serial3)],
1696 description
=> "opens a serial terminal (defaults to display)",
1701 additionalProperties
=> 0,
1703 user
=> { type
=> 'string' },
1704 ticket
=> { type
=> 'string' },
1705 port
=> { type
=> 'integer' },
1706 upid
=> { type
=> 'string' },
1712 my $rpcenv = PVE
::RPCEnvironment
::get
();
1714 my $authuser = $rpcenv->get_user();
1716 my $vmid = $param->{vmid
};
1717 my $node = $param->{node
};
1718 my $serial = $param->{serial
};
1720 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1722 if (!defined($serial)) {
1723 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1724 $serial = $conf->{vga
};
1728 my $authpath = "/vms/$vmid";
1730 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1735 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1736 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1737 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1738 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1739 push @$remcmd, '--';
1741 $family = PVE
::Tools
::get_host_address_family
($node);
1744 my $port = PVE
::Tools
::next_vnc_port
($family);
1746 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1747 push @$termcmd, '-iface', $serial if $serial;
1752 syslog
('info', "starting qemu termproxy $upid\n");
1754 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1755 '--perm', 'VM.Console', '--'];
1756 push @$cmd, @$remcmd, @$termcmd;
1758 PVE
::Tools
::run_command
($cmd);
1761 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1763 PVE
::Tools
::wait_for_vnc_port
($port);
1773 __PACKAGE__-
>register_method({
1774 name
=> 'vncwebsocket',
1775 path
=> '{vmid}/vncwebsocket',
1778 description
=> "You also need to pass a valid ticket (vncticket).",
1779 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1781 description
=> "Opens a weksocket for VNC traffic.",
1783 additionalProperties
=> 0,
1785 node
=> get_standard_option
('pve-node'),
1786 vmid
=> get_standard_option
('pve-vmid'),
1788 description
=> "Ticket from previous call to vncproxy.",
1793 description
=> "Port number returned by previous vncproxy call.",
1803 port
=> { type
=> 'string' },
1809 my $rpcenv = PVE
::RPCEnvironment
::get
();
1811 my $authuser = $rpcenv->get_user();
1813 my $vmid = $param->{vmid
};
1814 my $node = $param->{node
};
1816 my $authpath = "/vms/$vmid";
1818 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1820 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1822 # Note: VNC ports are acessible from outside, so we do not gain any
1823 # security if we verify that $param->{port} belongs to VM $vmid. This
1824 # check is done by verifying the VNC ticket (inside VNC protocol).
1826 my $port = $param->{port
};
1828 return { port
=> $port };
1831 __PACKAGE__-
>register_method({
1832 name
=> 'spiceproxy',
1833 path
=> '{vmid}/spiceproxy',
1838 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1840 description
=> "Returns a SPICE configuration to connect to the VM.",
1842 additionalProperties
=> 0,
1844 node
=> get_standard_option
('pve-node'),
1845 vmid
=> get_standard_option
('pve-vmid'),
1846 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1849 returns
=> get_standard_option
('remote-viewer-config'),
1853 my $rpcenv = PVE
::RPCEnvironment
::get
();
1855 my $authuser = $rpcenv->get_user();
1857 my $vmid = $param->{vmid
};
1858 my $node = $param->{node
};
1859 my $proxy = $param->{proxy
};
1861 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1862 my $title = "VM $vmid";
1863 $title .= " - ". $conf->{name
} if $conf->{name
};
1865 my $port = PVE
::QemuServer
::spice_port
($vmid);
1867 my ($ticket, undef, $remote_viewer_config) =
1868 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1870 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1871 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1873 return $remote_viewer_config;
1876 __PACKAGE__-
>register_method({
1878 path
=> '{vmid}/status',
1881 description
=> "Directory index",
1886 additionalProperties
=> 0,
1888 node
=> get_standard_option
('pve-node'),
1889 vmid
=> get_standard_option
('pve-vmid'),
1897 subdir
=> { type
=> 'string' },
1900 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1906 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1909 { subdir
=> 'current' },
1910 { subdir
=> 'start' },
1911 { subdir
=> 'stop' },
1917 __PACKAGE__-
>register_method({
1918 name
=> 'vm_status',
1919 path
=> '{vmid}/status/current',
1922 protected
=> 1, # qemu pid files are only readable by root
1923 description
=> "Get virtual machine status.",
1925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1928 additionalProperties
=> 0,
1930 node
=> get_standard_option
('pve-node'),
1931 vmid
=> get_standard_option
('pve-vmid'),
1937 %$PVE::QemuServer
::vmstatus_return_properties
,
1939 description
=> "HA manager service status.",
1943 description
=> "Qemu VGA configuration supports spice.",
1948 description
=> "Qemu GuestAgent enabled in config.",
1958 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1960 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1961 my $status = $vmstatus->{$param->{vmid
}};
1963 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1965 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1966 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1971 __PACKAGE__-
>register_method({
1973 path
=> '{vmid}/status/start',
1977 description
=> "Start virtual machine.",
1979 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1982 additionalProperties
=> 0,
1984 node
=> get_standard_option
('pve-node'),
1985 vmid
=> get_standard_option
('pve-vmid',
1986 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1987 skiplock
=> get_standard_option
('skiplock'),
1988 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1989 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1992 enum
=> ['secure', 'insecure'],
1993 description
=> "Migration traffic is encrypted using an SSH " .
1994 "tunnel by default. On secure, completely private networks " .
1995 "this can be disabled to increase performance.",
1998 migration_network
=> {
1999 type
=> 'string', format
=> 'CIDR',
2000 description
=> "CIDR of the (sub) network that is used for migration.",
2003 machine
=> get_standard_option
('pve-qm-machine'),
2005 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2017 my $rpcenv = PVE
::RPCEnvironment
::get
();
2019 my $authuser = $rpcenv->get_user();
2021 my $node = extract_param
($param, 'node');
2023 my $vmid = extract_param
($param, 'vmid');
2025 my $machine = extract_param
($param, 'machine');
2027 my $stateuri = extract_param
($param, 'stateuri');
2028 raise_param_exc
({ stateuri
=> "Only root may use this option." })
2029 if $stateuri && $authuser ne 'root@pam';
2031 my $skiplock = extract_param
($param, 'skiplock');
2032 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2033 if $skiplock && $authuser ne 'root@pam';
2035 my $migratedfrom = extract_param
($param, 'migratedfrom');
2036 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2037 if $migratedfrom && $authuser ne 'root@pam';
2039 my $migration_type = extract_param
($param, 'migration_type');
2040 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2041 if $migration_type && $authuser ne 'root@pam';
2043 my $migration_network = extract_param
($param, 'migration_network');
2044 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2045 if $migration_network && $authuser ne 'root@pam';
2047 my $targetstorage = extract_param
($param, 'targetstorage');
2048 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2049 if $targetstorage && $authuser ne 'root@pam';
2051 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2052 if $targetstorage && !$migratedfrom;
2054 # read spice ticket from STDIN
2056 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2057 if (defined(my $line = <STDIN
>)) {
2059 $spice_ticket = $line;
2063 PVE
::Cluster
::check_cfs_quorum
();
2065 my $storecfg = PVE
::Storage
::config
();
2067 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2068 $rpcenv->{type
} ne 'ha') {
2073 my $service = "vm:$vmid";
2075 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2077 print "Requesting HA start for VM $vmid\n";
2079 PVE
::Tools
::run_command
($cmd);
2084 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2091 syslog
('info', "start VM $vmid: $upid\n");
2093 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2094 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2099 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2103 __PACKAGE__-
>register_method({
2105 path
=> '{vmid}/status/stop',
2109 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2110 "is akin to pulling the power plug of a running computer and may damage the VM data",
2112 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2115 additionalProperties
=> 0,
2117 node
=> get_standard_option
('pve-node'),
2118 vmid
=> get_standard_option
('pve-vmid',
2119 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2120 skiplock
=> get_standard_option
('skiplock'),
2121 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2123 description
=> "Wait maximal timeout seconds.",
2129 description
=> "Do not deactivate storage volumes.",
2142 my $rpcenv = PVE
::RPCEnvironment
::get
();
2144 my $authuser = $rpcenv->get_user();
2146 my $node = extract_param
($param, 'node');
2148 my $vmid = extract_param
($param, 'vmid');
2150 my $skiplock = extract_param
($param, 'skiplock');
2151 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2152 if $skiplock && $authuser ne 'root@pam';
2154 my $keepActive = extract_param
($param, 'keepActive');
2155 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2156 if $keepActive && $authuser ne 'root@pam';
2158 my $migratedfrom = extract_param
($param, 'migratedfrom');
2159 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2160 if $migratedfrom && $authuser ne 'root@pam';
2163 my $storecfg = PVE
::Storage
::config
();
2165 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2170 my $service = "vm:$vmid";
2172 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2174 print "Requesting HA stop for VM $vmid\n";
2176 PVE
::Tools
::run_command
($cmd);
2181 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2187 syslog
('info', "stop VM $vmid: $upid\n");
2189 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2190 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2195 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2199 __PACKAGE__-
>register_method({
2201 path
=> '{vmid}/status/reset',
2205 description
=> "Reset virtual machine.",
2207 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2210 additionalProperties
=> 0,
2212 node
=> get_standard_option
('pve-node'),
2213 vmid
=> get_standard_option
('pve-vmid',
2214 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2215 skiplock
=> get_standard_option
('skiplock'),
2224 my $rpcenv = PVE
::RPCEnvironment
::get
();
2226 my $authuser = $rpcenv->get_user();
2228 my $node = extract_param
($param, 'node');
2230 my $vmid = extract_param
($param, 'vmid');
2232 my $skiplock = extract_param
($param, 'skiplock');
2233 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2234 if $skiplock && $authuser ne 'root@pam';
2236 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2241 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2246 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2249 __PACKAGE__-
>register_method({
2250 name
=> 'vm_shutdown',
2251 path
=> '{vmid}/status/shutdown',
2255 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2256 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2258 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2261 additionalProperties
=> 0,
2263 node
=> get_standard_option
('pve-node'),
2264 vmid
=> get_standard_option
('pve-vmid',
2265 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2266 skiplock
=> get_standard_option
('skiplock'),
2268 description
=> "Wait maximal timeout seconds.",
2274 description
=> "Make sure the VM stops.",
2280 description
=> "Do not deactivate storage volumes.",
2293 my $rpcenv = PVE
::RPCEnvironment
::get
();
2295 my $authuser = $rpcenv->get_user();
2297 my $node = extract_param
($param, 'node');
2299 my $vmid = extract_param
($param, 'vmid');
2301 my $skiplock = extract_param
($param, 'skiplock');
2302 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2303 if $skiplock && $authuser ne 'root@pam';
2305 my $keepActive = extract_param
($param, 'keepActive');
2306 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2307 if $keepActive && $authuser ne 'root@pam';
2309 my $storecfg = PVE
::Storage
::config
();
2313 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2314 # otherwise, we will infer a shutdown command, but run into the timeout,
2315 # then when the vm is resumed, it will instantly shutdown
2317 # checking the qmp status here to get feedback to the gui/cli/api
2318 # and the status query should not take too long
2321 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2325 if (!$err && $qmpstatus->{status
} eq "paused") {
2326 if ($param->{forceStop
}) {
2327 warn "VM is paused - stop instead of shutdown\n";
2330 die "VM is paused - cannot shutdown\n";
2334 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2335 ($rpcenv->{type
} ne 'ha')) {
2340 my $service = "vm:$vmid";
2342 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2344 print "Requesting HA stop for VM $vmid\n";
2346 PVE
::Tools
::run_command
($cmd);
2351 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2358 syslog
('info', "shutdown VM $vmid: $upid\n");
2360 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2361 $shutdown, $param->{forceStop
}, $keepActive);
2366 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2370 __PACKAGE__-
>register_method({
2371 name
=> 'vm_suspend',
2372 path
=> '{vmid}/status/suspend',
2376 description
=> "Suspend virtual machine.",
2378 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2381 additionalProperties
=> 0,
2383 node
=> get_standard_option
('pve-node'),
2384 vmid
=> get_standard_option
('pve-vmid',
2385 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2386 skiplock
=> get_standard_option
('skiplock'),
2391 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2393 statestorage
=> get_standard_option
('pve-storage-id', {
2394 description
=> "The storage for the VM state",
2395 requires
=> 'todisk',
2397 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2407 my $rpcenv = PVE
::RPCEnvironment
::get
();
2409 my $authuser = $rpcenv->get_user();
2411 my $node = extract_param
($param, 'node');
2413 my $vmid = extract_param
($param, 'vmid');
2415 my $todisk = extract_param
($param, 'todisk') // 0;
2417 my $statestorage = extract_param
($param, 'statestorage');
2419 my $skiplock = extract_param
($param, 'skiplock');
2420 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2421 if $skiplock && $authuser ne 'root@pam';
2423 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2425 die "Cannot suspend HA managed VM to disk\n"
2426 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2428 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2433 syslog
('info', "suspend VM $vmid: $upid\n");
2435 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2440 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2443 __PACKAGE__-
>register_method({
2444 name
=> 'vm_resume',
2445 path
=> '{vmid}/status/resume',
2449 description
=> "Resume virtual machine.",
2451 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2454 additionalProperties
=> 0,
2456 node
=> get_standard_option
('pve-node'),
2457 vmid
=> get_standard_option
('pve-vmid',
2458 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2459 skiplock
=> get_standard_option
('skiplock'),
2460 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $node = extract_param
($param, 'node');
2476 my $vmid = extract_param
($param, 'vmid');
2478 my $skiplock = extract_param
($param, 'skiplock');
2479 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2480 if $skiplock && $authuser ne 'root@pam';
2482 my $nocheck = extract_param
($param, 'nocheck');
2484 my $to_disk_suspended;
2486 PVE
::QemuConfig-
>lock_config($vmid, sub {
2487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2488 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2492 die "VM $vmid not running\n"
2493 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2498 syslog
('info', "resume VM $vmid: $upid\n");
2500 if (!$to_disk_suspended) {
2501 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2503 my $storecfg = PVE
::Storage
::config
();
2504 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2510 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2513 __PACKAGE__-
>register_method({
2514 name
=> 'vm_sendkey',
2515 path
=> '{vmid}/sendkey',
2519 description
=> "Send key event to virtual machine.",
2521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2524 additionalProperties
=> 0,
2526 node
=> get_standard_option
('pve-node'),
2527 vmid
=> get_standard_option
('pve-vmid',
2528 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2529 skiplock
=> get_standard_option
('skiplock'),
2531 description
=> "The key (qemu monitor encoding).",
2536 returns
=> { type
=> 'null'},
2540 my $rpcenv = PVE
::RPCEnvironment
::get
();
2542 my $authuser = $rpcenv->get_user();
2544 my $node = extract_param
($param, 'node');
2546 my $vmid = extract_param
($param, 'vmid');
2548 my $skiplock = extract_param
($param, 'skiplock');
2549 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2550 if $skiplock && $authuser ne 'root@pam';
2552 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2557 __PACKAGE__-
>register_method({
2558 name
=> 'vm_feature',
2559 path
=> '{vmid}/feature',
2563 description
=> "Check if feature for virtual machine is available.",
2565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2568 additionalProperties
=> 0,
2570 node
=> get_standard_option
('pve-node'),
2571 vmid
=> get_standard_option
('pve-vmid'),
2573 description
=> "Feature to check.",
2575 enum
=> [ 'snapshot', 'clone', 'copy' ],
2577 snapname
=> get_standard_option
('pve-snapshot-name', {
2585 hasFeature
=> { type
=> 'boolean' },
2588 items
=> { type
=> 'string' },
2595 my $node = extract_param
($param, 'node');
2597 my $vmid = extract_param
($param, 'vmid');
2599 my $snapname = extract_param
($param, 'snapname');
2601 my $feature = extract_param
($param, 'feature');
2603 my $running = PVE
::QemuServer
::check_running
($vmid);
2605 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2608 my $snap = $conf->{snapshots
}->{$snapname};
2609 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2612 my $storecfg = PVE
::Storage
::config
();
2614 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2615 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2618 hasFeature
=> $hasFeature,
2619 nodes
=> [ keys %$nodelist ],
2623 __PACKAGE__-
>register_method({
2625 path
=> '{vmid}/clone',
2629 description
=> "Create a copy of virtual machine/template.",
2631 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2632 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2633 "'Datastore.AllocateSpace' on any used storage.",
2636 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2638 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2639 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2644 additionalProperties
=> 0,
2646 node
=> get_standard_option
('pve-node'),
2647 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2648 newid
=> get_standard_option
('pve-vmid', {
2649 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2650 description
=> 'VMID for the clone.' }),
2653 type
=> 'string', format
=> 'dns-name',
2654 description
=> "Set a name for the new VM.",
2659 description
=> "Description for the new VM.",
2663 type
=> 'string', format
=> 'pve-poolid',
2664 description
=> "Add the new VM to the specified pool.",
2666 snapname
=> get_standard_option
('pve-snapshot-name', {
2669 storage
=> get_standard_option
('pve-storage-id', {
2670 description
=> "Target storage for full clone.",
2674 description
=> "Target format for file storage. Only valid for full clone.",
2677 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2682 description
=> "Create a full copy of all disks. This is always done when " .
2683 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2685 target
=> get_standard_option
('pve-node', {
2686 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2690 description
=> "Override I/O bandwidth limit (in KiB/s).",
2694 default => 'clone limit from datacenter or storage config',
2704 my $rpcenv = PVE
::RPCEnvironment
::get
();
2706 my $authuser = $rpcenv->get_user();
2708 my $node = extract_param
($param, 'node');
2710 my $vmid = extract_param
($param, 'vmid');
2712 my $newid = extract_param
($param, 'newid');
2714 my $pool = extract_param
($param, 'pool');
2716 if (defined($pool)) {
2717 $rpcenv->check_pool_exist($pool);
2720 my $snapname = extract_param
($param, 'snapname');
2722 my $storage = extract_param
($param, 'storage');
2724 my $format = extract_param
($param, 'format');
2726 my $target = extract_param
($param, 'target');
2728 my $localnode = PVE
::INotify
::nodename
();
2730 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2732 PVE
::Cluster
::check_node_exists
($target) if $target;
2734 my $storecfg = PVE
::Storage
::config
();
2737 # check if storage is enabled on local node
2738 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2740 # check if storage is available on target node
2741 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2742 # clone only works if target storage is shared
2743 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2744 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2748 PVE
::Cluster
::check_cfs_quorum
();
2750 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2752 # exclusive lock if VM is running - else shared lock is enough;
2753 my $shared_lock = $running ?
0 : 1;
2757 # do all tests after lock
2758 # we also try to do all tests before we fork the worker
2760 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2762 PVE
::QemuConfig-
>check_lock($conf);
2764 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2766 die "unexpected state change\n" if $verify_running != $running;
2768 die "snapshot '$snapname' does not exist\n"
2769 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2771 my $full = extract_param
($param, 'full');
2772 if (!defined($full)) {
2773 $full = !PVE
::QemuConfig-
>is_template($conf);
2776 die "parameter 'storage' not allowed for linked clones\n"
2777 if defined($storage) && !$full;
2779 die "parameter 'format' not allowed for linked clones\n"
2780 if defined($format) && !$full;
2782 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2784 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2786 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2788 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2790 die "unable to create VM $newid: config file already exists\n"
2793 my $newconf = { lock => 'clone' };
2798 foreach my $opt (keys %$oldconf) {
2799 my $value = $oldconf->{$opt};
2801 # do not copy snapshot related info
2802 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2803 $opt eq 'vmstate' || $opt eq 'snapstate';
2805 # no need to copy unused images, because VMID(owner) changes anyways
2806 next if $opt =~ m/^unused\d+$/;
2808 # always change MAC! address
2809 if ($opt =~ m/^net(\d+)$/) {
2810 my $net = PVE
::QemuServer
::parse_net
($value);
2811 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2812 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2813 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2814 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2815 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2816 die "unable to parse drive options for '$opt'\n" if !$drive;
2817 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2818 $newconf->{$opt} = $value; # simply copy configuration
2820 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2821 die "Full clone feature is not supported for drive '$opt'\n"
2822 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2823 $fullclone->{$opt} = 1;
2825 # not full means clone instead of copy
2826 die "Linked clone feature is not supported for drive '$opt'\n"
2827 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2829 $drives->{$opt} = $drive;
2830 push @$vollist, $drive->{file
};
2833 # copy everything else
2834 $newconf->{$opt} = $value;
2838 # auto generate a new uuid
2839 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2840 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2841 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2843 # auto generate a new vmgenid if the option was set
2844 if ($newconf->{vmgenid
}) {
2845 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2848 delete $newconf->{template
};
2850 if ($param->{name
}) {
2851 $newconf->{name
} = $param->{name
};
2853 if ($oldconf->{name
}) {
2854 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2856 $newconf->{name
} = "Copy-of-VM-$vmid";
2860 if ($param->{description
}) {
2861 $newconf->{description
} = $param->{description
};
2864 # create empty/temp config - this fails if VM already exists on other node
2865 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2870 my $newvollist = [];
2877 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2879 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2881 my $bwlimit = extract_param
($param, 'bwlimit');
2883 my $total_jobs = scalar(keys %{$drives});
2886 foreach my $opt (keys %$drives) {
2887 my $drive = $drives->{$opt};
2888 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2890 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2891 my $storage_list = [ $src_sid ];
2892 push @$storage_list, $storage if defined($storage);
2893 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2895 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2896 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2897 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2899 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2901 PVE
::QemuConfig-
>write_config($newid, $newconf);
2905 delete $newconf->{lock};
2907 # do not write pending changes
2908 if (my @changes = keys %{$newconf->{pending
}}) {
2909 my $pending = join(',', @changes);
2910 warn "found pending changes for '$pending', discarding for clone\n";
2911 delete $newconf->{pending
};
2914 PVE
::QemuConfig-
>write_config($newid, $newconf);
2917 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2918 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2919 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2921 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2922 die "Failed to move config to node '$target' - rename failed: $!\n"
2923 if !rename($conffile, $newconffile);
2926 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2931 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2933 sleep 1; # some storage like rbd need to wait before release volume - really?
2935 foreach my $volid (@$newvollist) {
2936 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2939 die "clone failed: $err";
2945 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2947 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2950 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2951 # Aquire exclusive lock lock for $newid
2952 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2957 __PACKAGE__-
>register_method({
2958 name
=> 'move_vm_disk',
2959 path
=> '{vmid}/move_disk',
2963 description
=> "Move volume to different storage.",
2965 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2967 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2968 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2972 additionalProperties
=> 0,
2974 node
=> get_standard_option
('pve-node'),
2975 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2978 description
=> "The disk you want to move.",
2979 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2981 storage
=> get_standard_option
('pve-storage-id', {
2982 description
=> "Target storage.",
2983 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2987 description
=> "Target Format.",
2988 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2993 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2999 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3004 description
=> "Override I/O bandwidth limit (in KiB/s).",
3008 default => 'move limit from datacenter or storage config',
3014 description
=> "the task ID.",
3019 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3023 my $node = extract_param
($param, 'node');
3025 my $vmid = extract_param
($param, 'vmid');
3027 my $digest = extract_param
($param, 'digest');
3029 my $disk = extract_param
($param, 'disk');
3031 my $storeid = extract_param
($param, 'storage');
3033 my $format = extract_param
($param, 'format');
3035 my $storecfg = PVE
::Storage
::config
();
3037 my $updatefn = sub {
3039 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3041 PVE
::QemuConfig-
>check_lock($conf);
3043 die "checksum missmatch (file change by other user?)\n"
3044 if $digest && $digest ne $conf->{digest
};
3046 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3048 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3050 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3052 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3055 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3056 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3060 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3061 (!$format || !$oldfmt || $oldfmt eq $format);
3063 # this only checks snapshots because $disk is passed!
3064 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3065 die "you can't move a disk with snapshots and delete the source\n"
3066 if $snapshotted && $param->{delete};
3068 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3070 my $running = PVE
::QemuServer
::check_running
($vmid);
3072 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3076 my $newvollist = [];
3082 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3084 warn "moving disk with snapshots, snapshots will not be moved!\n"
3087 my $bwlimit = extract_param
($param, 'bwlimit');
3088 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3090 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3091 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3093 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3095 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3097 # convert moved disk to base if part of template
3098 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3099 if PVE
::QemuConfig-
>is_template($conf);
3101 PVE
::QemuConfig-
>write_config($vmid, $conf);
3103 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3104 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3108 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3109 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3116 foreach my $volid (@$newvollist) {
3117 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3120 die "storage migration failed: $err";
3123 if ($param->{delete}) {
3125 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3126 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3132 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3135 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3138 __PACKAGE__-
>register_method({
3139 name
=> 'migrate_vm',
3140 path
=> '{vmid}/migrate',
3144 description
=> "Migrate virtual machine. Creates a new migration task.",
3146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3149 additionalProperties
=> 0,
3151 node
=> get_standard_option
('pve-node'),
3152 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3153 target
=> get_standard_option
('pve-node', {
3154 description
=> "Target node.",
3155 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3159 description
=> "Use online/live migration.",
3164 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3169 enum
=> ['secure', 'insecure'],
3170 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3173 migration_network
=> {
3174 type
=> 'string', format
=> 'CIDR',
3175 description
=> "CIDR of the (sub) network that is used for migration.",
3178 "with-local-disks" => {
3180 description
=> "Enable live storage migration for local disk",
3183 targetstorage
=> get_standard_option
('pve-storage-id', {
3184 description
=> "Default target storage.",
3186 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3189 description
=> "Override I/O bandwidth limit (in KiB/s).",
3193 default => 'migrate limit from datacenter or storage config',
3199 description
=> "the task ID.",
3204 my $rpcenv = PVE
::RPCEnvironment
::get
();
3206 my $authuser = $rpcenv->get_user();
3208 my $target = extract_param
($param, 'target');
3210 my $localnode = PVE
::INotify
::nodename
();
3211 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3213 PVE
::Cluster
::check_cfs_quorum
();
3215 PVE
::Cluster
::check_node_exists
($target);
3217 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3219 my $vmid = extract_param
($param, 'vmid');
3221 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3222 if !$param->{online
} && $param->{targetstorage
};
3224 raise_param_exc
({ force
=> "Only root may use this option." })
3225 if $param->{force
} && $authuser ne 'root@pam';
3227 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3228 if $param->{migration_type
} && $authuser ne 'root@pam';
3230 # allow root only until better network permissions are available
3231 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3232 if $param->{migration_network
} && $authuser ne 'root@pam';
3235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3237 # try to detect errors early
3239 PVE
::QemuConfig-
>check_lock($conf);
3241 if (PVE
::QemuServer
::check_running
($vmid)) {
3242 die "cant migrate running VM without --online\n"
3243 if !$param->{online
};
3246 my $storecfg = PVE
::Storage
::config
();
3248 if( $param->{targetstorage
}) {
3249 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3251 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3254 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3259 my $service = "vm:$vmid";
3261 my $cmd = ['ha-manager', 'migrate', $service, $target];
3263 print "Requesting HA migration for VM $vmid to node $target\n";
3265 PVE
::Tools
::run_command
($cmd);
3270 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3275 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3279 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3282 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3287 __PACKAGE__-
>register_method({
3289 path
=> '{vmid}/monitor',
3293 description
=> "Execute Qemu monitor commands.",
3295 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3296 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3299 additionalProperties
=> 0,
3301 node
=> get_standard_option
('pve-node'),
3302 vmid
=> get_standard_option
('pve-vmid'),
3305 description
=> "The monitor command.",
3309 returns
=> { type
=> 'string'},
3313 my $rpcenv = PVE
::RPCEnvironment
::get
();
3314 my $authuser = $rpcenv->get_user();
3317 my $command = shift;
3318 return $command =~ m/^\s*info(\s+|$)/
3319 || $command =~ m/^\s*help\s*$/;
3322 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3323 if !&$is_ro($param->{command
});
3325 my $vmid = $param->{vmid
};
3327 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3331 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3333 $res = "ERROR: $@" if $@;
3338 __PACKAGE__-
>register_method({
3339 name
=> 'resize_vm',
3340 path
=> '{vmid}/resize',
3344 description
=> "Extend volume size.",
3346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3349 additionalProperties
=> 0,
3351 node
=> get_standard_option
('pve-node'),
3352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3353 skiplock
=> get_standard_option
('skiplock'),
3356 description
=> "The disk you want to resize.",
3357 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3361 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3362 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.",
3366 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3372 returns
=> { type
=> 'null'},
3376 my $rpcenv = PVE
::RPCEnvironment
::get
();
3378 my $authuser = $rpcenv->get_user();
3380 my $node = extract_param
($param, 'node');
3382 my $vmid = extract_param
($param, 'vmid');
3384 my $digest = extract_param
($param, 'digest');
3386 my $disk = extract_param
($param, 'disk');
3388 my $sizestr = extract_param
($param, 'size');
3390 my $skiplock = extract_param
($param, 'skiplock');
3391 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3392 if $skiplock && $authuser ne 'root@pam';
3394 my $storecfg = PVE
::Storage
::config
();
3396 my $updatefn = sub {
3398 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3400 die "checksum missmatch (file change by other user?)\n"
3401 if $digest && $digest ne $conf->{digest
};
3402 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3404 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3406 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3408 my (undef, undef, undef, undef, undef, undef, $format) =
3409 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3411 die "can't resize volume: $disk if snapshot exists\n"
3412 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3414 my $volid = $drive->{file
};
3416 die "disk '$disk' has no associated volume\n" if !$volid;
3418 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3420 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3422 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3424 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3425 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3427 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3428 my ($ext, $newsize, $unit) = ($1, $2, $4);
3431 $newsize = $newsize * 1024;
3432 } elsif ($unit eq 'M') {
3433 $newsize = $newsize * 1024 * 1024;
3434 } elsif ($unit eq 'G') {
3435 $newsize = $newsize * 1024 * 1024 * 1024;
3436 } elsif ($unit eq 'T') {
3437 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3440 $newsize += $size if $ext;
3441 $newsize = int($newsize);
3443 die "shrinking disks is not supported\n" if $newsize < $size;
3445 return if $size == $newsize;
3447 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3449 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3451 $drive->{size
} = $newsize;
3452 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3454 PVE
::QemuConfig-
>write_config($vmid, $conf);
3457 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3461 __PACKAGE__-
>register_method({
3462 name
=> 'snapshot_list',
3463 path
=> '{vmid}/snapshot',
3465 description
=> "List all snapshots.",
3467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3470 protected
=> 1, # qemu pid files are only readable by root
3472 additionalProperties
=> 0,
3474 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3475 node
=> get_standard_option
('pve-node'),
3484 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3488 description
=> "Snapshot includes RAM.",
3493 description
=> "Snapshot description.",
3497 description
=> "Snapshot creation time",
3499 renderer
=> 'timestamp',
3503 description
=> "Parent snapshot identifier.",
3509 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3514 my $vmid = $param->{vmid
};
3516 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3517 my $snaphash = $conf->{snapshots
} || {};
3521 foreach my $name (keys %$snaphash) {
3522 my $d = $snaphash->{$name};
3525 snaptime
=> $d->{snaptime
} || 0,
3526 vmstate
=> $d->{vmstate
} ?
1 : 0,
3527 description
=> $d->{description
} || '',
3529 $item->{parent
} = $d->{parent
} if $d->{parent
};
3530 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3534 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3537 digest
=> $conf->{digest
},
3538 running
=> $running,
3539 description
=> "You are here!",
3541 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3543 push @$res, $current;
3548 __PACKAGE__-
>register_method({
3550 path
=> '{vmid}/snapshot',
3554 description
=> "Snapshot a VM.",
3556 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3559 additionalProperties
=> 0,
3561 node
=> get_standard_option
('pve-node'),
3562 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3563 snapname
=> get_standard_option
('pve-snapshot-name'),
3567 description
=> "Save the vmstate",
3572 description
=> "A textual description or comment.",
3578 description
=> "the task ID.",
3583 my $rpcenv = PVE
::RPCEnvironment
::get
();
3585 my $authuser = $rpcenv->get_user();
3587 my $node = extract_param
($param, 'node');
3589 my $vmid = extract_param
($param, 'vmid');
3591 my $snapname = extract_param
($param, 'snapname');
3593 die "unable to use snapshot name 'current' (reserved name)\n"
3594 if $snapname eq 'current';
3597 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3598 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3599 $param->{description
});
3602 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3605 __PACKAGE__-
>register_method({
3606 name
=> 'snapshot_cmd_idx',
3607 path
=> '{vmid}/snapshot/{snapname}',
3614 additionalProperties
=> 0,
3616 vmid
=> get_standard_option
('pve-vmid'),
3617 node
=> get_standard_option
('pve-node'),
3618 snapname
=> get_standard_option
('pve-snapshot-name'),
3627 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3634 push @$res, { cmd
=> 'rollback' };
3635 push @$res, { cmd
=> 'config' };
3640 __PACKAGE__-
>register_method({
3641 name
=> 'update_snapshot_config',
3642 path
=> '{vmid}/snapshot/{snapname}/config',
3646 description
=> "Update snapshot metadata.",
3648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3651 additionalProperties
=> 0,
3653 node
=> get_standard_option
('pve-node'),
3654 vmid
=> get_standard_option
('pve-vmid'),
3655 snapname
=> get_standard_option
('pve-snapshot-name'),
3659 description
=> "A textual description or comment.",
3663 returns
=> { type
=> 'null' },
3667 my $rpcenv = PVE
::RPCEnvironment
::get
();
3669 my $authuser = $rpcenv->get_user();
3671 my $vmid = extract_param
($param, 'vmid');
3673 my $snapname = extract_param
($param, 'snapname');
3675 return undef if !defined($param->{description
});
3677 my $updatefn = sub {
3679 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3681 PVE
::QemuConfig-
>check_lock($conf);
3683 my $snap = $conf->{snapshots
}->{$snapname};
3685 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3687 $snap->{description
} = $param->{description
} if defined($param->{description
});
3689 PVE
::QemuConfig-
>write_config($vmid, $conf);
3692 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3697 __PACKAGE__-
>register_method({
3698 name
=> 'get_snapshot_config',
3699 path
=> '{vmid}/snapshot/{snapname}/config',
3702 description
=> "Get snapshot configuration",
3704 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3707 additionalProperties
=> 0,
3709 node
=> get_standard_option
('pve-node'),
3710 vmid
=> get_standard_option
('pve-vmid'),
3711 snapname
=> get_standard_option
('pve-snapshot-name'),
3714 returns
=> { type
=> "object" },
3718 my $rpcenv = PVE
::RPCEnvironment
::get
();
3720 my $authuser = $rpcenv->get_user();
3722 my $vmid = extract_param
($param, 'vmid');
3724 my $snapname = extract_param
($param, 'snapname');
3726 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3728 my $snap = $conf->{snapshots
}->{$snapname};
3730 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3735 __PACKAGE__-
>register_method({
3737 path
=> '{vmid}/snapshot/{snapname}/rollback',
3741 description
=> "Rollback VM state to specified snapshot.",
3743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3746 additionalProperties
=> 0,
3748 node
=> get_standard_option
('pve-node'),
3749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3750 snapname
=> get_standard_option
('pve-snapshot-name'),
3755 description
=> "the task ID.",
3760 my $rpcenv = PVE
::RPCEnvironment
::get
();
3762 my $authuser = $rpcenv->get_user();
3764 my $node = extract_param
($param, 'node');
3766 my $vmid = extract_param
($param, 'vmid');
3768 my $snapname = extract_param
($param, 'snapname');
3771 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3772 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3776 # hold migration lock, this makes sure that nobody create replication snapshots
3777 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3780 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3783 __PACKAGE__-
>register_method({
3784 name
=> 'delsnapshot',
3785 path
=> '{vmid}/snapshot/{snapname}',
3789 description
=> "Delete a VM snapshot.",
3791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3794 additionalProperties
=> 0,
3796 node
=> get_standard_option
('pve-node'),
3797 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3798 snapname
=> get_standard_option
('pve-snapshot-name'),
3802 description
=> "For removal from config file, even if removing disk snapshots fails.",
3808 description
=> "the task ID.",
3813 my $rpcenv = PVE
::RPCEnvironment
::get
();
3815 my $authuser = $rpcenv->get_user();
3817 my $node = extract_param
($param, 'node');
3819 my $vmid = extract_param
($param, 'vmid');
3821 my $snapname = extract_param
($param, 'snapname');
3824 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3825 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3828 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3831 __PACKAGE__-
>register_method({
3833 path
=> '{vmid}/template',
3837 description
=> "Create a Template.",
3839 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3840 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3843 additionalProperties
=> 0,
3845 node
=> get_standard_option
('pve-node'),
3846 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3850 description
=> "If you want to convert only 1 disk to base image.",
3851 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3856 returns
=> { type
=> 'null'},
3860 my $rpcenv = PVE
::RPCEnvironment
::get
();
3862 my $authuser = $rpcenv->get_user();
3864 my $node = extract_param
($param, 'node');
3866 my $vmid = extract_param
($param, 'vmid');
3868 my $disk = extract_param
($param, 'disk');
3870 my $updatefn = sub {
3872 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3874 PVE
::QemuConfig-
>check_lock($conf);
3876 die "unable to create template, because VM contains snapshots\n"
3877 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3879 die "you can't convert a template to a template\n"
3880 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3882 die "you can't convert a VM to template if VM is running\n"
3883 if PVE
::QemuServer
::check_running
($vmid);
3886 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3889 $conf->{template
} = 1;
3890 PVE
::QemuConfig-
>write_config($vmid, $conf);
3892 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3895 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);