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 # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
160 my $cloudinit_iso_size = 5; # 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 = {
303 my $check_vm_modify_config_perm = sub {
304 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
306 return 1 if $authuser eq 'root@pam';
308 foreach my $opt (@$key_list) {
309 # disk checks need to be done somewhere else
310 next if PVE
::QemuServer
::is_valid_drivename
($opt);
311 next if $opt eq 'cdrom';
312 next if $opt =~ m/^unused\d+$/;
314 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
315 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
316 } elsif ($memoryoptions->{$opt}) {
317 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
318 } elsif ($hwtypeoptions->{$opt}) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
320 } elsif ($generaloptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
322 # special case for startup since it changes host behaviour
323 if ($opt eq 'startup') {
324 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
326 } elsif ($vmpoweroptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
328 } elsif ($diskoptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
330 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 # catches usb\d+, hostpci\d+, args, lock, etc.
334 # new options will be checked here
335 die "only root can set '$opt' config\n";
342 __PACKAGE__-
>register_method({
346 description
=> "Virtual machine index (per node).",
348 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
352 protected
=> 1, # qemu pid files are only readable by root
354 additionalProperties
=> 0,
356 node
=> get_standard_option
('pve-node'),
360 description
=> "Determine the full status of active VMs.",
368 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
370 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
375 my $rpcenv = PVE
::RPCEnvironment
::get
();
376 my $authuser = $rpcenv->get_user();
378 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
381 foreach my $vmid (keys %$vmstatus) {
382 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
384 my $data = $vmstatus->{$vmid};
393 __PACKAGE__-
>register_method({
397 description
=> "Create or restore a virtual machine.",
399 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
400 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
401 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
402 user
=> 'all', # check inside
407 additionalProperties
=> 0,
408 properties
=> PVE
::QemuServer
::json_config_properties
(
410 node
=> get_standard_option
('pve-node'),
411 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
413 description
=> "The backup file.",
417 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
419 storage
=> get_standard_option
('pve-storage-id', {
420 description
=> "Default storage.",
422 completion
=> \
&PVE
::QemuServer
::complete_storage
,
427 description
=> "Allow to overwrite existing VM.",
428 requires
=> 'archive',
433 description
=> "Assign a unique random ethernet address.",
434 requires
=> 'archive',
438 type
=> 'string', format
=> 'pve-poolid',
439 description
=> "Add the VM to the specified pool.",
442 description
=> "Override i/o bandwidth limit (in KiB/s).",
451 description
=> "Start VM after it was created successfully.",
461 my $rpcenv = PVE
::RPCEnvironment
::get
();
463 my $authuser = $rpcenv->get_user();
465 my $node = extract_param
($param, 'node');
467 my $vmid = extract_param
($param, 'vmid');
469 my $archive = extract_param
($param, 'archive');
470 my $is_restore = !!$archive;
472 my $storage = extract_param
($param, 'storage');
474 my $force = extract_param
($param, 'force');
476 my $unique = extract_param
($param, 'unique');
478 my $pool = extract_param
($param, 'pool');
480 my $bwlimit = extract_param
($param, 'bwlimit');
482 my $start_after_create = extract_param
($param, 'start');
484 my $filename = PVE
::QemuConfig-
>config_file($vmid);
486 my $storecfg = PVE
::Storage
::config
();
488 if (defined(my $ssh_keys = $param->{sshkeys
})) {
489 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
490 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
493 PVE
::Cluster
::check_cfs_quorum
();
495 if (defined($pool)) {
496 $rpcenv->check_pool_exist($pool);
499 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
500 if defined($storage);
502 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
504 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
506 } elsif ($archive && $force && (-f
$filename) &&
507 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
508 # OK: user has VM.Backup permissions, and want to restore an existing VM
514 &$resolve_cdrom_alias($param);
516 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
518 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
520 foreach my $opt (keys %$param) {
521 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
522 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
523 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
525 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
526 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
530 PVE
::QemuServer
::add_random_macs
($param);
532 my $keystr = join(' ', keys %$param);
533 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
535 if ($archive eq '-') {
536 die "pipe requires cli environment\n"
537 if $rpcenv->{type
} ne 'cli';
539 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
540 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
544 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
546 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
547 die "$emsg $@" if $@;
549 my $restorefn = sub {
550 my $conf = PVE
::QemuConfig-
>load_config($vmid);
552 PVE
::QemuConfig-
>check_protection($conf, $emsg);
554 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
555 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
558 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
562 bwlimit
=> $bwlimit, });
564 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
566 if ($start_after_create) {
567 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
572 # ensure no old replication state are exists
573 PVE
::ReplicationState
::delete_guest_states
($vmid);
575 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
588 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
592 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
594 if (!$conf->{bootdisk
}) {
595 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
596 $conf->{bootdisk
} = $firstdisk if $firstdisk;
599 # auto generate uuid if user did not specify smbios1 option
600 if (!$conf->{smbios1
}) {
601 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
604 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
605 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
608 PVE
::QemuConfig-
>write_config($vmid, $conf);
614 foreach my $volid (@$vollist) {
615 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
621 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
624 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
626 if ($start_after_create) {
627 print "Execute autostart\n";
628 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
633 my ($code, $worker_name);
635 $worker_name = 'qmrestore';
637 eval { $restorefn->() };
639 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
645 $worker_name = 'qmcreate';
647 eval { $createfn->() };
650 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
651 unlink($conffile) or die "failed to remove config file: $!\n";
659 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
662 __PACKAGE__-
>register_method({
667 description
=> "Directory index",
672 additionalProperties
=> 0,
674 node
=> get_standard_option
('pve-node'),
675 vmid
=> get_standard_option
('pve-vmid'),
683 subdir
=> { type
=> 'string' },
686 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
692 { subdir
=> 'config' },
693 { subdir
=> 'pending' },
694 { subdir
=> 'status' },
695 { subdir
=> 'unlink' },
696 { subdir
=> 'vncproxy' },
697 { subdir
=> 'termproxy' },
698 { subdir
=> 'migrate' },
699 { subdir
=> 'resize' },
700 { subdir
=> 'move' },
702 { subdir
=> 'rrddata' },
703 { subdir
=> 'monitor' },
704 { subdir
=> 'agent' },
705 { subdir
=> 'snapshot' },
706 { subdir
=> 'spiceproxy' },
707 { subdir
=> 'sendkey' },
708 { subdir
=> 'firewall' },
714 __PACKAGE__-
>register_method ({
715 subclass
=> "PVE::API2::Firewall::VM",
716 path
=> '{vmid}/firewall',
719 __PACKAGE__-
>register_method ({
720 subclass
=> "PVE::API2::Qemu::Agent",
721 path
=> '{vmid}/agent',
724 __PACKAGE__-
>register_method({
726 path
=> '{vmid}/rrd',
728 protected
=> 1, # fixme: can we avoid that?
730 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
732 description
=> "Read VM RRD statistics (returns PNG)",
734 additionalProperties
=> 0,
736 node
=> get_standard_option
('pve-node'),
737 vmid
=> get_standard_option
('pve-vmid'),
739 description
=> "Specify the time frame you are interested in.",
741 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
744 description
=> "The list of datasources you want to display.",
745 type
=> 'string', format
=> 'pve-configid-list',
748 description
=> "The RRD consolidation function",
750 enum
=> [ 'AVERAGE', 'MAX' ],
758 filename
=> { type
=> 'string' },
764 return PVE
::Cluster
::create_rrd_graph
(
765 "pve2-vm/$param->{vmid}", $param->{timeframe
},
766 $param->{ds
}, $param->{cf
});
770 __PACKAGE__-
>register_method({
772 path
=> '{vmid}/rrddata',
774 protected
=> 1, # fixme: can we avoid that?
776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
778 description
=> "Read VM RRD statistics",
780 additionalProperties
=> 0,
782 node
=> get_standard_option
('pve-node'),
783 vmid
=> get_standard_option
('pve-vmid'),
785 description
=> "Specify the time frame you are interested in.",
787 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
790 description
=> "The RRD consolidation function",
792 enum
=> [ 'AVERAGE', 'MAX' ],
807 return PVE
::Cluster
::create_rrd_data
(
808 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
812 __PACKAGE__-
>register_method({
814 path
=> '{vmid}/config',
817 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
819 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
822 additionalProperties
=> 0,
824 node
=> get_standard_option
('pve-node'),
825 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
827 description
=> "Get current values (instead of pending values).",
832 snapshot
=> get_standard_option
('pve-snapshot-name', {
833 description
=> "Fetch config values from given snapshot.",
836 my ($cmd, $pname, $cur, $args) = @_;
837 PVE
::QemuConfig-
>snapshot_list($args->[0]);
843 description
=> "The current VM configuration.",
845 properties
=> PVE
::QemuServer
::json_config_properties
({
848 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
855 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
857 my $snapname = $param->{snapshot
};
859 my $snapshot = $conf->{snapshots
}->{$snapname};
860 die "snapshot '$snapname' does not exist\n"
861 if !defined($snapshot);
863 # we need the digest of the file
864 $snapshot->{digest
} = $conf->{digest
};
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
();
1125 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1127 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1129 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1131 my $updatefn = sub {
1133 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1135 die "checksum missmatch (file change by other user?)\n"
1136 if $digest && $digest ne $conf->{digest
};
1138 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1140 foreach my $opt (keys %$revert) {
1141 if (defined($conf->{$opt})) {
1142 $param->{$opt} = $conf->{$opt};
1143 } elsif (defined($conf->{pending
}->{$opt})) {
1148 if ($param->{memory
} || defined($param->{balloon
})) {
1149 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1150 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1152 die "balloon value too large (must be smaller than assigned memory)\n"
1153 if $balloon && $balloon > $maxmem;
1156 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1160 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1162 # write updates to pending section
1164 my $modified = {}; # record what $option we modify
1166 foreach my $opt (@delete) {
1167 $modified->{$opt} = 1;
1168 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1169 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1170 warn "cannot delete '$opt' - not set in current configuration!\n";
1171 $modified->{$opt} = 0;
1175 if ($opt =~ m/^unused/) {
1176 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1177 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1178 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1179 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1180 delete $conf->{$opt};
1181 PVE
::QemuConfig-
>write_config($vmid, $conf);
1183 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1184 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1185 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1186 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1187 if defined($conf->{pending
}->{$opt});
1188 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1191 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1192 PVE
::QemuConfig-
>write_config($vmid, $conf);
1196 foreach my $opt (keys %$param) { # add/change
1197 $modified->{$opt} = 1;
1198 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1199 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1201 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1203 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1204 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1205 # FIXME: cloudinit: CDROM or Disk?
1206 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1211 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1212 if defined($conf->{pending
}->{$opt});
1214 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1216 $conf->{pending
}->{$opt} = $param->{$opt};
1218 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1219 PVE
::QemuConfig-
>write_config($vmid, $conf);
1222 # remove pending changes when nothing changed
1223 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1224 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1225 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1227 return if !scalar(keys %{$conf->{pending
}});
1229 my $running = PVE
::QemuServer
::check_running
($vmid);
1231 # apply pending changes
1233 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1237 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1238 raise_param_exc
($errors) if scalar(keys %$errors);
1240 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1250 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1252 if ($background_delay) {
1254 # Note: It would be better to do that in the Event based HTTPServer
1255 # to avoid blocking call to sleep.
1257 my $end_time = time() + $background_delay;
1259 my $task = PVE
::Tools
::upid_decode
($upid);
1262 while (time() < $end_time) {
1263 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1265 sleep(1); # this gets interrupted when child process ends
1269 my $status = PVE
::Tools
::upid_read_status
($upid);
1270 return undef if $status eq 'OK';
1279 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1282 my $vm_config_perm_list = [
1287 'VM.Config.Network',
1289 'VM.Config.Options',
1292 __PACKAGE__-
>register_method({
1293 name
=> 'update_vm_async',
1294 path
=> '{vmid}/config',
1298 description
=> "Set virtual machine options (asynchrounous API).",
1300 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1303 additionalProperties
=> 0,
1304 properties
=> PVE
::QemuServer
::json_config_properties
(
1306 node
=> get_standard_option
('pve-node'),
1307 vmid
=> get_standard_option
('pve-vmid'),
1308 skiplock
=> get_standard_option
('skiplock'),
1310 type
=> 'string', format
=> 'pve-configid-list',
1311 description
=> "A list of settings you want to delete.",
1315 type
=> 'string', format
=> 'pve-configid-list',
1316 description
=> "Revert a pending change.",
1321 description
=> $opt_force_description,
1323 requires
=> 'delete',
1327 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1331 background_delay
=> {
1333 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1344 code
=> $update_vm_api,
1347 __PACKAGE__-
>register_method({
1348 name
=> 'update_vm',
1349 path
=> '{vmid}/config',
1353 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1355 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1358 additionalProperties
=> 0,
1359 properties
=> PVE
::QemuServer
::json_config_properties
(
1361 node
=> get_standard_option
('pve-node'),
1362 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1363 skiplock
=> get_standard_option
('skiplock'),
1365 type
=> 'string', format
=> 'pve-configid-list',
1366 description
=> "A list of settings you want to delete.",
1370 type
=> 'string', format
=> 'pve-configid-list',
1371 description
=> "Revert a pending change.",
1376 description
=> $opt_force_description,
1378 requires
=> 'delete',
1382 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1388 returns
=> { type
=> 'null' },
1391 &$update_vm_api($param, 1);
1397 __PACKAGE__-
>register_method({
1398 name
=> 'destroy_vm',
1403 description
=> "Destroy the vm (also delete all used/owned volumes).",
1405 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1408 additionalProperties
=> 0,
1410 node
=> get_standard_option
('pve-node'),
1411 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1412 skiplock
=> get_standard_option
('skiplock'),
1421 my $rpcenv = PVE
::RPCEnvironment
::get
();
1423 my $authuser = $rpcenv->get_user();
1425 my $vmid = $param->{vmid
};
1427 my $skiplock = $param->{skiplock
};
1428 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1429 if $skiplock && $authuser ne 'root@pam';
1432 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1434 my $storecfg = PVE
::Storage
::config
();
1436 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1438 die "unable to remove VM $vmid - used in HA resources\n"
1439 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1441 # do not allow destroy if there are replication jobs
1442 my $repl_conf = PVE
::ReplicationConfig-
>new();
1443 $repl_conf->check_for_existing_jobs($vmid);
1445 # early tests (repeat after locking)
1446 die "VM $vmid is running - destroy failed\n"
1447 if PVE
::QemuServer
::check_running
($vmid);
1452 syslog
('info', "destroy VM $vmid: $upid\n");
1454 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1456 PVE
::AccessControl
::remove_vm_access
($vmid);
1458 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1461 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1464 __PACKAGE__-
>register_method({
1466 path
=> '{vmid}/unlink',
1470 description
=> "Unlink/delete disk images.",
1472 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1475 additionalProperties
=> 0,
1477 node
=> get_standard_option
('pve-node'),
1478 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1480 type
=> 'string', format
=> 'pve-configid-list',
1481 description
=> "A list of disk IDs you want to delete.",
1485 description
=> $opt_force_description,
1490 returns
=> { type
=> 'null'},
1494 $param->{delete} = extract_param
($param, 'idlist');
1496 __PACKAGE__-
>update_vm($param);
1503 __PACKAGE__-
>register_method({
1505 path
=> '{vmid}/vncproxy',
1509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1511 description
=> "Creates a TCP VNC proxy connections.",
1513 additionalProperties
=> 0,
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid'),
1520 description
=> "starts websockify instead of vncproxy",
1525 additionalProperties
=> 0,
1527 user
=> { type
=> 'string' },
1528 ticket
=> { type
=> 'string' },
1529 cert
=> { type
=> 'string' },
1530 port
=> { type
=> 'integer' },
1531 upid
=> { type
=> 'string' },
1537 my $rpcenv = PVE
::RPCEnvironment
::get
();
1539 my $authuser = $rpcenv->get_user();
1541 my $vmid = $param->{vmid
};
1542 my $node = $param->{node
};
1543 my $websocket = $param->{websocket
};
1545 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1546 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1548 my $authpath = "/vms/$vmid";
1550 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1552 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1558 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1559 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1560 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1561 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1562 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1564 $family = PVE
::Tools
::get_host_address_family
($node);
1567 my $port = PVE
::Tools
::next_vnc_port
($family);
1574 syslog
('info', "starting vnc proxy $upid\n");
1580 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1582 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1583 '-timeout', $timeout, '-authpath', $authpath,
1584 '-perm', 'Sys.Console'];
1586 if ($param->{websocket
}) {
1587 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1588 push @$cmd, '-notls', '-listen', 'localhost';
1591 push @$cmd, '-c', @$remcmd, @$termcmd;
1593 PVE
::Tools
::run_command
($cmd);
1597 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1599 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1601 my $sock = IO
::Socket
::IP-
>new(
1606 GetAddrInfoFlags
=> 0,
1607 ) or die "failed to create socket: $!\n";
1608 # Inside the worker we shouldn't have any previous alarms
1609 # running anyway...:
1611 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1613 accept(my $cli, $sock) or die "connection failed: $!\n";
1616 if (PVE
::Tools
::run_command
($cmd,
1617 output
=> '>&'.fileno($cli),
1618 input
=> '<&'.fileno($cli),
1621 die "Failed to run vncproxy.\n";
1628 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1630 PVE
::Tools
::wait_for_vnc_port
($port);
1641 __PACKAGE__-
>register_method({
1642 name
=> 'termproxy',
1643 path
=> '{vmid}/termproxy',
1647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1649 description
=> "Creates a TCP proxy connections.",
1651 additionalProperties
=> 0,
1653 node
=> get_standard_option
('pve-node'),
1654 vmid
=> get_standard_option
('pve-vmid'),
1658 enum
=> [qw(serial0 serial1 serial2 serial3)],
1659 description
=> "opens a serial terminal (defaults to display)",
1664 additionalProperties
=> 0,
1666 user
=> { type
=> 'string' },
1667 ticket
=> { type
=> 'string' },
1668 port
=> { type
=> 'integer' },
1669 upid
=> { type
=> 'string' },
1675 my $rpcenv = PVE
::RPCEnvironment
::get
();
1677 my $authuser = $rpcenv->get_user();
1679 my $vmid = $param->{vmid
};
1680 my $node = $param->{node
};
1681 my $serial = $param->{serial
};
1683 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1685 if (!defined($serial)) {
1686 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1687 $serial = $conf->{vga
};
1691 my $authpath = "/vms/$vmid";
1693 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1698 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1699 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1700 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1701 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1702 push @$remcmd, '--';
1704 $family = PVE
::Tools
::get_host_address_family
($node);
1707 my $port = PVE
::Tools
::next_vnc_port
($family);
1709 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1710 push @$termcmd, '-iface', $serial if $serial;
1715 syslog
('info', "starting qemu termproxy $upid\n");
1717 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1718 '--perm', 'VM.Console', '--'];
1719 push @$cmd, @$remcmd, @$termcmd;
1721 PVE
::Tools
::run_command
($cmd);
1724 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1726 PVE
::Tools
::wait_for_vnc_port
($port);
1736 __PACKAGE__-
>register_method({
1737 name
=> 'vncwebsocket',
1738 path
=> '{vmid}/vncwebsocket',
1741 description
=> "You also need to pass a valid ticket (vncticket).",
1742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1744 description
=> "Opens a weksocket for VNC traffic.",
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid'),
1751 description
=> "Ticket from previous call to vncproxy.",
1756 description
=> "Port number returned by previous vncproxy call.",
1766 port
=> { type
=> 'string' },
1772 my $rpcenv = PVE
::RPCEnvironment
::get
();
1774 my $authuser = $rpcenv->get_user();
1776 my $vmid = $param->{vmid
};
1777 my $node = $param->{node
};
1779 my $authpath = "/vms/$vmid";
1781 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1783 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1785 # Note: VNC ports are acessible from outside, so we do not gain any
1786 # security if we verify that $param->{port} belongs to VM $vmid. This
1787 # check is done by verifying the VNC ticket (inside VNC protocol).
1789 my $port = $param->{port
};
1791 return { port
=> $port };
1794 __PACKAGE__-
>register_method({
1795 name
=> 'spiceproxy',
1796 path
=> '{vmid}/spiceproxy',
1801 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1803 description
=> "Returns a SPICE configuration to connect to the VM.",
1805 additionalProperties
=> 0,
1807 node
=> get_standard_option
('pve-node'),
1808 vmid
=> get_standard_option
('pve-vmid'),
1809 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1812 returns
=> get_standard_option
('remote-viewer-config'),
1816 my $rpcenv = PVE
::RPCEnvironment
::get
();
1818 my $authuser = $rpcenv->get_user();
1820 my $vmid = $param->{vmid
};
1821 my $node = $param->{node
};
1822 my $proxy = $param->{proxy
};
1824 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1825 my $title = "VM $vmid";
1826 $title .= " - ". $conf->{name
} if $conf->{name
};
1828 my $port = PVE
::QemuServer
::spice_port
($vmid);
1830 my ($ticket, undef, $remote_viewer_config) =
1831 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1833 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1834 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1836 return $remote_viewer_config;
1839 __PACKAGE__-
>register_method({
1841 path
=> '{vmid}/status',
1844 description
=> "Directory index",
1849 additionalProperties
=> 0,
1851 node
=> get_standard_option
('pve-node'),
1852 vmid
=> get_standard_option
('pve-vmid'),
1860 subdir
=> { type
=> 'string' },
1863 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1869 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1872 { subdir
=> 'current' },
1873 { subdir
=> 'start' },
1874 { subdir
=> 'stop' },
1880 __PACKAGE__-
>register_method({
1881 name
=> 'vm_status',
1882 path
=> '{vmid}/status/current',
1885 protected
=> 1, # qemu pid files are only readable by root
1886 description
=> "Get virtual machine status.",
1888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1891 additionalProperties
=> 0,
1893 node
=> get_standard_option
('pve-node'),
1894 vmid
=> get_standard_option
('pve-vmid'),
1900 %$PVE::QemuServer
::vmstatus_return_properties
,
1902 description
=> "HA manager service status.",
1906 description
=> "Qemu VGA configuration supports spice.",
1911 description
=> "Qemu GuestAgent enabled in config.",
1921 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1923 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1924 my $status = $vmstatus->{$param->{vmid
}};
1926 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1928 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1929 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1934 __PACKAGE__-
>register_method({
1936 path
=> '{vmid}/status/start',
1940 description
=> "Start virtual machine.",
1942 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1945 additionalProperties
=> 0,
1947 node
=> get_standard_option
('pve-node'),
1948 vmid
=> get_standard_option
('pve-vmid',
1949 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1950 skiplock
=> get_standard_option
('skiplock'),
1951 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1952 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1955 enum
=> ['secure', 'insecure'],
1956 description
=> "Migration traffic is encrypted using an SSH " .
1957 "tunnel by default. On secure, completely private networks " .
1958 "this can be disabled to increase performance.",
1961 migration_network
=> {
1962 type
=> 'string', format
=> 'CIDR',
1963 description
=> "CIDR of the (sub) network that is used for migration.",
1966 machine
=> get_standard_option
('pve-qm-machine'),
1968 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1980 my $rpcenv = PVE
::RPCEnvironment
::get
();
1982 my $authuser = $rpcenv->get_user();
1984 my $node = extract_param
($param, 'node');
1986 my $vmid = extract_param
($param, 'vmid');
1988 my $machine = extract_param
($param, 'machine');
1990 my $stateuri = extract_param
($param, 'stateuri');
1991 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1992 if $stateuri && $authuser ne 'root@pam';
1994 my $skiplock = extract_param
($param, 'skiplock');
1995 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1996 if $skiplock && $authuser ne 'root@pam';
1998 my $migratedfrom = extract_param
($param, 'migratedfrom');
1999 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2000 if $migratedfrom && $authuser ne 'root@pam';
2002 my $migration_type = extract_param
($param, 'migration_type');
2003 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2004 if $migration_type && $authuser ne 'root@pam';
2006 my $migration_network = extract_param
($param, 'migration_network');
2007 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2008 if $migration_network && $authuser ne 'root@pam';
2010 my $targetstorage = extract_param
($param, 'targetstorage');
2011 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2012 if $targetstorage && $authuser ne 'root@pam';
2014 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2015 if $targetstorage && !$migratedfrom;
2017 # read spice ticket from STDIN
2019 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2020 if (defined(my $line = <STDIN
>)) {
2022 $spice_ticket = $line;
2026 PVE
::Cluster
::check_cfs_quorum
();
2028 my $storecfg = PVE
::Storage
::config
();
2030 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2031 $rpcenv->{type
} ne 'ha') {
2036 my $service = "vm:$vmid";
2038 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2040 print "Requesting HA start for VM $vmid\n";
2042 PVE
::Tools
::run_command
($cmd);
2047 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2054 syslog
('info', "start VM $vmid: $upid\n");
2056 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2057 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2062 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2066 __PACKAGE__-
>register_method({
2068 path
=> '{vmid}/status/stop',
2072 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2073 "is akin to pulling the power plug of a running computer and may damage the VM data",
2075 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2078 additionalProperties
=> 0,
2080 node
=> get_standard_option
('pve-node'),
2081 vmid
=> get_standard_option
('pve-vmid',
2082 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2083 skiplock
=> get_standard_option
('skiplock'),
2084 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2086 description
=> "Wait maximal timeout seconds.",
2092 description
=> "Do not deactivate storage volumes.",
2105 my $rpcenv = PVE
::RPCEnvironment
::get
();
2107 my $authuser = $rpcenv->get_user();
2109 my $node = extract_param
($param, 'node');
2111 my $vmid = extract_param
($param, 'vmid');
2113 my $skiplock = extract_param
($param, 'skiplock');
2114 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2115 if $skiplock && $authuser ne 'root@pam';
2117 my $keepActive = extract_param
($param, 'keepActive');
2118 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2119 if $keepActive && $authuser ne 'root@pam';
2121 my $migratedfrom = extract_param
($param, 'migratedfrom');
2122 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2123 if $migratedfrom && $authuser ne 'root@pam';
2126 my $storecfg = PVE
::Storage
::config
();
2128 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2133 my $service = "vm:$vmid";
2135 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2137 print "Requesting HA stop for VM $vmid\n";
2139 PVE
::Tools
::run_command
($cmd);
2144 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2150 syslog
('info', "stop VM $vmid: $upid\n");
2152 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2153 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2158 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2162 __PACKAGE__-
>register_method({
2164 path
=> '{vmid}/status/reset',
2168 description
=> "Reset virtual machine.",
2170 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2173 additionalProperties
=> 0,
2175 node
=> get_standard_option
('pve-node'),
2176 vmid
=> get_standard_option
('pve-vmid',
2177 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2178 skiplock
=> get_standard_option
('skiplock'),
2187 my $rpcenv = PVE
::RPCEnvironment
::get
();
2189 my $authuser = $rpcenv->get_user();
2191 my $node = extract_param
($param, 'node');
2193 my $vmid = extract_param
($param, 'vmid');
2195 my $skiplock = extract_param
($param, 'skiplock');
2196 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2197 if $skiplock && $authuser ne 'root@pam';
2199 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2204 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2209 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2212 __PACKAGE__-
>register_method({
2213 name
=> 'vm_shutdown',
2214 path
=> '{vmid}/status/shutdown',
2218 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2219 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2221 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2224 additionalProperties
=> 0,
2226 node
=> get_standard_option
('pve-node'),
2227 vmid
=> get_standard_option
('pve-vmid',
2228 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2229 skiplock
=> get_standard_option
('skiplock'),
2231 description
=> "Wait maximal timeout seconds.",
2237 description
=> "Make sure the VM stops.",
2243 description
=> "Do not deactivate storage volumes.",
2256 my $rpcenv = PVE
::RPCEnvironment
::get
();
2258 my $authuser = $rpcenv->get_user();
2260 my $node = extract_param
($param, 'node');
2262 my $vmid = extract_param
($param, 'vmid');
2264 my $skiplock = extract_param
($param, 'skiplock');
2265 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2266 if $skiplock && $authuser ne 'root@pam';
2268 my $keepActive = extract_param
($param, 'keepActive');
2269 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2270 if $keepActive && $authuser ne 'root@pam';
2272 my $storecfg = PVE
::Storage
::config
();
2276 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2277 # otherwise, we will infer a shutdown command, but run into the timeout,
2278 # then when the vm is resumed, it will instantly shutdown
2280 # checking the qmp status here to get feedback to the gui/cli/api
2281 # and the status query should not take too long
2284 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2288 if (!$err && $qmpstatus->{status
} eq "paused") {
2289 if ($param->{forceStop
}) {
2290 warn "VM is paused - stop instead of shutdown\n";
2293 die "VM is paused - cannot shutdown\n";
2297 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2298 ($rpcenv->{type
} ne 'ha')) {
2303 my $service = "vm:$vmid";
2305 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2307 print "Requesting HA stop for VM $vmid\n";
2309 PVE
::Tools
::run_command
($cmd);
2314 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2321 syslog
('info', "shutdown VM $vmid: $upid\n");
2323 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2324 $shutdown, $param->{forceStop
}, $keepActive);
2329 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2333 __PACKAGE__-
>register_method({
2334 name
=> 'vm_suspend',
2335 path
=> '{vmid}/status/suspend',
2339 description
=> "Suspend virtual machine.",
2341 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2344 additionalProperties
=> 0,
2346 node
=> get_standard_option
('pve-node'),
2347 vmid
=> get_standard_option
('pve-vmid',
2348 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2349 skiplock
=> get_standard_option
('skiplock'),
2358 my $rpcenv = PVE
::RPCEnvironment
::get
();
2360 my $authuser = $rpcenv->get_user();
2362 my $node = extract_param
($param, 'node');
2364 my $vmid = extract_param
($param, 'vmid');
2366 my $skiplock = extract_param
($param, 'skiplock');
2367 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2368 if $skiplock && $authuser ne 'root@pam';
2370 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2375 syslog
('info', "suspend VM $vmid: $upid\n");
2377 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2382 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2385 __PACKAGE__-
>register_method({
2386 name
=> 'vm_resume',
2387 path
=> '{vmid}/status/resume',
2391 description
=> "Resume virtual machine.",
2393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2396 additionalProperties
=> 0,
2398 node
=> get_standard_option
('pve-node'),
2399 vmid
=> get_standard_option
('pve-vmid',
2400 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2401 skiplock
=> get_standard_option
('skiplock'),
2402 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2412 my $rpcenv = PVE
::RPCEnvironment
::get
();
2414 my $authuser = $rpcenv->get_user();
2416 my $node = extract_param
($param, 'node');
2418 my $vmid = extract_param
($param, 'vmid');
2420 my $skiplock = extract_param
($param, 'skiplock');
2421 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2422 if $skiplock && $authuser ne 'root@pam';
2424 my $nocheck = extract_param
($param, 'nocheck');
2426 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2431 syslog
('info', "resume VM $vmid: $upid\n");
2433 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2438 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2441 __PACKAGE__-
>register_method({
2442 name
=> 'vm_sendkey',
2443 path
=> '{vmid}/sendkey',
2447 description
=> "Send key event to virtual machine.",
2449 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2452 additionalProperties
=> 0,
2454 node
=> get_standard_option
('pve-node'),
2455 vmid
=> get_standard_option
('pve-vmid',
2456 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2457 skiplock
=> get_standard_option
('skiplock'),
2459 description
=> "The key (qemu monitor encoding).",
2464 returns
=> { type
=> 'null'},
2468 my $rpcenv = PVE
::RPCEnvironment
::get
();
2470 my $authuser = $rpcenv->get_user();
2472 my $node = extract_param
($param, 'node');
2474 my $vmid = extract_param
($param, 'vmid');
2476 my $skiplock = extract_param
($param, 'skiplock');
2477 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2478 if $skiplock && $authuser ne 'root@pam';
2480 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2485 __PACKAGE__-
>register_method({
2486 name
=> 'vm_feature',
2487 path
=> '{vmid}/feature',
2491 description
=> "Check if feature for virtual machine is available.",
2493 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2496 additionalProperties
=> 0,
2498 node
=> get_standard_option
('pve-node'),
2499 vmid
=> get_standard_option
('pve-vmid'),
2501 description
=> "Feature to check.",
2503 enum
=> [ 'snapshot', 'clone', 'copy' ],
2505 snapname
=> get_standard_option
('pve-snapshot-name', {
2513 hasFeature
=> { type
=> 'boolean' },
2516 items
=> { type
=> 'string' },
2523 my $node = extract_param
($param, 'node');
2525 my $vmid = extract_param
($param, 'vmid');
2527 my $snapname = extract_param
($param, 'snapname');
2529 my $feature = extract_param
($param, 'feature');
2531 my $running = PVE
::QemuServer
::check_running
($vmid);
2533 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2536 my $snap = $conf->{snapshots
}->{$snapname};
2537 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2540 my $storecfg = PVE
::Storage
::config
();
2542 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2543 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2546 hasFeature
=> $hasFeature,
2547 nodes
=> [ keys %$nodelist ],
2551 __PACKAGE__-
>register_method({
2553 path
=> '{vmid}/clone',
2557 description
=> "Create a copy of virtual machine/template.",
2559 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2560 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2561 "'Datastore.AllocateSpace' on any used storage.",
2564 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2566 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2567 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2572 additionalProperties
=> 0,
2574 node
=> get_standard_option
('pve-node'),
2575 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2576 newid
=> get_standard_option
('pve-vmid', {
2577 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2578 description
=> 'VMID for the clone.' }),
2581 type
=> 'string', format
=> 'dns-name',
2582 description
=> "Set a name for the new VM.",
2587 description
=> "Description for the new VM.",
2591 type
=> 'string', format
=> 'pve-poolid',
2592 description
=> "Add the new VM to the specified pool.",
2594 snapname
=> get_standard_option
('pve-snapshot-name', {
2597 storage
=> get_standard_option
('pve-storage-id', {
2598 description
=> "Target storage for full clone.",
2602 description
=> "Target format for file storage. Only valid for full clone.",
2605 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2610 description
=> "Create a full copy of all disks. This is always done when " .
2611 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2613 target
=> get_standard_option
('pve-node', {
2614 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2625 my $rpcenv = PVE
::RPCEnvironment
::get
();
2627 my $authuser = $rpcenv->get_user();
2629 my $node = extract_param
($param, 'node');
2631 my $vmid = extract_param
($param, 'vmid');
2633 my $newid = extract_param
($param, 'newid');
2635 my $pool = extract_param
($param, 'pool');
2637 if (defined($pool)) {
2638 $rpcenv->check_pool_exist($pool);
2641 my $snapname = extract_param
($param, 'snapname');
2643 my $storage = extract_param
($param, 'storage');
2645 my $format = extract_param
($param, 'format');
2647 my $target = extract_param
($param, 'target');
2649 my $localnode = PVE
::INotify
::nodename
();
2651 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2653 PVE
::Cluster
::check_node_exists
($target) if $target;
2655 my $storecfg = PVE
::Storage
::config
();
2658 # check if storage is enabled on local node
2659 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2661 # check if storage is available on target node
2662 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2663 # clone only works if target storage is shared
2664 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2665 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2669 PVE
::Cluster
::check_cfs_quorum
();
2671 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2673 # exclusive lock if VM is running - else shared lock is enough;
2674 my $shared_lock = $running ?
0 : 1;
2678 # do all tests after lock
2679 # we also try to do all tests before we fork the worker
2681 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2683 PVE
::QemuConfig-
>check_lock($conf);
2685 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2687 die "unexpected state change\n" if $verify_running != $running;
2689 die "snapshot '$snapname' does not exist\n"
2690 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2692 my $full = extract_param
($param, 'full');
2693 if (!defined($full)) {
2694 $full = !PVE
::QemuConfig-
>is_template($conf);
2697 die "parameter 'storage' not allowed for linked clones\n"
2698 if defined($storage) && !$full;
2700 die "parameter 'format' not allowed for linked clones\n"
2701 if defined($format) && !$full;
2703 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2705 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2707 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2709 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2711 die "unable to create VM $newid: config file already exists\n"
2714 my $newconf = { lock => 'clone' };
2719 foreach my $opt (keys %$oldconf) {
2720 my $value = $oldconf->{$opt};
2722 # do not copy snapshot related info
2723 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2724 $opt eq 'vmstate' || $opt eq 'snapstate';
2726 # no need to copy unused images, because VMID(owner) changes anyways
2727 next if $opt =~ m/^unused\d+$/;
2729 # always change MAC! address
2730 if ($opt =~ m/^net(\d+)$/) {
2731 my $net = PVE
::QemuServer
::parse_net
($value);
2732 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2733 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2734 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2735 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2736 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2737 die "unable to parse drive options for '$opt'\n" if !$drive;
2738 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2739 $newconf->{$opt} = $value; # simply copy configuration
2741 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2742 die "Full clone feature is not supported for drive '$opt'\n"
2743 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2744 $fullclone->{$opt} = 1;
2746 # not full means clone instead of copy
2747 die "Linked clone feature is not supported for drive '$opt'\n"
2748 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2750 $drives->{$opt} = $drive;
2751 push @$vollist, $drive->{file
};
2754 # copy everything else
2755 $newconf->{$opt} = $value;
2759 # auto generate a new uuid
2760 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2761 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2762 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2764 # auto generate a new vmgenid if the option was set
2765 if ($newconf->{vmgenid
}) {
2766 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2769 delete $newconf->{template
};
2771 if ($param->{name
}) {
2772 $newconf->{name
} = $param->{name
};
2774 if ($oldconf->{name
}) {
2775 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2777 $newconf->{name
} = "Copy-of-VM-$vmid";
2781 if ($param->{description
}) {
2782 $newconf->{description
} = $param->{description
};
2785 # create empty/temp config - this fails if VM already exists on other node
2786 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2791 my $newvollist = [];
2798 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2800 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2802 my $total_jobs = scalar(keys %{$drives});
2805 foreach my $opt (keys %$drives) {
2806 my $drive = $drives->{$opt};
2807 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2809 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2810 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2811 $jobs, $skipcomplete, $oldconf->{agent
});
2813 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2815 PVE
::QemuConfig-
>write_config($newid, $newconf);
2819 delete $newconf->{lock};
2821 # do not write pending changes
2822 if (my @changes = keys %{$newconf->{pending
}}) {
2823 my $pending = join(',', @changes);
2824 warn "found pending changes for '$pending', discarding for clone\n";
2825 delete $newconf->{pending
};
2828 PVE
::QemuConfig-
>write_config($newid, $newconf);
2831 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2832 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2833 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2835 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2836 die "Failed to move config to node '$target' - rename failed: $!\n"
2837 if !rename($conffile, $newconffile);
2840 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2845 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2847 sleep 1; # some storage like rbd need to wait before release volume - really?
2849 foreach my $volid (@$newvollist) {
2850 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2853 die "clone failed: $err";
2859 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2861 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2864 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2865 # Aquire exclusive lock lock for $newid
2866 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2871 __PACKAGE__-
>register_method({
2872 name
=> 'move_vm_disk',
2873 path
=> '{vmid}/move_disk',
2877 description
=> "Move volume to different storage.",
2879 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2881 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2882 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2886 additionalProperties
=> 0,
2888 node
=> get_standard_option
('pve-node'),
2889 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2892 description
=> "The disk you want to move.",
2893 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2895 storage
=> get_standard_option
('pve-storage-id', {
2896 description
=> "Target storage.",
2897 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2901 description
=> "Target Format.",
2902 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2907 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2913 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2921 description
=> "the task ID.",
2926 my $rpcenv = PVE
::RPCEnvironment
::get
();
2928 my $authuser = $rpcenv->get_user();
2930 my $node = extract_param
($param, 'node');
2932 my $vmid = extract_param
($param, 'vmid');
2934 my $digest = extract_param
($param, 'digest');
2936 my $disk = extract_param
($param, 'disk');
2938 my $storeid = extract_param
($param, 'storage');
2940 my $format = extract_param
($param, 'format');
2942 my $storecfg = PVE
::Storage
::config
();
2944 my $updatefn = sub {
2946 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2948 PVE
::QemuConfig-
>check_lock($conf);
2950 die "checksum missmatch (file change by other user?)\n"
2951 if $digest && $digest ne $conf->{digest
};
2953 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2955 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2957 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2959 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2962 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2963 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2967 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2968 (!$format || !$oldfmt || $oldfmt eq $format);
2970 # this only checks snapshots because $disk is passed!
2971 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2972 die "you can't move a disk with snapshots and delete the source\n"
2973 if $snapshotted && $param->{delete};
2975 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2977 my $running = PVE
::QemuServer
::check_running
($vmid);
2979 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2983 my $newvollist = [];
2989 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2991 warn "moving disk with snapshots, snapshots will not be moved!\n"
2994 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2995 $vmid, $storeid, $format, 1, $newvollist);
2997 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2999 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3001 # convert moved disk to base if part of template
3002 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3003 if PVE
::QemuConfig-
>is_template($conf);
3005 PVE
::QemuConfig-
>write_config($vmid, $conf);
3007 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3008 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3012 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3013 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3020 foreach my $volid (@$newvollist) {
3021 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3024 die "storage migration failed: $err";
3027 if ($param->{delete}) {
3029 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3030 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3036 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3039 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3042 __PACKAGE__-
>register_method({
3043 name
=> 'migrate_vm',
3044 path
=> '{vmid}/migrate',
3048 description
=> "Migrate virtual machine. Creates a new migration task.",
3050 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3053 additionalProperties
=> 0,
3055 node
=> get_standard_option
('pve-node'),
3056 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3057 target
=> get_standard_option
('pve-node', {
3058 description
=> "Target node.",
3059 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3063 description
=> "Use online/live migration.",
3068 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3073 enum
=> ['secure', 'insecure'],
3074 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3077 migration_network
=> {
3078 type
=> 'string', format
=> 'CIDR',
3079 description
=> "CIDR of the (sub) network that is used for migration.",
3082 "with-local-disks" => {
3084 description
=> "Enable live storage migration for local disk",
3087 targetstorage
=> get_standard_option
('pve-storage-id', {
3088 description
=> "Default target storage.",
3090 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3096 description
=> "the task ID.",
3101 my $rpcenv = PVE
::RPCEnvironment
::get
();
3103 my $authuser = $rpcenv->get_user();
3105 my $target = extract_param
($param, 'target');
3107 my $localnode = PVE
::INotify
::nodename
();
3108 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3110 PVE
::Cluster
::check_cfs_quorum
();
3112 PVE
::Cluster
::check_node_exists
($target);
3114 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3116 my $vmid = extract_param
($param, 'vmid');
3118 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3119 if !$param->{online
} && $param->{targetstorage
};
3121 raise_param_exc
({ force
=> "Only root may use this option." })
3122 if $param->{force
} && $authuser ne 'root@pam';
3124 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3125 if $param->{migration_type
} && $authuser ne 'root@pam';
3127 # allow root only until better network permissions are available
3128 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3129 if $param->{migration_network
} && $authuser ne 'root@pam';
3132 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3134 # try to detect errors early
3136 PVE
::QemuConfig-
>check_lock($conf);
3138 if (PVE
::QemuServer
::check_running
($vmid)) {
3139 die "cant migrate running VM without --online\n"
3140 if !$param->{online
};
3143 my $storecfg = PVE
::Storage
::config
();
3145 if( $param->{targetstorage
}) {
3146 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3148 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3151 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3156 my $service = "vm:$vmid";
3158 my $cmd = ['ha-manager', 'migrate', $service, $target];
3160 print "Requesting HA migration for VM $vmid to node $target\n";
3162 PVE
::Tools
::run_command
($cmd);
3167 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3172 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3176 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3179 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3184 __PACKAGE__-
>register_method({
3186 path
=> '{vmid}/monitor',
3190 description
=> "Execute Qemu monitor commands.",
3192 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3193 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3196 additionalProperties
=> 0,
3198 node
=> get_standard_option
('pve-node'),
3199 vmid
=> get_standard_option
('pve-vmid'),
3202 description
=> "The monitor command.",
3206 returns
=> { type
=> 'string'},
3210 my $rpcenv = PVE
::RPCEnvironment
::get
();
3211 my $authuser = $rpcenv->get_user();
3214 my $command = shift;
3215 return $command =~ m/^\s*info(\s+|$)/
3216 || $command =~ m/^\s*help\s*$/;
3219 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3220 if !&$is_ro($param->{command
});
3222 my $vmid = $param->{vmid
};
3224 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3228 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3230 $res = "ERROR: $@" if $@;
3235 __PACKAGE__-
>register_method({
3236 name
=> 'resize_vm',
3237 path
=> '{vmid}/resize',
3241 description
=> "Extend volume size.",
3243 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3246 additionalProperties
=> 0,
3248 node
=> get_standard_option
('pve-node'),
3249 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3250 skiplock
=> get_standard_option
('skiplock'),
3253 description
=> "The disk you want to resize.",
3254 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3258 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3259 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.",
3263 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3269 returns
=> { type
=> 'null'},
3273 my $rpcenv = PVE
::RPCEnvironment
::get
();
3275 my $authuser = $rpcenv->get_user();
3277 my $node = extract_param
($param, 'node');
3279 my $vmid = extract_param
($param, 'vmid');
3281 my $digest = extract_param
($param, 'digest');
3283 my $disk = extract_param
($param, 'disk');
3285 my $sizestr = extract_param
($param, 'size');
3287 my $skiplock = extract_param
($param, 'skiplock');
3288 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3289 if $skiplock && $authuser ne 'root@pam';
3291 my $storecfg = PVE
::Storage
::config
();
3293 my $updatefn = sub {
3295 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3297 die "checksum missmatch (file change by other user?)\n"
3298 if $digest && $digest ne $conf->{digest
};
3299 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3301 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3303 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3305 my (undef, undef, undef, undef, undef, undef, $format) =
3306 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3308 die "can't resize volume: $disk if snapshot exists\n"
3309 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3311 my $volid = $drive->{file
};
3313 die "disk '$disk' has no associated volume\n" if !$volid;
3315 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3317 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3319 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3321 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3322 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3324 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3325 my ($ext, $newsize, $unit) = ($1, $2, $4);
3328 $newsize = $newsize * 1024;
3329 } elsif ($unit eq 'M') {
3330 $newsize = $newsize * 1024 * 1024;
3331 } elsif ($unit eq 'G') {
3332 $newsize = $newsize * 1024 * 1024 * 1024;
3333 } elsif ($unit eq 'T') {
3334 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3337 $newsize += $size if $ext;
3338 $newsize = int($newsize);
3340 die "shrinking disks is not supported\n" if $newsize < $size;
3342 return if $size == $newsize;
3344 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3346 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3348 $drive->{size
} = $newsize;
3349 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3351 PVE
::QemuConfig-
>write_config($vmid, $conf);
3354 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3358 __PACKAGE__-
>register_method({
3359 name
=> 'snapshot_list',
3360 path
=> '{vmid}/snapshot',
3362 description
=> "List all snapshots.",
3364 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3367 protected
=> 1, # qemu pid files are only readable by root
3369 additionalProperties
=> 0,
3371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3372 node
=> get_standard_option
('pve-node'),
3381 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3385 description
=> "Snapshot includes RAM.",
3390 description
=> "Snapshot description.",
3394 description
=> "Snapshot creation time",
3396 renderer
=> 'timestamp',
3400 description
=> "Parent snapshot identifier.",
3406 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3411 my $vmid = $param->{vmid
};
3413 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3414 my $snaphash = $conf->{snapshots
} || {};
3418 foreach my $name (keys %$snaphash) {
3419 my $d = $snaphash->{$name};
3422 snaptime
=> $d->{snaptime
} || 0,
3423 vmstate
=> $d->{vmstate
} ?
1 : 0,
3424 description
=> $d->{description
} || '',
3426 $item->{parent
} = $d->{parent
} if $d->{parent
};
3427 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3431 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3434 digest
=> $conf->{digest
},
3435 running
=> $running,
3436 description
=> "You are here!",
3438 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3440 push @$res, $current;
3445 __PACKAGE__-
>register_method({
3447 path
=> '{vmid}/snapshot',
3451 description
=> "Snapshot a VM.",
3453 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3456 additionalProperties
=> 0,
3458 node
=> get_standard_option
('pve-node'),
3459 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3460 snapname
=> get_standard_option
('pve-snapshot-name'),
3464 description
=> "Save the vmstate",
3469 description
=> "A textual description or comment.",
3475 description
=> "the task ID.",
3480 my $rpcenv = PVE
::RPCEnvironment
::get
();
3482 my $authuser = $rpcenv->get_user();
3484 my $node = extract_param
($param, 'node');
3486 my $vmid = extract_param
($param, 'vmid');
3488 my $snapname = extract_param
($param, 'snapname');
3490 die "unable to use snapshot name 'current' (reserved name)\n"
3491 if $snapname eq 'current';
3494 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3495 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3496 $param->{description
});
3499 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3502 __PACKAGE__-
>register_method({
3503 name
=> 'snapshot_cmd_idx',
3504 path
=> '{vmid}/snapshot/{snapname}',
3511 additionalProperties
=> 0,
3513 vmid
=> get_standard_option
('pve-vmid'),
3514 node
=> get_standard_option
('pve-node'),
3515 snapname
=> get_standard_option
('pve-snapshot-name'),
3524 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3531 push @$res, { cmd
=> 'rollback' };
3532 push @$res, { cmd
=> 'config' };
3537 __PACKAGE__-
>register_method({
3538 name
=> 'update_snapshot_config',
3539 path
=> '{vmid}/snapshot/{snapname}/config',
3543 description
=> "Update snapshot metadata.",
3545 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3548 additionalProperties
=> 0,
3550 node
=> get_standard_option
('pve-node'),
3551 vmid
=> get_standard_option
('pve-vmid'),
3552 snapname
=> get_standard_option
('pve-snapshot-name'),
3556 description
=> "A textual description or comment.",
3560 returns
=> { type
=> 'null' },
3564 my $rpcenv = PVE
::RPCEnvironment
::get
();
3566 my $authuser = $rpcenv->get_user();
3568 my $vmid = extract_param
($param, 'vmid');
3570 my $snapname = extract_param
($param, 'snapname');
3572 return undef if !defined($param->{description
});
3574 my $updatefn = sub {
3576 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3578 PVE
::QemuConfig-
>check_lock($conf);
3580 my $snap = $conf->{snapshots
}->{$snapname};
3582 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3584 $snap->{description
} = $param->{description
} if defined($param->{description
});
3586 PVE
::QemuConfig-
>write_config($vmid, $conf);
3589 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3594 __PACKAGE__-
>register_method({
3595 name
=> 'get_snapshot_config',
3596 path
=> '{vmid}/snapshot/{snapname}/config',
3599 description
=> "Get snapshot configuration",
3601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3604 additionalProperties
=> 0,
3606 node
=> get_standard_option
('pve-node'),
3607 vmid
=> get_standard_option
('pve-vmid'),
3608 snapname
=> get_standard_option
('pve-snapshot-name'),
3611 returns
=> { type
=> "object" },
3615 my $rpcenv = PVE
::RPCEnvironment
::get
();
3617 my $authuser = $rpcenv->get_user();
3619 my $vmid = extract_param
($param, 'vmid');
3621 my $snapname = extract_param
($param, 'snapname');
3623 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3625 my $snap = $conf->{snapshots
}->{$snapname};
3627 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3632 __PACKAGE__-
>register_method({
3634 path
=> '{vmid}/snapshot/{snapname}/rollback',
3638 description
=> "Rollback VM state to specified snapshot.",
3640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3643 additionalProperties
=> 0,
3645 node
=> get_standard_option
('pve-node'),
3646 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3647 snapname
=> get_standard_option
('pve-snapshot-name'),
3652 description
=> "the task ID.",
3657 my $rpcenv = PVE
::RPCEnvironment
::get
();
3659 my $authuser = $rpcenv->get_user();
3661 my $node = extract_param
($param, 'node');
3663 my $vmid = extract_param
($param, 'vmid');
3665 my $snapname = extract_param
($param, 'snapname');
3668 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3669 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3673 # hold migration lock, this makes sure that nobody create replication snapshots
3674 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3677 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3680 __PACKAGE__-
>register_method({
3681 name
=> 'delsnapshot',
3682 path
=> '{vmid}/snapshot/{snapname}',
3686 description
=> "Delete a VM snapshot.",
3688 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3691 additionalProperties
=> 0,
3693 node
=> get_standard_option
('pve-node'),
3694 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3695 snapname
=> get_standard_option
('pve-snapshot-name'),
3699 description
=> "For removal from config file, even if removing disk snapshots fails.",
3705 description
=> "the task ID.",
3710 my $rpcenv = PVE
::RPCEnvironment
::get
();
3712 my $authuser = $rpcenv->get_user();
3714 my $node = extract_param
($param, 'node');
3716 my $vmid = extract_param
($param, 'vmid');
3718 my $snapname = extract_param
($param, 'snapname');
3721 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3722 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3725 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3728 __PACKAGE__-
>register_method({
3730 path
=> '{vmid}/template',
3734 description
=> "Create a Template.",
3736 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3737 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3740 additionalProperties
=> 0,
3742 node
=> get_standard_option
('pve-node'),
3743 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3747 description
=> "If you want to convert only 1 disk to base image.",
3748 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3753 returns
=> { type
=> 'null'},
3757 my $rpcenv = PVE
::RPCEnvironment
::get
();
3759 my $authuser = $rpcenv->get_user();
3761 my $node = extract_param
($param, 'node');
3763 my $vmid = extract_param
($param, 'vmid');
3765 my $disk = extract_param
($param, 'disk');
3767 my $updatefn = sub {
3769 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3771 PVE
::QemuConfig-
>check_lock($conf);
3773 die "unable to create template, because VM contains snapshots\n"
3774 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3776 die "you can't convert a template to a template\n"
3777 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3779 die "you can't convert a VM to template if VM is running\n"
3780 if PVE
::QemuServer
::check_running
($vmid);
3783 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3786 $conf->{template
} = 1;
3787 PVE
::QemuConfig-
>write_config($vmid, $conf);
3789 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3792 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);