1 package PVE
::API2
::Qemu
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
159 # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
160 my $cloudinit_iso_size = 4; # in MB
161 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, $name, $cloudinit_iso_size*1024);
163 $disk->{file
} = $volid;
164 $disk->{media
} = 'cdrom';
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
168 } elsif ($volid =~ $NEW_DISK_RE) {
169 my ($storeid, $size) = ($2 || $default_storage, $3);
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
172 my $fmt = $disk->{format
} || $defformat;
174 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
177 if ($ds eq 'efidisk0') {
178 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
180 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
182 push @$vollist, $volid;
183 $disk->{file
} = $volid;
184 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
185 delete $disk->{format
}; # no longer needed
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
189 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
191 my $volid_is_new = 1;
194 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
195 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
200 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
304 my $check_vm_modify_config_perm = sub {
305 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
307 return 1 if $authuser eq 'root@pam';
309 foreach my $opt (@$key_list) {
310 # disk checks need to be done somewhere else
311 next if PVE
::QemuServer
::is_valid_drivename
($opt);
312 next if $opt eq 'cdrom';
313 next if $opt =~ m/^unused\d+$/;
315 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
316 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
317 } elsif ($memoryoptions->{$opt}) {
318 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
319 } elsif ($hwtypeoptions->{$opt}) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
321 } elsif ($generaloptions->{$opt}) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
323 # special case for startup since it changes host behaviour
324 if ($opt eq 'startup') {
325 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
327 } elsif ($vmpoweroptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
329 } elsif ($diskoptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
331 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
332 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
334 # catches usb\d+, hostpci\d+, args, lock, etc.
335 # new options will be checked here
336 die "only root can set '$opt' config\n";
343 __PACKAGE__-
>register_method({
347 description
=> "Virtual machine index (per node).",
349 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
353 protected
=> 1, # qemu pid files are only readable by root
355 additionalProperties
=> 0,
357 node
=> get_standard_option
('pve-node'),
361 description
=> "Determine the full status of active VMs.",
369 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
371 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
376 my $rpcenv = PVE
::RPCEnvironment
::get
();
377 my $authuser = $rpcenv->get_user();
379 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
382 foreach my $vmid (keys %$vmstatus) {
383 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
385 my $data = $vmstatus->{$vmid};
394 __PACKAGE__-
>register_method({
398 description
=> "Create or restore a virtual machine.",
400 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
401 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
402 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
403 user
=> 'all', # check inside
408 additionalProperties
=> 0,
409 properties
=> PVE
::QemuServer
::json_config_properties
(
411 node
=> get_standard_option
('pve-node'),
412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
414 description
=> "The backup file.",
418 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
420 storage
=> get_standard_option
('pve-storage-id', {
421 description
=> "Default storage.",
423 completion
=> \
&PVE
::QemuServer
::complete_storage
,
428 description
=> "Allow to overwrite existing VM.",
429 requires
=> 'archive',
434 description
=> "Assign a unique random ethernet address.",
435 requires
=> 'archive',
439 type
=> 'string', format
=> 'pve-poolid',
440 description
=> "Add the VM to the specified pool.",
443 description
=> "Override I/O bandwidth limit (in KiB/s).",
447 default => 'restore limit from datacenter.cfg/storage.cfg',
453 description
=> "Start VM after it was created successfully.",
463 my $rpcenv = PVE
::RPCEnvironment
::get
();
465 my $authuser = $rpcenv->get_user();
467 my $node = extract_param
($param, 'node');
469 my $vmid = extract_param
($param, 'vmid');
471 my $archive = extract_param
($param, 'archive');
472 my $is_restore = !!$archive;
474 my $storage = extract_param
($param, 'storage');
476 my $force = extract_param
($param, 'force');
478 my $unique = extract_param
($param, 'unique');
480 my $pool = extract_param
($param, 'pool');
482 my $bwlimit = extract_param
($param, 'bwlimit');
484 my $start_after_create = extract_param
($param, 'start');
486 my $filename = PVE
::QemuConfig-
>config_file($vmid);
488 my $storecfg = PVE
::Storage
::config
();
490 if (defined(my $ssh_keys = $param->{sshkeys
})) {
491 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
492 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
495 PVE
::Cluster
::check_cfs_quorum
();
497 if (defined($pool)) {
498 $rpcenv->check_pool_exist($pool);
501 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
502 if defined($storage);
504 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
506 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
508 } elsif ($archive && $force && (-f
$filename) &&
509 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
510 # OK: user has VM.Backup permissions, and want to restore an existing VM
516 &$resolve_cdrom_alias($param);
518 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
520 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
522 foreach my $opt (keys %$param) {
523 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
524 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
525 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
527 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
528 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
532 PVE
::QemuServer
::add_random_macs
($param);
534 my $keystr = join(' ', keys %$param);
535 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
537 if ($archive eq '-') {
538 die "pipe requires cli environment\n"
539 if $rpcenv->{type
} ne 'cli';
541 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
542 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
546 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
548 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
549 die "$emsg $@" if $@;
551 my $restorefn = sub {
552 my $conf = PVE
::QemuConfig-
>load_config($vmid);
554 PVE
::QemuConfig-
>check_protection($conf, $emsg);
556 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
557 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
560 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
564 bwlimit
=> $bwlimit, });
566 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
568 if ($start_after_create) {
569 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
574 # ensure no old replication state are exists
575 PVE
::ReplicationState
::delete_guest_states
($vmid);
577 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
581 # ensure no old replication state are exists
582 PVE
::ReplicationState
::delete_guest_states
($vmid);
590 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
594 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
596 if (!$conf->{bootdisk
}) {
597 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
598 $conf->{bootdisk
} = $firstdisk if $firstdisk;
601 # auto generate uuid if user did not specify smbios1 option
602 if (!$conf->{smbios1
}) {
603 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
606 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
607 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
610 PVE
::QemuConfig-
>write_config($vmid, $conf);
616 foreach my $volid (@$vollist) {
617 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
623 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
626 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
628 if ($start_after_create) {
629 print "Execute autostart\n";
630 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
635 my ($code, $worker_name);
637 $worker_name = 'qmrestore';
639 eval { $restorefn->() };
641 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
647 $worker_name = 'qmcreate';
649 eval { $createfn->() };
652 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
653 unlink($conffile) or die "failed to remove config file: $!\n";
661 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
664 __PACKAGE__-
>register_method({
669 description
=> "Directory index",
674 additionalProperties
=> 0,
676 node
=> get_standard_option
('pve-node'),
677 vmid
=> get_standard_option
('pve-vmid'),
685 subdir
=> { type
=> 'string' },
688 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
694 { subdir
=> 'config' },
695 { subdir
=> 'pending' },
696 { subdir
=> 'status' },
697 { subdir
=> 'unlink' },
698 { subdir
=> 'vncproxy' },
699 { subdir
=> 'termproxy' },
700 { subdir
=> 'migrate' },
701 { subdir
=> 'resize' },
702 { subdir
=> 'move' },
704 { subdir
=> 'rrddata' },
705 { subdir
=> 'monitor' },
706 { subdir
=> 'agent' },
707 { subdir
=> 'snapshot' },
708 { subdir
=> 'spiceproxy' },
709 { subdir
=> 'sendkey' },
710 { subdir
=> 'firewall' },
716 __PACKAGE__-
>register_method ({
717 subclass
=> "PVE::API2::Firewall::VM",
718 path
=> '{vmid}/firewall',
721 __PACKAGE__-
>register_method ({
722 subclass
=> "PVE::API2::Qemu::Agent",
723 path
=> '{vmid}/agent',
726 __PACKAGE__-
>register_method({
728 path
=> '{vmid}/rrd',
730 protected
=> 1, # fixme: can we avoid that?
732 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
734 description
=> "Read VM RRD statistics (returns PNG)",
736 additionalProperties
=> 0,
738 node
=> get_standard_option
('pve-node'),
739 vmid
=> get_standard_option
('pve-vmid'),
741 description
=> "Specify the time frame you are interested in.",
743 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
746 description
=> "The list of datasources you want to display.",
747 type
=> 'string', format
=> 'pve-configid-list',
750 description
=> "The RRD consolidation function",
752 enum
=> [ 'AVERAGE', 'MAX' ],
760 filename
=> { type
=> 'string' },
766 return PVE
::Cluster
::create_rrd_graph
(
767 "pve2-vm/$param->{vmid}", $param->{timeframe
},
768 $param->{ds
}, $param->{cf
});
772 __PACKAGE__-
>register_method({
774 path
=> '{vmid}/rrddata',
776 protected
=> 1, # fixme: can we avoid that?
778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
780 description
=> "Read VM RRD statistics",
782 additionalProperties
=> 0,
784 node
=> get_standard_option
('pve-node'),
785 vmid
=> get_standard_option
('pve-vmid'),
787 description
=> "Specify the time frame you are interested in.",
789 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
792 description
=> "The RRD consolidation function",
794 enum
=> [ 'AVERAGE', 'MAX' ],
809 return PVE
::Cluster
::create_rrd_data
(
810 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
814 __PACKAGE__-
>register_method({
816 path
=> '{vmid}/config',
819 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
824 additionalProperties
=> 0,
826 node
=> get_standard_option
('pve-node'),
827 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
829 description
=> "Get current values (instead of pending values).",
834 snapshot
=> get_standard_option
('pve-snapshot-name', {
835 description
=> "Fetch config values from given snapshot.",
838 my ($cmd, $pname, $cur, $args) = @_;
839 PVE
::QemuConfig-
>snapshot_list($args->[0]);
845 description
=> "The current VM configuration.",
847 properties
=> PVE
::QemuServer
::json_config_properties
({
850 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
857 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
859 if (my $snapname = $param->{snapshot
}) {
860 my $snapshot = $conf->{snapshots
}->{$snapname};
861 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
863 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
868 delete $conf->{snapshots
};
870 if (!$param->{current
}) {
871 foreach my $opt (keys %{$conf->{pending
}}) {
872 next if $opt eq 'delete';
873 my $value = $conf->{pending
}->{$opt};
874 next if ref($value); # just to be sure
875 $conf->{$opt} = $value;
877 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
878 foreach my $opt (keys %$pending_delete_hash) {
879 delete $conf->{$opt} if $conf->{$opt};
883 delete $conf->{pending
};
885 # hide cloudinit password
886 if ($conf->{cipassword
}) {
887 $conf->{cipassword
} = '**********';
893 __PACKAGE__-
>register_method({
894 name
=> 'vm_pending',
895 path
=> '{vmid}/pending',
898 description
=> "Get virtual machine configuration, including pending changes.",
900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
903 additionalProperties
=> 0,
905 node
=> get_standard_option
('pve-node'),
906 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
915 description
=> "Configuration option name.",
919 description
=> "Current value.",
924 description
=> "Pending value.",
929 description
=> "Indicates a pending delete request if present and not 0. " .
930 "The value 2 indicates a force-delete request.",
942 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
944 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
948 foreach my $opt (keys %$conf) {
949 next if ref($conf->{$opt});
950 my $item = { key
=> $opt };
951 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
952 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
953 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
955 # hide cloudinit password
956 if ($opt eq 'cipassword') {
957 $item->{value
} = '**********' if defined($item->{value
});
958 # the trailing space so that the pending string is different
959 $item->{pending
} = '********** ' if defined($item->{pending
});
964 foreach my $opt (keys %{$conf->{pending
}}) {
965 next if $opt eq 'delete';
966 next if ref($conf->{pending
}->{$opt}); # just to be sure
967 next if defined($conf->{$opt});
968 my $item = { key
=> $opt };
969 $item->{pending
} = $conf->{pending
}->{$opt};
971 # hide cloudinit password
972 if ($opt eq 'cipassword') {
973 $item->{pending
} = '**********' if defined($item->{pending
});
978 while (my ($opt, $force) = each %$pending_delete_hash) {
979 next if $conf->{pending
}->{$opt}; # just to be sure
980 next if $conf->{$opt};
981 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
988 # POST/PUT {vmid}/config implementation
990 # The original API used PUT (idempotent) an we assumed that all operations
991 # are fast. But it turned out that almost any configuration change can
992 # involve hot-plug actions, or disk alloc/free. Such actions can take long
993 # time to complete and have side effects (not idempotent).
995 # The new implementation uses POST and forks a worker process. We added
996 # a new option 'background_delay'. If specified we wait up to
997 # 'background_delay' second for the worker task to complete. It returns null
998 # if the task is finished within that time, else we return the UPID.
1000 my $update_vm_api = sub {
1001 my ($param, $sync) = @_;
1003 my $rpcenv = PVE
::RPCEnvironment
::get
();
1005 my $authuser = $rpcenv->get_user();
1007 my $node = extract_param
($param, 'node');
1009 my $vmid = extract_param
($param, 'vmid');
1011 my $digest = extract_param
($param, 'digest');
1013 my $background_delay = extract_param
($param, 'background_delay');
1015 if (defined(my $cipassword = $param->{cipassword
})) {
1016 # Same logic as in cloud-init (but with the regex fixed...)
1017 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1018 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1021 my @paramarr = (); # used for log message
1022 foreach my $key (sort keys %$param) {
1023 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1024 push @paramarr, "-$key", $value;
1027 my $skiplock = extract_param
($param, 'skiplock');
1028 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1029 if $skiplock && $authuser ne 'root@pam';
1031 my $delete_str = extract_param
($param, 'delete');
1033 my $revert_str = extract_param
($param, 'revert');
1035 my $force = extract_param
($param, 'force');
1037 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1038 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1039 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1042 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1044 my $storecfg = PVE
::Storage
::config
();
1046 my $defaults = PVE
::QemuServer
::load_defaults
();
1048 &$resolve_cdrom_alias($param);
1050 # now try to verify all parameters
1053 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1054 if (!PVE
::QemuServer
::option_exists
($opt)) {
1055 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1058 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1059 "-revert $opt' at the same time" })
1060 if defined($param->{$opt});
1062 $revert->{$opt} = 1;
1066 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1067 $opt = 'ide2' if $opt eq 'cdrom';
1069 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1070 "-delete $opt' at the same time" })
1071 if defined($param->{$opt});
1073 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1074 "-revert $opt' at the same time" })
1077 if (!PVE
::QemuServer
::option_exists
($opt)) {
1078 raise_param_exc
({ delete => "unknown option '$opt'" });
1084 my $repl_conf = PVE
::ReplicationConfig-
>new();
1085 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1086 my $check_replication = sub {
1088 return if !$is_replicated;
1089 my $volid = $drive->{file
};
1090 return if !$volid || !($drive->{replicate
}//1);
1091 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1092 my ($storeid, $format);
1093 if ($volid =~ $NEW_DISK_RE) {
1095 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1097 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1098 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1100 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1101 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1102 return if $scfg->{shared
};
1103 die "cannot add non-replicatable volume to a replicated VM\n";
1106 foreach my $opt (keys %$param) {
1107 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1108 # cleanup drive path
1109 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1110 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1111 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1112 $check_replication->($drive);
1113 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1114 } elsif ($opt =~ m/^net(\d+)$/) {
1116 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1117 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1118 } elsif ($opt eq 'vmgenid') {
1119 if ($param->{$opt} eq '1') {
1120 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1122 } elsif ($opt eq 'hookscript') {
1123 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1124 raise_param_exc
({ $opt => $@ }) if $@;
1128 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1130 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1132 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1134 my $updatefn = sub {
1136 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1138 die "checksum missmatch (file change by other user?)\n"
1139 if $digest && $digest ne $conf->{digest
};
1141 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1143 foreach my $opt (keys %$revert) {
1144 if (defined($conf->{$opt})) {
1145 $param->{$opt} = $conf->{$opt};
1146 } elsif (defined($conf->{pending
}->{$opt})) {
1151 if ($param->{memory
} || defined($param->{balloon
})) {
1152 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1153 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1155 die "balloon value too large (must be smaller than assigned memory)\n"
1156 if $balloon && $balloon > $maxmem;
1159 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1163 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1165 # write updates to pending section
1167 my $modified = {}; # record what $option we modify
1169 foreach my $opt (@delete) {
1170 $modified->{$opt} = 1;
1171 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1172 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1173 warn "cannot delete '$opt' - not set in current configuration!\n";
1174 $modified->{$opt} = 0;
1178 if ($opt =~ m/^unused/) {
1179 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1180 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1181 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1182 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1183 delete $conf->{$opt};
1184 PVE
::QemuConfig-
>write_config($vmid, $conf);
1186 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1187 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1188 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1189 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1190 if defined($conf->{pending
}->{$opt});
1191 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1192 PVE
::QemuConfig-
>write_config($vmid, $conf);
1194 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1195 PVE
::QemuConfig-
>write_config($vmid, $conf);
1199 foreach my $opt (keys %$param) { # add/change
1200 $modified->{$opt} = 1;
1201 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1202 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1204 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1206 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1207 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1208 # FIXME: cloudinit: CDROM or Disk?
1209 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1210 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1212 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1214 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1215 if defined($conf->{pending
}->{$opt});
1217 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1219 $conf->{pending
}->{$opt} = $param->{$opt};
1221 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1222 PVE
::QemuConfig-
>write_config($vmid, $conf);
1225 # remove pending changes when nothing changed
1226 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1227 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1228 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1230 return if !scalar(keys %{$conf->{pending
}});
1232 my $running = PVE
::QemuServer
::check_running
($vmid);
1234 # apply pending changes
1236 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1240 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1241 raise_param_exc
($errors) if scalar(keys %$errors);
1243 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1253 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1255 if ($background_delay) {
1257 # Note: It would be better to do that in the Event based HTTPServer
1258 # to avoid blocking call to sleep.
1260 my $end_time = time() + $background_delay;
1262 my $task = PVE
::Tools
::upid_decode
($upid);
1265 while (time() < $end_time) {
1266 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1268 sleep(1); # this gets interrupted when child process ends
1272 my $status = PVE
::Tools
::upid_read_status
($upid);
1273 return undef if $status eq 'OK';
1282 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1285 my $vm_config_perm_list = [
1290 'VM.Config.Network',
1292 'VM.Config.Options',
1295 __PACKAGE__-
>register_method({
1296 name
=> 'update_vm_async',
1297 path
=> '{vmid}/config',
1301 description
=> "Set virtual machine options (asynchrounous API).",
1303 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1306 additionalProperties
=> 0,
1307 properties
=> PVE
::QemuServer
::json_config_properties
(
1309 node
=> get_standard_option
('pve-node'),
1310 vmid
=> get_standard_option
('pve-vmid'),
1311 skiplock
=> get_standard_option
('skiplock'),
1313 type
=> 'string', format
=> 'pve-configid-list',
1314 description
=> "A list of settings you want to delete.",
1318 type
=> 'string', format
=> 'pve-configid-list',
1319 description
=> "Revert a pending change.",
1324 description
=> $opt_force_description,
1326 requires
=> 'delete',
1330 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1334 background_delay
=> {
1336 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1347 code
=> $update_vm_api,
1350 __PACKAGE__-
>register_method({
1351 name
=> 'update_vm',
1352 path
=> '{vmid}/config',
1356 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1358 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1361 additionalProperties
=> 0,
1362 properties
=> PVE
::QemuServer
::json_config_properties
(
1364 node
=> get_standard_option
('pve-node'),
1365 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1366 skiplock
=> get_standard_option
('skiplock'),
1368 type
=> 'string', format
=> 'pve-configid-list',
1369 description
=> "A list of settings you want to delete.",
1373 type
=> 'string', format
=> 'pve-configid-list',
1374 description
=> "Revert a pending change.",
1379 description
=> $opt_force_description,
1381 requires
=> 'delete',
1385 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1391 returns
=> { type
=> 'null' },
1394 &$update_vm_api($param, 1);
1400 __PACKAGE__-
>register_method({
1401 name
=> 'destroy_vm',
1406 description
=> "Destroy the vm (also delete all used/owned volumes).",
1408 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1411 additionalProperties
=> 0,
1413 node
=> get_standard_option
('pve-node'),
1414 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1415 skiplock
=> get_standard_option
('skiplock'),
1424 my $rpcenv = PVE
::RPCEnvironment
::get
();
1426 my $authuser = $rpcenv->get_user();
1428 my $vmid = $param->{vmid
};
1430 my $skiplock = $param->{skiplock
};
1431 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1432 if $skiplock && $authuser ne 'root@pam';
1435 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1437 my $storecfg = PVE
::Storage
::config
();
1439 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1441 die "unable to remove VM $vmid - used in HA resources\n"
1442 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1444 # do not allow destroy if there are replication jobs
1445 my $repl_conf = PVE
::ReplicationConfig-
>new();
1446 $repl_conf->check_for_existing_jobs($vmid);
1448 # early tests (repeat after locking)
1449 die "VM $vmid is running - destroy failed\n"
1450 if PVE
::QemuServer
::check_running
($vmid);
1455 syslog
('info', "destroy VM $vmid: $upid\n");
1457 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1459 PVE
::AccessControl
::remove_vm_access
($vmid);
1461 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1464 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1467 __PACKAGE__-
>register_method({
1469 path
=> '{vmid}/unlink',
1473 description
=> "Unlink/delete disk images.",
1475 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1478 additionalProperties
=> 0,
1480 node
=> get_standard_option
('pve-node'),
1481 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1483 type
=> 'string', format
=> 'pve-configid-list',
1484 description
=> "A list of disk IDs you want to delete.",
1488 description
=> $opt_force_description,
1493 returns
=> { type
=> 'null'},
1497 $param->{delete} = extract_param
($param, 'idlist');
1499 __PACKAGE__-
>update_vm($param);
1506 __PACKAGE__-
>register_method({
1508 path
=> '{vmid}/vncproxy',
1512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1514 description
=> "Creates a TCP VNC proxy connections.",
1516 additionalProperties
=> 0,
1518 node
=> get_standard_option
('pve-node'),
1519 vmid
=> get_standard_option
('pve-vmid'),
1523 description
=> "starts websockify instead of vncproxy",
1528 additionalProperties
=> 0,
1530 user
=> { type
=> 'string' },
1531 ticket
=> { type
=> 'string' },
1532 cert
=> { type
=> 'string' },
1533 port
=> { type
=> 'integer' },
1534 upid
=> { type
=> 'string' },
1540 my $rpcenv = PVE
::RPCEnvironment
::get
();
1542 my $authuser = $rpcenv->get_user();
1544 my $vmid = $param->{vmid
};
1545 my $node = $param->{node
};
1546 my $websocket = $param->{websocket
};
1548 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1549 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1551 my $authpath = "/vms/$vmid";
1553 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1555 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1561 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1562 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1563 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1564 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1565 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1567 $family = PVE
::Tools
::get_host_address_family
($node);
1570 my $port = PVE
::Tools
::next_vnc_port
($family);
1577 syslog
('info', "starting vnc proxy $upid\n");
1583 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1585 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1586 '-timeout', $timeout, '-authpath', $authpath,
1587 '-perm', 'Sys.Console'];
1589 if ($param->{websocket
}) {
1590 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1591 push @$cmd, '-notls', '-listen', 'localhost';
1594 push @$cmd, '-c', @$remcmd, @$termcmd;
1596 PVE
::Tools
::run_command
($cmd);
1600 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1602 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1604 my $sock = IO
::Socket
::IP-
>new(
1609 GetAddrInfoFlags
=> 0,
1610 ) or die "failed to create socket: $!\n";
1611 # Inside the worker we shouldn't have any previous alarms
1612 # running anyway...:
1614 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1616 accept(my $cli, $sock) or die "connection failed: $!\n";
1619 if (PVE
::Tools
::run_command
($cmd,
1620 output
=> '>&'.fileno($cli),
1621 input
=> '<&'.fileno($cli),
1624 die "Failed to run vncproxy.\n";
1631 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1633 PVE
::Tools
::wait_for_vnc_port
($port);
1644 __PACKAGE__-
>register_method({
1645 name
=> 'termproxy',
1646 path
=> '{vmid}/termproxy',
1650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1652 description
=> "Creates a TCP proxy connections.",
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid'),
1661 enum
=> [qw(serial0 serial1 serial2 serial3)],
1662 description
=> "opens a serial terminal (defaults to display)",
1667 additionalProperties
=> 0,
1669 user
=> { type
=> 'string' },
1670 ticket
=> { type
=> 'string' },
1671 port
=> { type
=> 'integer' },
1672 upid
=> { type
=> 'string' },
1678 my $rpcenv = PVE
::RPCEnvironment
::get
();
1680 my $authuser = $rpcenv->get_user();
1682 my $vmid = $param->{vmid
};
1683 my $node = $param->{node
};
1684 my $serial = $param->{serial
};
1686 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1688 if (!defined($serial)) {
1689 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1690 $serial = $conf->{vga
};
1694 my $authpath = "/vms/$vmid";
1696 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1701 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1702 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1703 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1704 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1705 push @$remcmd, '--';
1707 $family = PVE
::Tools
::get_host_address_family
($node);
1710 my $port = PVE
::Tools
::next_vnc_port
($family);
1712 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1713 push @$termcmd, '-iface', $serial if $serial;
1718 syslog
('info', "starting qemu termproxy $upid\n");
1720 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1721 '--perm', 'VM.Console', '--'];
1722 push @$cmd, @$remcmd, @$termcmd;
1724 PVE
::Tools
::run_command
($cmd);
1727 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1729 PVE
::Tools
::wait_for_vnc_port
($port);
1739 __PACKAGE__-
>register_method({
1740 name
=> 'vncwebsocket',
1741 path
=> '{vmid}/vncwebsocket',
1744 description
=> "You also need to pass a valid ticket (vncticket).",
1745 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1747 description
=> "Opens a weksocket for VNC traffic.",
1749 additionalProperties
=> 0,
1751 node
=> get_standard_option
('pve-node'),
1752 vmid
=> get_standard_option
('pve-vmid'),
1754 description
=> "Ticket from previous call to vncproxy.",
1759 description
=> "Port number returned by previous vncproxy call.",
1769 port
=> { type
=> 'string' },
1775 my $rpcenv = PVE
::RPCEnvironment
::get
();
1777 my $authuser = $rpcenv->get_user();
1779 my $vmid = $param->{vmid
};
1780 my $node = $param->{node
};
1782 my $authpath = "/vms/$vmid";
1784 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1786 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1788 # Note: VNC ports are acessible from outside, so we do not gain any
1789 # security if we verify that $param->{port} belongs to VM $vmid. This
1790 # check is done by verifying the VNC ticket (inside VNC protocol).
1792 my $port = $param->{port
};
1794 return { port
=> $port };
1797 __PACKAGE__-
>register_method({
1798 name
=> 'spiceproxy',
1799 path
=> '{vmid}/spiceproxy',
1804 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1806 description
=> "Returns a SPICE configuration to connect to the VM.",
1808 additionalProperties
=> 0,
1810 node
=> get_standard_option
('pve-node'),
1811 vmid
=> get_standard_option
('pve-vmid'),
1812 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1815 returns
=> get_standard_option
('remote-viewer-config'),
1819 my $rpcenv = PVE
::RPCEnvironment
::get
();
1821 my $authuser = $rpcenv->get_user();
1823 my $vmid = $param->{vmid
};
1824 my $node = $param->{node
};
1825 my $proxy = $param->{proxy
};
1827 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1828 my $title = "VM $vmid";
1829 $title .= " - ". $conf->{name
} if $conf->{name
};
1831 my $port = PVE
::QemuServer
::spice_port
($vmid);
1833 my ($ticket, undef, $remote_viewer_config) =
1834 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1836 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1837 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1839 return $remote_viewer_config;
1842 __PACKAGE__-
>register_method({
1844 path
=> '{vmid}/status',
1847 description
=> "Directory index",
1852 additionalProperties
=> 0,
1854 node
=> get_standard_option
('pve-node'),
1855 vmid
=> get_standard_option
('pve-vmid'),
1863 subdir
=> { type
=> 'string' },
1866 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1872 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1875 { subdir
=> 'current' },
1876 { subdir
=> 'start' },
1877 { subdir
=> 'stop' },
1883 __PACKAGE__-
>register_method({
1884 name
=> 'vm_status',
1885 path
=> '{vmid}/status/current',
1888 protected
=> 1, # qemu pid files are only readable by root
1889 description
=> "Get virtual machine status.",
1891 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1894 additionalProperties
=> 0,
1896 node
=> get_standard_option
('pve-node'),
1897 vmid
=> get_standard_option
('pve-vmid'),
1903 %$PVE::QemuServer
::vmstatus_return_properties
,
1905 description
=> "HA manager service status.",
1909 description
=> "Qemu VGA configuration supports spice.",
1914 description
=> "Qemu GuestAgent enabled in config.",
1924 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1926 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1927 my $status = $vmstatus->{$param->{vmid
}};
1929 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1931 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1932 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1937 __PACKAGE__-
>register_method({
1939 path
=> '{vmid}/status/start',
1943 description
=> "Start virtual machine.",
1945 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1948 additionalProperties
=> 0,
1950 node
=> get_standard_option
('pve-node'),
1951 vmid
=> get_standard_option
('pve-vmid',
1952 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1953 skiplock
=> get_standard_option
('skiplock'),
1954 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1955 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1958 enum
=> ['secure', 'insecure'],
1959 description
=> "Migration traffic is encrypted using an SSH " .
1960 "tunnel by default. On secure, completely private networks " .
1961 "this can be disabled to increase performance.",
1964 migration_network
=> {
1965 type
=> 'string', format
=> 'CIDR',
1966 description
=> "CIDR of the (sub) network that is used for migration.",
1969 machine
=> get_standard_option
('pve-qm-machine'),
1971 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1983 my $rpcenv = PVE
::RPCEnvironment
::get
();
1985 my $authuser = $rpcenv->get_user();
1987 my $node = extract_param
($param, 'node');
1989 my $vmid = extract_param
($param, 'vmid');
1991 my $machine = extract_param
($param, 'machine');
1993 my $stateuri = extract_param
($param, 'stateuri');
1994 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1995 if $stateuri && $authuser ne 'root@pam';
1997 my $skiplock = extract_param
($param, 'skiplock');
1998 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1999 if $skiplock && $authuser ne 'root@pam';
2001 my $migratedfrom = extract_param
($param, 'migratedfrom');
2002 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2003 if $migratedfrom && $authuser ne 'root@pam';
2005 my $migration_type = extract_param
($param, 'migration_type');
2006 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2007 if $migration_type && $authuser ne 'root@pam';
2009 my $migration_network = extract_param
($param, 'migration_network');
2010 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2011 if $migration_network && $authuser ne 'root@pam';
2013 my $targetstorage = extract_param
($param, 'targetstorage');
2014 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2015 if $targetstorage && $authuser ne 'root@pam';
2017 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2018 if $targetstorage && !$migratedfrom;
2020 # read spice ticket from STDIN
2022 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2023 if (defined(my $line = <STDIN
>)) {
2025 $spice_ticket = $line;
2029 PVE
::Cluster
::check_cfs_quorum
();
2031 my $storecfg = PVE
::Storage
::config
();
2033 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2034 $rpcenv->{type
} ne 'ha') {
2039 my $service = "vm:$vmid";
2041 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2043 print "Requesting HA start for VM $vmid\n";
2045 PVE
::Tools
::run_command
($cmd);
2050 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2057 syslog
('info', "start VM $vmid: $upid\n");
2059 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2060 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2065 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2069 __PACKAGE__-
>register_method({
2071 path
=> '{vmid}/status/stop',
2075 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2076 "is akin to pulling the power plug of a running computer and may damage the VM data",
2078 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid',
2085 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2086 skiplock
=> get_standard_option
('skiplock'),
2087 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2089 description
=> "Wait maximal timeout seconds.",
2095 description
=> "Do not deactivate storage volumes.",
2108 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $node = extract_param
($param, 'node');
2114 my $vmid = extract_param
($param, 'vmid');
2116 my $skiplock = extract_param
($param, 'skiplock');
2117 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2118 if $skiplock && $authuser ne 'root@pam';
2120 my $keepActive = extract_param
($param, 'keepActive');
2121 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2122 if $keepActive && $authuser ne 'root@pam';
2124 my $migratedfrom = extract_param
($param, 'migratedfrom');
2125 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2126 if $migratedfrom && $authuser ne 'root@pam';
2129 my $storecfg = PVE
::Storage
::config
();
2131 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2136 my $service = "vm:$vmid";
2138 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2140 print "Requesting HA stop for VM $vmid\n";
2142 PVE
::Tools
::run_command
($cmd);
2147 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2153 syslog
('info', "stop VM $vmid: $upid\n");
2155 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2156 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2161 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2165 __PACKAGE__-
>register_method({
2167 path
=> '{vmid}/status/reset',
2171 description
=> "Reset virtual machine.",
2173 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2176 additionalProperties
=> 0,
2178 node
=> get_standard_option
('pve-node'),
2179 vmid
=> get_standard_option
('pve-vmid',
2180 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2181 skiplock
=> get_standard_option
('skiplock'),
2190 my $rpcenv = PVE
::RPCEnvironment
::get
();
2192 my $authuser = $rpcenv->get_user();
2194 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2207 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2212 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2215 __PACKAGE__-
>register_method({
2216 name
=> 'vm_shutdown',
2217 path
=> '{vmid}/status/shutdown',
2221 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2222 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2224 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2227 additionalProperties
=> 0,
2229 node
=> get_standard_option
('pve-node'),
2230 vmid
=> get_standard_option
('pve-vmid',
2231 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2232 skiplock
=> get_standard_option
('skiplock'),
2234 description
=> "Wait maximal timeout seconds.",
2240 description
=> "Make sure the VM stops.",
2246 description
=> "Do not deactivate storage volumes.",
2259 my $rpcenv = PVE
::RPCEnvironment
::get
();
2261 my $authuser = $rpcenv->get_user();
2263 my $node = extract_param
($param, 'node');
2265 my $vmid = extract_param
($param, 'vmid');
2267 my $skiplock = extract_param
($param, 'skiplock');
2268 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2269 if $skiplock && $authuser ne 'root@pam';
2271 my $keepActive = extract_param
($param, 'keepActive');
2272 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2273 if $keepActive && $authuser ne 'root@pam';
2275 my $storecfg = PVE
::Storage
::config
();
2279 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2280 # otherwise, we will infer a shutdown command, but run into the timeout,
2281 # then when the vm is resumed, it will instantly shutdown
2283 # checking the qmp status here to get feedback to the gui/cli/api
2284 # and the status query should not take too long
2287 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2291 if (!$err && $qmpstatus->{status
} eq "paused") {
2292 if ($param->{forceStop
}) {
2293 warn "VM is paused - stop instead of shutdown\n";
2296 die "VM is paused - cannot shutdown\n";
2300 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2301 ($rpcenv->{type
} ne 'ha')) {
2306 my $service = "vm:$vmid";
2308 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2310 print "Requesting HA stop for VM $vmid\n";
2312 PVE
::Tools
::run_command
($cmd);
2317 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2324 syslog
('info', "shutdown VM $vmid: $upid\n");
2326 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2327 $shutdown, $param->{forceStop
}, $keepActive);
2332 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2336 __PACKAGE__-
>register_method({
2337 name
=> 'vm_suspend',
2338 path
=> '{vmid}/status/suspend',
2342 description
=> "Suspend virtual machine.",
2344 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2347 additionalProperties
=> 0,
2349 node
=> get_standard_option
('pve-node'),
2350 vmid
=> get_standard_option
('pve-vmid',
2351 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2352 skiplock
=> get_standard_option
('skiplock'),
2357 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2359 statestorage
=> get_standard_option
('pve-storage-id', {
2360 description
=> "The storage for the VM state",
2361 requires
=> 'todisk',
2363 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2373 my $rpcenv = PVE
::RPCEnvironment
::get
();
2375 my $authuser = $rpcenv->get_user();
2377 my $node = extract_param
($param, 'node');
2379 my $vmid = extract_param
($param, 'vmid');
2381 my $todisk = extract_param
($param, 'todisk') // 0;
2383 my $statestorage = extract_param
($param, 'statestorage');
2385 my $skiplock = extract_param
($param, 'skiplock');
2386 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2387 if $skiplock && $authuser ne 'root@pam';
2389 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2391 die "Cannot suspend HA managed VM to disk\n"
2392 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2394 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2399 syslog
('info', "suspend VM $vmid: $upid\n");
2401 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2406 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2409 __PACKAGE__-
>register_method({
2410 name
=> 'vm_resume',
2411 path
=> '{vmid}/status/resume',
2415 description
=> "Resume virtual machine.",
2417 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2420 additionalProperties
=> 0,
2422 node
=> get_standard_option
('pve-node'),
2423 vmid
=> get_standard_option
('pve-vmid',
2424 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2425 skiplock
=> get_standard_option
('skiplock'),
2426 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2438 my $authuser = $rpcenv->get_user();
2440 my $node = extract_param
($param, 'node');
2442 my $vmid = extract_param
($param, 'vmid');
2444 my $skiplock = extract_param
($param, 'skiplock');
2445 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2446 if $skiplock && $authuser ne 'root@pam';
2448 my $nocheck = extract_param
($param, 'nocheck');
2450 my $to_disk_suspended;
2452 PVE
::QemuConfig-
>lock_config($vmid, sub {
2453 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2454 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2458 die "VM $vmid not running\n"
2459 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2464 syslog
('info', "resume VM $vmid: $upid\n");
2466 if (!$to_disk_suspended) {
2467 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2469 my $storecfg = PVE
::Storage
::config
();
2470 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2476 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2479 __PACKAGE__-
>register_method({
2480 name
=> 'vm_sendkey',
2481 path
=> '{vmid}/sendkey',
2485 description
=> "Send key event to virtual machine.",
2487 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2490 additionalProperties
=> 0,
2492 node
=> get_standard_option
('pve-node'),
2493 vmid
=> get_standard_option
('pve-vmid',
2494 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2495 skiplock
=> get_standard_option
('skiplock'),
2497 description
=> "The key (qemu monitor encoding).",
2502 returns
=> { type
=> 'null'},
2506 my $rpcenv = PVE
::RPCEnvironment
::get
();
2508 my $authuser = $rpcenv->get_user();
2510 my $node = extract_param
($param, 'node');
2512 my $vmid = extract_param
($param, 'vmid');
2514 my $skiplock = extract_param
($param, 'skiplock');
2515 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2516 if $skiplock && $authuser ne 'root@pam';
2518 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2523 __PACKAGE__-
>register_method({
2524 name
=> 'vm_feature',
2525 path
=> '{vmid}/feature',
2529 description
=> "Check if feature for virtual machine is available.",
2531 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2534 additionalProperties
=> 0,
2536 node
=> get_standard_option
('pve-node'),
2537 vmid
=> get_standard_option
('pve-vmid'),
2539 description
=> "Feature to check.",
2541 enum
=> [ 'snapshot', 'clone', 'copy' ],
2543 snapname
=> get_standard_option
('pve-snapshot-name', {
2551 hasFeature
=> { type
=> 'boolean' },
2554 items
=> { type
=> 'string' },
2561 my $node = extract_param
($param, 'node');
2563 my $vmid = extract_param
($param, 'vmid');
2565 my $snapname = extract_param
($param, 'snapname');
2567 my $feature = extract_param
($param, 'feature');
2569 my $running = PVE
::QemuServer
::check_running
($vmid);
2571 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2574 my $snap = $conf->{snapshots
}->{$snapname};
2575 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2578 my $storecfg = PVE
::Storage
::config
();
2580 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2581 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2584 hasFeature
=> $hasFeature,
2585 nodes
=> [ keys %$nodelist ],
2589 __PACKAGE__-
>register_method({
2591 path
=> '{vmid}/clone',
2595 description
=> "Create a copy of virtual machine/template.",
2597 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2598 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2599 "'Datastore.AllocateSpace' on any used storage.",
2602 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2604 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2605 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2610 additionalProperties
=> 0,
2612 node
=> get_standard_option
('pve-node'),
2613 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2614 newid
=> get_standard_option
('pve-vmid', {
2615 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2616 description
=> 'VMID for the clone.' }),
2619 type
=> 'string', format
=> 'dns-name',
2620 description
=> "Set a name for the new VM.",
2625 description
=> "Description for the new VM.",
2629 type
=> 'string', format
=> 'pve-poolid',
2630 description
=> "Add the new VM to the specified pool.",
2632 snapname
=> get_standard_option
('pve-snapshot-name', {
2635 storage
=> get_standard_option
('pve-storage-id', {
2636 description
=> "Target storage for full clone.",
2640 description
=> "Target format for file storage. Only valid for full clone.",
2643 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2648 description
=> "Create a full copy of all disks. This is always done when " .
2649 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2651 target
=> get_standard_option
('pve-node', {
2652 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2656 description
=> "Override I/O bandwidth limit (in KiB/s).",
2660 default => 'clone limit from datacenter.cfg/storage.cfg',
2670 my $rpcenv = PVE
::RPCEnvironment
::get
();
2672 my $authuser = $rpcenv->get_user();
2674 my $node = extract_param
($param, 'node');
2676 my $vmid = extract_param
($param, 'vmid');
2678 my $newid = extract_param
($param, 'newid');
2680 my $pool = extract_param
($param, 'pool');
2682 if (defined($pool)) {
2683 $rpcenv->check_pool_exist($pool);
2686 my $snapname = extract_param
($param, 'snapname');
2688 my $storage = extract_param
($param, 'storage');
2690 my $format = extract_param
($param, 'format');
2692 my $target = extract_param
($param, 'target');
2694 my $localnode = PVE
::INotify
::nodename
();
2696 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2698 PVE
::Cluster
::check_node_exists
($target) if $target;
2700 my $storecfg = PVE
::Storage
::config
();
2703 # check if storage is enabled on local node
2704 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2706 # check if storage is available on target node
2707 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2708 # clone only works if target storage is shared
2709 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2710 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2714 PVE
::Cluster
::check_cfs_quorum
();
2716 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2718 # exclusive lock if VM is running - else shared lock is enough;
2719 my $shared_lock = $running ?
0 : 1;
2723 # do all tests after lock
2724 # we also try to do all tests before we fork the worker
2726 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2728 PVE
::QemuConfig-
>check_lock($conf);
2730 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2732 die "unexpected state change\n" if $verify_running != $running;
2734 die "snapshot '$snapname' does not exist\n"
2735 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2737 my $full = extract_param
($param, 'full');
2738 if (!defined($full)) {
2739 $full = !PVE
::QemuConfig-
>is_template($conf);
2742 die "parameter 'storage' not allowed for linked clones\n"
2743 if defined($storage) && !$full;
2745 die "parameter 'format' not allowed for linked clones\n"
2746 if defined($format) && !$full;
2748 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2750 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2752 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2754 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2756 die "unable to create VM $newid: config file already exists\n"
2759 my $newconf = { lock => 'clone' };
2764 foreach my $opt (keys %$oldconf) {
2765 my $value = $oldconf->{$opt};
2767 # do not copy snapshot related info
2768 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2769 $opt eq 'vmstate' || $opt eq 'snapstate';
2771 # no need to copy unused images, because VMID(owner) changes anyways
2772 next if $opt =~ m/^unused\d+$/;
2774 # always change MAC! address
2775 if ($opt =~ m/^net(\d+)$/) {
2776 my $net = PVE
::QemuServer
::parse_net
($value);
2777 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2778 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2779 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2780 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2781 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2782 die "unable to parse drive options for '$opt'\n" if !$drive;
2783 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2784 $newconf->{$opt} = $value; # simply copy configuration
2786 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2787 die "Full clone feature is not supported for drive '$opt'\n"
2788 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2789 $fullclone->{$opt} = 1;
2791 # not full means clone instead of copy
2792 die "Linked clone feature is not supported for drive '$opt'\n"
2793 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2795 $drives->{$opt} = $drive;
2796 push @$vollist, $drive->{file
};
2799 # copy everything else
2800 $newconf->{$opt} = $value;
2804 # auto generate a new uuid
2805 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2806 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2807 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2809 # auto generate a new vmgenid if the option was set
2810 if ($newconf->{vmgenid
}) {
2811 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2814 delete $newconf->{template
};
2816 if ($param->{name
}) {
2817 $newconf->{name
} = $param->{name
};
2819 if ($oldconf->{name
}) {
2820 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2822 $newconf->{name
} = "Copy-of-VM-$vmid";
2826 if ($param->{description
}) {
2827 $newconf->{description
} = $param->{description
};
2830 # create empty/temp config - this fails if VM already exists on other node
2831 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2836 my $newvollist = [];
2843 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2845 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2847 my $bwlimit = extract_param
($param, 'bwlimit');
2849 my $total_jobs = scalar(keys %{$drives});
2852 foreach my $opt (keys %$drives) {
2853 my $drive = $drives->{$opt};
2854 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2856 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2857 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$src_sid, $storage], $bwlimit);
2859 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2860 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2861 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2863 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2865 PVE
::QemuConfig-
>write_config($newid, $newconf);
2869 delete $newconf->{lock};
2871 # do not write pending changes
2872 if (my @changes = keys %{$newconf->{pending
}}) {
2873 my $pending = join(',', @changes);
2874 warn "found pending changes for '$pending', discarding for clone\n";
2875 delete $newconf->{pending
};
2878 PVE
::QemuConfig-
>write_config($newid, $newconf);
2881 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2882 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2883 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2885 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2886 die "Failed to move config to node '$target' - rename failed: $!\n"
2887 if !rename($conffile, $newconffile);
2890 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2895 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2897 sleep 1; # some storage like rbd need to wait before release volume - really?
2899 foreach my $volid (@$newvollist) {
2900 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2903 die "clone failed: $err";
2909 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2911 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2914 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2915 # Aquire exclusive lock lock for $newid
2916 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2921 __PACKAGE__-
>register_method({
2922 name
=> 'move_vm_disk',
2923 path
=> '{vmid}/move_disk',
2927 description
=> "Move volume to different storage.",
2929 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2931 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2932 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2936 additionalProperties
=> 0,
2938 node
=> get_standard_option
('pve-node'),
2939 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2942 description
=> "The disk you want to move.",
2943 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2945 storage
=> get_standard_option
('pve-storage-id', {
2946 description
=> "Target storage.",
2947 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2951 description
=> "Target Format.",
2952 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2957 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2963 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2968 description
=> "Override I/O bandwidth limit (in KiB/s).",
2972 default => 'move limit from datacenter.cfg/storage.cfg',
2978 description
=> "the task ID.",
2983 my $rpcenv = PVE
::RPCEnvironment
::get
();
2985 my $authuser = $rpcenv->get_user();
2987 my $node = extract_param
($param, 'node');
2989 my $vmid = extract_param
($param, 'vmid');
2991 my $digest = extract_param
($param, 'digest');
2993 my $disk = extract_param
($param, 'disk');
2995 my $storeid = extract_param
($param, 'storage');
2997 my $format = extract_param
($param, 'format');
2999 my $storecfg = PVE
::Storage
::config
();
3001 my $updatefn = sub {
3003 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3005 PVE
::QemuConfig-
>check_lock($conf);
3007 die "checksum missmatch (file change by other user?)\n"
3008 if $digest && $digest ne $conf->{digest
};
3010 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3012 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3014 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3016 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3019 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3020 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3024 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3025 (!$format || !$oldfmt || $oldfmt eq $format);
3027 # this only checks snapshots because $disk is passed!
3028 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3029 die "you can't move a disk with snapshots and delete the source\n"
3030 if $snapshotted && $param->{delete};
3032 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3034 my $running = PVE
::QemuServer
::check_running
($vmid);
3036 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3040 my $newvollist = [];
3046 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3048 warn "moving disk with snapshots, snapshots will not be moved!\n"
3051 my $bwlimit = extract_param
($param, 'bwlimit');
3052 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3054 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3055 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3057 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3059 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3061 # convert moved disk to base if part of template
3062 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3063 if PVE
::QemuConfig-
>is_template($conf);
3065 PVE
::QemuConfig-
>write_config($vmid, $conf);
3067 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3068 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3072 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3073 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3080 foreach my $volid (@$newvollist) {
3081 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3084 die "storage migration failed: $err";
3087 if ($param->{delete}) {
3089 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3090 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3096 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3099 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3102 __PACKAGE__-
>register_method({
3103 name
=> 'migrate_vm',
3104 path
=> '{vmid}/migrate',
3108 description
=> "Migrate virtual machine. Creates a new migration task.",
3110 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3113 additionalProperties
=> 0,
3115 node
=> get_standard_option
('pve-node'),
3116 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3117 target
=> get_standard_option
('pve-node', {
3118 description
=> "Target node.",
3119 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3123 description
=> "Use online/live migration.",
3128 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3133 enum
=> ['secure', 'insecure'],
3134 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3137 migration_network
=> {
3138 type
=> 'string', format
=> 'CIDR',
3139 description
=> "CIDR of the (sub) network that is used for migration.",
3142 "with-local-disks" => {
3144 description
=> "Enable live storage migration for local disk",
3147 targetstorage
=> get_standard_option
('pve-storage-id', {
3148 description
=> "Default target storage.",
3150 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3153 description
=> "Override I/O bandwidth limit (in KiB/s).",
3157 default => 'migrate limit from datacenter.cfg/storage.cfg',
3163 description
=> "the task ID.",
3168 my $rpcenv = PVE
::RPCEnvironment
::get
();
3170 my $authuser = $rpcenv->get_user();
3172 my $target = extract_param
($param, 'target');
3174 my $localnode = PVE
::INotify
::nodename
();
3175 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3177 PVE
::Cluster
::check_cfs_quorum
();
3179 PVE
::Cluster
::check_node_exists
($target);
3181 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3183 my $vmid = extract_param
($param, 'vmid');
3185 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3186 if !$param->{online
} && $param->{targetstorage
};
3188 raise_param_exc
({ force
=> "Only root may use this option." })
3189 if $param->{force
} && $authuser ne 'root@pam';
3191 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3192 if $param->{migration_type
} && $authuser ne 'root@pam';
3194 # allow root only until better network permissions are available
3195 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3196 if $param->{migration_network
} && $authuser ne 'root@pam';
3199 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3201 # try to detect errors early
3203 PVE
::QemuConfig-
>check_lock($conf);
3205 if (PVE
::QemuServer
::check_running
($vmid)) {
3206 die "cant migrate running VM without --online\n"
3207 if !$param->{online
};
3210 my $storecfg = PVE
::Storage
::config
();
3212 if( $param->{targetstorage
}) {
3213 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3215 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3218 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3223 my $service = "vm:$vmid";
3225 my $cmd = ['ha-manager', 'migrate', $service, $target];
3227 print "Requesting HA migration for VM $vmid to node $target\n";
3229 PVE
::Tools
::run_command
($cmd);
3234 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3239 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3243 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3246 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3251 __PACKAGE__-
>register_method({
3253 path
=> '{vmid}/monitor',
3257 description
=> "Execute Qemu monitor commands.",
3259 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3263 additionalProperties
=> 0,
3265 node
=> get_standard_option
('pve-node'),
3266 vmid
=> get_standard_option
('pve-vmid'),
3269 description
=> "The monitor command.",
3273 returns
=> { type
=> 'string'},
3277 my $rpcenv = PVE
::RPCEnvironment
::get
();
3278 my $authuser = $rpcenv->get_user();
3281 my $command = shift;
3282 return $command =~ m/^\s*info(\s+|$)/
3283 || $command =~ m/^\s*help\s*$/;
3286 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3287 if !&$is_ro($param->{command
});
3289 my $vmid = $param->{vmid
};
3291 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3295 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3297 $res = "ERROR: $@" if $@;
3302 __PACKAGE__-
>register_method({
3303 name
=> 'resize_vm',
3304 path
=> '{vmid}/resize',
3308 description
=> "Extend volume size.",
3310 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3313 additionalProperties
=> 0,
3315 node
=> get_standard_option
('pve-node'),
3316 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3317 skiplock
=> get_standard_option
('skiplock'),
3320 description
=> "The disk you want to resize.",
3321 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3325 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3326 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.",
3330 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3336 returns
=> { type
=> 'null'},
3340 my $rpcenv = PVE
::RPCEnvironment
::get
();
3342 my $authuser = $rpcenv->get_user();
3344 my $node = extract_param
($param, 'node');
3346 my $vmid = extract_param
($param, 'vmid');
3348 my $digest = extract_param
($param, 'digest');
3350 my $disk = extract_param
($param, 'disk');
3352 my $sizestr = extract_param
($param, 'size');
3354 my $skiplock = extract_param
($param, 'skiplock');
3355 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3356 if $skiplock && $authuser ne 'root@pam';
3358 my $storecfg = PVE
::Storage
::config
();
3360 my $updatefn = sub {
3362 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3364 die "checksum missmatch (file change by other user?)\n"
3365 if $digest && $digest ne $conf->{digest
};
3366 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3368 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3370 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3372 my (undef, undef, undef, undef, undef, undef, $format) =
3373 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3375 die "can't resize volume: $disk if snapshot exists\n"
3376 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3378 my $volid = $drive->{file
};
3380 die "disk '$disk' has no associated volume\n" if !$volid;
3382 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3384 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3386 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3388 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3389 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3391 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3392 my ($ext, $newsize, $unit) = ($1, $2, $4);
3395 $newsize = $newsize * 1024;
3396 } elsif ($unit eq 'M') {
3397 $newsize = $newsize * 1024 * 1024;
3398 } elsif ($unit eq 'G') {
3399 $newsize = $newsize * 1024 * 1024 * 1024;
3400 } elsif ($unit eq 'T') {
3401 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3404 $newsize += $size if $ext;
3405 $newsize = int($newsize);
3407 die "shrinking disks is not supported\n" if $newsize < $size;
3409 return if $size == $newsize;
3411 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3413 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3415 $drive->{size
} = $newsize;
3416 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3418 PVE
::QemuConfig-
>write_config($vmid, $conf);
3421 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3425 __PACKAGE__-
>register_method({
3426 name
=> 'snapshot_list',
3427 path
=> '{vmid}/snapshot',
3429 description
=> "List all snapshots.",
3431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3434 protected
=> 1, # qemu pid files are only readable by root
3436 additionalProperties
=> 0,
3438 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3439 node
=> get_standard_option
('pve-node'),
3448 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3452 description
=> "Snapshot includes RAM.",
3457 description
=> "Snapshot description.",
3461 description
=> "Snapshot creation time",
3463 renderer
=> 'timestamp',
3467 description
=> "Parent snapshot identifier.",
3473 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3478 my $vmid = $param->{vmid
};
3480 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3481 my $snaphash = $conf->{snapshots
} || {};
3485 foreach my $name (keys %$snaphash) {
3486 my $d = $snaphash->{$name};
3489 snaptime
=> $d->{snaptime
} || 0,
3490 vmstate
=> $d->{vmstate
} ?
1 : 0,
3491 description
=> $d->{description
} || '',
3493 $item->{parent
} = $d->{parent
} if $d->{parent
};
3494 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3498 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3501 digest
=> $conf->{digest
},
3502 running
=> $running,
3503 description
=> "You are here!",
3505 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3507 push @$res, $current;
3512 __PACKAGE__-
>register_method({
3514 path
=> '{vmid}/snapshot',
3518 description
=> "Snapshot a VM.",
3520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3523 additionalProperties
=> 0,
3525 node
=> get_standard_option
('pve-node'),
3526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3527 snapname
=> get_standard_option
('pve-snapshot-name'),
3531 description
=> "Save the vmstate",
3536 description
=> "A textual description or comment.",
3542 description
=> "the task ID.",
3547 my $rpcenv = PVE
::RPCEnvironment
::get
();
3549 my $authuser = $rpcenv->get_user();
3551 my $node = extract_param
($param, 'node');
3553 my $vmid = extract_param
($param, 'vmid');
3555 my $snapname = extract_param
($param, 'snapname');
3557 die "unable to use snapshot name 'current' (reserved name)\n"
3558 if $snapname eq 'current';
3561 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3562 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3563 $param->{description
});
3566 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3569 __PACKAGE__-
>register_method({
3570 name
=> 'snapshot_cmd_idx',
3571 path
=> '{vmid}/snapshot/{snapname}',
3578 additionalProperties
=> 0,
3580 vmid
=> get_standard_option
('pve-vmid'),
3581 node
=> get_standard_option
('pve-node'),
3582 snapname
=> get_standard_option
('pve-snapshot-name'),
3591 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3598 push @$res, { cmd
=> 'rollback' };
3599 push @$res, { cmd
=> 'config' };
3604 __PACKAGE__-
>register_method({
3605 name
=> 'update_snapshot_config',
3606 path
=> '{vmid}/snapshot/{snapname}/config',
3610 description
=> "Update snapshot metadata.",
3612 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3615 additionalProperties
=> 0,
3617 node
=> get_standard_option
('pve-node'),
3618 vmid
=> get_standard_option
('pve-vmid'),
3619 snapname
=> get_standard_option
('pve-snapshot-name'),
3623 description
=> "A textual description or comment.",
3627 returns
=> { type
=> 'null' },
3631 my $rpcenv = PVE
::RPCEnvironment
::get
();
3633 my $authuser = $rpcenv->get_user();
3635 my $vmid = extract_param
($param, 'vmid');
3637 my $snapname = extract_param
($param, 'snapname');
3639 return undef if !defined($param->{description
});
3641 my $updatefn = sub {
3643 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3645 PVE
::QemuConfig-
>check_lock($conf);
3647 my $snap = $conf->{snapshots
}->{$snapname};
3649 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3651 $snap->{description
} = $param->{description
} if defined($param->{description
});
3653 PVE
::QemuConfig-
>write_config($vmid, $conf);
3656 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3661 __PACKAGE__-
>register_method({
3662 name
=> 'get_snapshot_config',
3663 path
=> '{vmid}/snapshot/{snapname}/config',
3666 description
=> "Get snapshot configuration",
3668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3671 additionalProperties
=> 0,
3673 node
=> get_standard_option
('pve-node'),
3674 vmid
=> get_standard_option
('pve-vmid'),
3675 snapname
=> get_standard_option
('pve-snapshot-name'),
3678 returns
=> { type
=> "object" },
3682 my $rpcenv = PVE
::RPCEnvironment
::get
();
3684 my $authuser = $rpcenv->get_user();
3686 my $vmid = extract_param
($param, 'vmid');
3688 my $snapname = extract_param
($param, 'snapname');
3690 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3692 my $snap = $conf->{snapshots
}->{$snapname};
3694 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3699 __PACKAGE__-
>register_method({
3701 path
=> '{vmid}/snapshot/{snapname}/rollback',
3705 description
=> "Rollback VM state to specified snapshot.",
3707 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3710 additionalProperties
=> 0,
3712 node
=> get_standard_option
('pve-node'),
3713 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3714 snapname
=> get_standard_option
('pve-snapshot-name'),
3719 description
=> "the task ID.",
3724 my $rpcenv = PVE
::RPCEnvironment
::get
();
3726 my $authuser = $rpcenv->get_user();
3728 my $node = extract_param
($param, 'node');
3730 my $vmid = extract_param
($param, 'vmid');
3732 my $snapname = extract_param
($param, 'snapname');
3735 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3736 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3740 # hold migration lock, this makes sure that nobody create replication snapshots
3741 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3744 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3747 __PACKAGE__-
>register_method({
3748 name
=> 'delsnapshot',
3749 path
=> '{vmid}/snapshot/{snapname}',
3753 description
=> "Delete a VM snapshot.",
3755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3758 additionalProperties
=> 0,
3760 node
=> get_standard_option
('pve-node'),
3761 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3762 snapname
=> get_standard_option
('pve-snapshot-name'),
3766 description
=> "For removal from config file, even if removing disk snapshots fails.",
3772 description
=> "the task ID.",
3777 my $rpcenv = PVE
::RPCEnvironment
::get
();
3779 my $authuser = $rpcenv->get_user();
3781 my $node = extract_param
($param, 'node');
3783 my $vmid = extract_param
($param, 'vmid');
3785 my $snapname = extract_param
($param, 'snapname');
3788 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3789 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3792 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3795 __PACKAGE__-
>register_method({
3797 path
=> '{vmid}/template',
3801 description
=> "Create a Template.",
3803 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3804 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3807 additionalProperties
=> 0,
3809 node
=> get_standard_option
('pve-node'),
3810 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3814 description
=> "If you want to convert only 1 disk to base image.",
3815 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3820 returns
=> { type
=> 'null'},
3824 my $rpcenv = PVE
::RPCEnvironment
::get
();
3826 my $authuser = $rpcenv->get_user();
3828 my $node = extract_param
($param, 'node');
3830 my $vmid = extract_param
($param, 'vmid');
3832 my $disk = extract_param
($param, 'disk');
3834 my $updatefn = sub {
3836 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3838 PVE
::QemuConfig-
>check_lock($conf);
3840 die "unable to create template, because VM contains snapshots\n"
3841 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3843 die "you can't convert a template to a template\n"
3844 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3846 die "you can't convert a VM to template if VM is running\n"
3847 if PVE
::QemuServer
::check_running
($vmid);
3850 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3853 $conf->{template
} = 1;
3854 PVE
::QemuConfig-
>write_config($vmid, $conf);
3856 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3859 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);