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 if (my $snapname = $param->{snapshot
}) {
858 my $snapshot = $conf->{snapshots
}->{$snapname};
859 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
861 $snapshot->{digest
} = $conf->{digest
}; # keep file digest for API
866 delete $conf->{snapshots
};
868 if (!$param->{current
}) {
869 foreach my $opt (keys %{$conf->{pending
}}) {
870 next if $opt eq 'delete';
871 my $value = $conf->{pending
}->{$opt};
872 next if ref($value); # just to be sure
873 $conf->{$opt} = $value;
875 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
876 foreach my $opt (keys %$pending_delete_hash) {
877 delete $conf->{$opt} if $conf->{$opt};
881 delete $conf->{pending
};
883 # hide cloudinit password
884 if ($conf->{cipassword
}) {
885 $conf->{cipassword
} = '**********';
891 __PACKAGE__-
>register_method({
892 name
=> 'vm_pending',
893 path
=> '{vmid}/pending',
896 description
=> "Get virtual machine configuration, including pending changes.",
898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
901 additionalProperties
=> 0,
903 node
=> get_standard_option
('pve-node'),
904 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
913 description
=> "Configuration option name.",
917 description
=> "Current value.",
922 description
=> "Pending value.",
927 description
=> "Indicates a pending delete request if present and not 0. " .
928 "The value 2 indicates a force-delete request.",
940 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
942 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
946 foreach my $opt (keys %$conf) {
947 next if ref($conf->{$opt});
948 my $item = { key
=> $opt };
949 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
950 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
951 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
953 # hide cloudinit password
954 if ($opt eq 'cipassword') {
955 $item->{value
} = '**********' if defined($item->{value
});
956 # the trailing space so that the pending string is different
957 $item->{pending
} = '********** ' if defined($item->{pending
});
962 foreach my $opt (keys %{$conf->{pending
}}) {
963 next if $opt eq 'delete';
964 next if ref($conf->{pending
}->{$opt}); # just to be sure
965 next if defined($conf->{$opt});
966 my $item = { key
=> $opt };
967 $item->{pending
} = $conf->{pending
}->{$opt};
969 # hide cloudinit password
970 if ($opt eq 'cipassword') {
971 $item->{pending
} = '**********' if defined($item->{pending
});
976 while (my ($opt, $force) = each %$pending_delete_hash) {
977 next if $conf->{pending
}->{$opt}; # just to be sure
978 next if $conf->{$opt};
979 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
986 # POST/PUT {vmid}/config implementation
988 # The original API used PUT (idempotent) an we assumed that all operations
989 # are fast. But it turned out that almost any configuration change can
990 # involve hot-plug actions, or disk alloc/free. Such actions can take long
991 # time to complete and have side effects (not idempotent).
993 # The new implementation uses POST and forks a worker process. We added
994 # a new option 'background_delay'. If specified we wait up to
995 # 'background_delay' second for the worker task to complete. It returns null
996 # if the task is finished within that time, else we return the UPID.
998 my $update_vm_api = sub {
999 my ($param, $sync) = @_;
1001 my $rpcenv = PVE
::RPCEnvironment
::get
();
1003 my $authuser = $rpcenv->get_user();
1005 my $node = extract_param
($param, 'node');
1007 my $vmid = extract_param
($param, 'vmid');
1009 my $digest = extract_param
($param, 'digest');
1011 my $background_delay = extract_param
($param, 'background_delay');
1013 if (defined(my $cipassword = $param->{cipassword
})) {
1014 # Same logic as in cloud-init (but with the regex fixed...)
1015 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1016 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1019 my @paramarr = (); # used for log message
1020 foreach my $key (sort keys %$param) {
1021 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1022 push @paramarr, "-$key", $value;
1025 my $skiplock = extract_param
($param, 'skiplock');
1026 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1027 if $skiplock && $authuser ne 'root@pam';
1029 my $delete_str = extract_param
($param, 'delete');
1031 my $revert_str = extract_param
($param, 'revert');
1033 my $force = extract_param
($param, 'force');
1035 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1036 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1037 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1040 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1042 my $storecfg = PVE
::Storage
::config
();
1044 my $defaults = PVE
::QemuServer
::load_defaults
();
1046 &$resolve_cdrom_alias($param);
1048 # now try to verify all parameters
1051 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1052 if (!PVE
::QemuServer
::option_exists
($opt)) {
1053 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1056 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1057 "-revert $opt' at the same time" })
1058 if defined($param->{$opt});
1060 $revert->{$opt} = 1;
1064 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1065 $opt = 'ide2' if $opt eq 'cdrom';
1067 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1068 "-delete $opt' at the same time" })
1069 if defined($param->{$opt});
1071 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1072 "-revert $opt' at the same time" })
1075 if (!PVE
::QemuServer
::option_exists
($opt)) {
1076 raise_param_exc
({ delete => "unknown option '$opt'" });
1082 my $repl_conf = PVE
::ReplicationConfig-
>new();
1083 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1084 my $check_replication = sub {
1086 return if !$is_replicated;
1087 my $volid = $drive->{file
};
1088 return if !$volid || !($drive->{replicate
}//1);
1089 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1090 my ($storeid, $format);
1091 if ($volid =~ $NEW_DISK_RE) {
1093 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1095 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1096 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1098 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1099 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1100 return if $scfg->{shared
};
1101 die "cannot add non-replicatable volume to a replicated VM\n";
1104 foreach my $opt (keys %$param) {
1105 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1106 # cleanup drive path
1107 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1108 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1109 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1110 $check_replication->($drive);
1111 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1112 } elsif ($opt =~ m/^net(\d+)$/) {
1114 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1115 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1116 } elsif ($opt eq 'vmgenid') {
1117 if ($param->{$opt} eq '1') {
1118 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1123 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1125 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1127 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1129 my $updatefn = sub {
1131 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1133 die "checksum missmatch (file change by other user?)\n"
1134 if $digest && $digest ne $conf->{digest
};
1136 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1138 foreach my $opt (keys %$revert) {
1139 if (defined($conf->{$opt})) {
1140 $param->{$opt} = $conf->{$opt};
1141 } elsif (defined($conf->{pending
}->{$opt})) {
1146 if ($param->{memory
} || defined($param->{balloon
})) {
1147 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1148 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1150 die "balloon value too large (must be smaller than assigned memory)\n"
1151 if $balloon && $balloon > $maxmem;
1154 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1158 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1160 # write updates to pending section
1162 my $modified = {}; # record what $option we modify
1164 foreach my $opt (@delete) {
1165 $modified->{$opt} = 1;
1166 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1167 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1168 warn "cannot delete '$opt' - not set in current configuration!\n";
1169 $modified->{$opt} = 0;
1173 if ($opt =~ m/^unused/) {
1174 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1175 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1176 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1177 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1178 delete $conf->{$opt};
1179 PVE
::QemuConfig-
>write_config($vmid, $conf);
1181 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1182 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1183 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1184 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1185 if defined($conf->{pending
}->{$opt});
1186 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1187 PVE
::QemuConfig-
>write_config($vmid, $conf);
1189 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1190 PVE
::QemuConfig-
>write_config($vmid, $conf);
1194 foreach my $opt (keys %$param) { # add/change
1195 $modified->{$opt} = 1;
1196 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1197 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1199 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1201 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1202 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1203 # FIXME: cloudinit: CDROM or Disk?
1204 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1205 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1209 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1210 if defined($conf->{pending
}->{$opt});
1212 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1214 $conf->{pending
}->{$opt} = $param->{$opt};
1216 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1217 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 # remove pending changes when nothing changed
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1223 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1225 return if !scalar(keys %{$conf->{pending
}});
1227 my $running = PVE
::QemuServer
::check_running
($vmid);
1229 # apply pending changes
1231 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1235 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1236 raise_param_exc
($errors) if scalar(keys %$errors);
1238 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1248 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1250 if ($background_delay) {
1252 # Note: It would be better to do that in the Event based HTTPServer
1253 # to avoid blocking call to sleep.
1255 my $end_time = time() + $background_delay;
1257 my $task = PVE
::Tools
::upid_decode
($upid);
1260 while (time() < $end_time) {
1261 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1263 sleep(1); # this gets interrupted when child process ends
1267 my $status = PVE
::Tools
::upid_read_status
($upid);
1268 return undef if $status eq 'OK';
1277 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1280 my $vm_config_perm_list = [
1285 'VM.Config.Network',
1287 'VM.Config.Options',
1290 __PACKAGE__-
>register_method({
1291 name
=> 'update_vm_async',
1292 path
=> '{vmid}/config',
1296 description
=> "Set virtual machine options (asynchrounous API).",
1298 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1301 additionalProperties
=> 0,
1302 properties
=> PVE
::QemuServer
::json_config_properties
(
1304 node
=> get_standard_option
('pve-node'),
1305 vmid
=> get_standard_option
('pve-vmid'),
1306 skiplock
=> get_standard_option
('skiplock'),
1308 type
=> 'string', format
=> 'pve-configid-list',
1309 description
=> "A list of settings you want to delete.",
1313 type
=> 'string', format
=> 'pve-configid-list',
1314 description
=> "Revert a pending change.",
1319 description
=> $opt_force_description,
1321 requires
=> 'delete',
1325 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1329 background_delay
=> {
1331 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1342 code
=> $update_vm_api,
1345 __PACKAGE__-
>register_method({
1346 name
=> 'update_vm',
1347 path
=> '{vmid}/config',
1351 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1353 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1356 additionalProperties
=> 0,
1357 properties
=> PVE
::QemuServer
::json_config_properties
(
1359 node
=> get_standard_option
('pve-node'),
1360 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1361 skiplock
=> get_standard_option
('skiplock'),
1363 type
=> 'string', format
=> 'pve-configid-list',
1364 description
=> "A list of settings you want to delete.",
1368 type
=> 'string', format
=> 'pve-configid-list',
1369 description
=> "Revert a pending change.",
1374 description
=> $opt_force_description,
1376 requires
=> 'delete',
1380 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1386 returns
=> { type
=> 'null' },
1389 &$update_vm_api($param, 1);
1395 __PACKAGE__-
>register_method({
1396 name
=> 'destroy_vm',
1401 description
=> "Destroy the vm (also delete all used/owned volumes).",
1403 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1406 additionalProperties
=> 0,
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1410 skiplock
=> get_standard_option
('skiplock'),
1419 my $rpcenv = PVE
::RPCEnvironment
::get
();
1421 my $authuser = $rpcenv->get_user();
1423 my $vmid = $param->{vmid
};
1425 my $skiplock = $param->{skiplock
};
1426 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1427 if $skiplock && $authuser ne 'root@pam';
1430 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1432 my $storecfg = PVE
::Storage
::config
();
1434 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1436 die "unable to remove VM $vmid - used in HA resources\n"
1437 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1439 # do not allow destroy if there are replication jobs
1440 my $repl_conf = PVE
::ReplicationConfig-
>new();
1441 $repl_conf->check_for_existing_jobs($vmid);
1443 # early tests (repeat after locking)
1444 die "VM $vmid is running - destroy failed\n"
1445 if PVE
::QemuServer
::check_running
($vmid);
1450 syslog
('info', "destroy VM $vmid: $upid\n");
1452 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1454 PVE
::AccessControl
::remove_vm_access
($vmid);
1456 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1459 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1462 __PACKAGE__-
>register_method({
1464 path
=> '{vmid}/unlink',
1468 description
=> "Unlink/delete disk images.",
1470 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1473 additionalProperties
=> 0,
1475 node
=> get_standard_option
('pve-node'),
1476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1478 type
=> 'string', format
=> 'pve-configid-list',
1479 description
=> "A list of disk IDs you want to delete.",
1483 description
=> $opt_force_description,
1488 returns
=> { type
=> 'null'},
1492 $param->{delete} = extract_param
($param, 'idlist');
1494 __PACKAGE__-
>update_vm($param);
1501 __PACKAGE__-
>register_method({
1503 path
=> '{vmid}/vncproxy',
1507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1509 description
=> "Creates a TCP VNC proxy connections.",
1511 additionalProperties
=> 0,
1513 node
=> get_standard_option
('pve-node'),
1514 vmid
=> get_standard_option
('pve-vmid'),
1518 description
=> "starts websockify instead of vncproxy",
1523 additionalProperties
=> 0,
1525 user
=> { type
=> 'string' },
1526 ticket
=> { type
=> 'string' },
1527 cert
=> { type
=> 'string' },
1528 port
=> { type
=> 'integer' },
1529 upid
=> { type
=> 'string' },
1535 my $rpcenv = PVE
::RPCEnvironment
::get
();
1537 my $authuser = $rpcenv->get_user();
1539 my $vmid = $param->{vmid
};
1540 my $node = $param->{node
};
1541 my $websocket = $param->{websocket
};
1543 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1544 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1546 my $authpath = "/vms/$vmid";
1548 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1550 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1556 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1557 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1558 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1559 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1560 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1562 $family = PVE
::Tools
::get_host_address_family
($node);
1565 my $port = PVE
::Tools
::next_vnc_port
($family);
1572 syslog
('info', "starting vnc proxy $upid\n");
1578 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1580 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1581 '-timeout', $timeout, '-authpath', $authpath,
1582 '-perm', 'Sys.Console'];
1584 if ($param->{websocket
}) {
1585 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1586 push @$cmd, '-notls', '-listen', 'localhost';
1589 push @$cmd, '-c', @$remcmd, @$termcmd;
1591 PVE
::Tools
::run_command
($cmd);
1595 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1597 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1599 my $sock = IO
::Socket
::IP-
>new(
1604 GetAddrInfoFlags
=> 0,
1605 ) or die "failed to create socket: $!\n";
1606 # Inside the worker we shouldn't have any previous alarms
1607 # running anyway...:
1609 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1611 accept(my $cli, $sock) or die "connection failed: $!\n";
1614 if (PVE
::Tools
::run_command
($cmd,
1615 output
=> '>&'.fileno($cli),
1616 input
=> '<&'.fileno($cli),
1619 die "Failed to run vncproxy.\n";
1626 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1628 PVE
::Tools
::wait_for_vnc_port
($port);
1639 __PACKAGE__-
>register_method({
1640 name
=> 'termproxy',
1641 path
=> '{vmid}/termproxy',
1645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1647 description
=> "Creates a TCP proxy connections.",
1649 additionalProperties
=> 0,
1651 node
=> get_standard_option
('pve-node'),
1652 vmid
=> get_standard_option
('pve-vmid'),
1656 enum
=> [qw(serial0 serial1 serial2 serial3)],
1657 description
=> "opens a serial terminal (defaults to display)",
1662 additionalProperties
=> 0,
1664 user
=> { type
=> 'string' },
1665 ticket
=> { type
=> 'string' },
1666 port
=> { type
=> 'integer' },
1667 upid
=> { type
=> 'string' },
1673 my $rpcenv = PVE
::RPCEnvironment
::get
();
1675 my $authuser = $rpcenv->get_user();
1677 my $vmid = $param->{vmid
};
1678 my $node = $param->{node
};
1679 my $serial = $param->{serial
};
1681 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1683 if (!defined($serial)) {
1684 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1685 $serial = $conf->{vga
};
1689 my $authpath = "/vms/$vmid";
1691 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1696 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1697 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1698 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1699 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1700 push @$remcmd, '--';
1702 $family = PVE
::Tools
::get_host_address_family
($node);
1705 my $port = PVE
::Tools
::next_vnc_port
($family);
1707 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1708 push @$termcmd, '-iface', $serial if $serial;
1713 syslog
('info', "starting qemu termproxy $upid\n");
1715 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1716 '--perm', 'VM.Console', '--'];
1717 push @$cmd, @$remcmd, @$termcmd;
1719 PVE
::Tools
::run_command
($cmd);
1722 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1724 PVE
::Tools
::wait_for_vnc_port
($port);
1734 __PACKAGE__-
>register_method({
1735 name
=> 'vncwebsocket',
1736 path
=> '{vmid}/vncwebsocket',
1739 description
=> "You also need to pass a valid ticket (vncticket).",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1742 description
=> "Opens a weksocket for VNC traffic.",
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid'),
1749 description
=> "Ticket from previous call to vncproxy.",
1754 description
=> "Port number returned by previous vncproxy call.",
1764 port
=> { type
=> 'string' },
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $vmid = $param->{vmid
};
1775 my $node = $param->{node
};
1777 my $authpath = "/vms/$vmid";
1779 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1781 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1783 # Note: VNC ports are acessible from outside, so we do not gain any
1784 # security if we verify that $param->{port} belongs to VM $vmid. This
1785 # check is done by verifying the VNC ticket (inside VNC protocol).
1787 my $port = $param->{port
};
1789 return { port
=> $port };
1792 __PACKAGE__-
>register_method({
1793 name
=> 'spiceproxy',
1794 path
=> '{vmid}/spiceproxy',
1799 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1801 description
=> "Returns a SPICE configuration to connect to the VM.",
1803 additionalProperties
=> 0,
1805 node
=> get_standard_option
('pve-node'),
1806 vmid
=> get_standard_option
('pve-vmid'),
1807 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1810 returns
=> get_standard_option
('remote-viewer-config'),
1814 my $rpcenv = PVE
::RPCEnvironment
::get
();
1816 my $authuser = $rpcenv->get_user();
1818 my $vmid = $param->{vmid
};
1819 my $node = $param->{node
};
1820 my $proxy = $param->{proxy
};
1822 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1823 my $title = "VM $vmid";
1824 $title .= " - ". $conf->{name
} if $conf->{name
};
1826 my $port = PVE
::QemuServer
::spice_port
($vmid);
1828 my ($ticket, undef, $remote_viewer_config) =
1829 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1831 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1832 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1834 return $remote_viewer_config;
1837 __PACKAGE__-
>register_method({
1839 path
=> '{vmid}/status',
1842 description
=> "Directory index",
1847 additionalProperties
=> 0,
1849 node
=> get_standard_option
('pve-node'),
1850 vmid
=> get_standard_option
('pve-vmid'),
1858 subdir
=> { type
=> 'string' },
1861 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1867 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1870 { subdir
=> 'current' },
1871 { subdir
=> 'start' },
1872 { subdir
=> 'stop' },
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_status',
1880 path
=> '{vmid}/status/current',
1883 protected
=> 1, # qemu pid files are only readable by root
1884 description
=> "Get virtual machine status.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid'),
1898 %$PVE::QemuServer
::vmstatus_return_properties
,
1900 description
=> "HA manager service status.",
1904 description
=> "Qemu VGA configuration supports spice.",
1909 description
=> "Qemu GuestAgent enabled in config.",
1919 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1921 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1922 my $status = $vmstatus->{$param->{vmid
}};
1924 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1926 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1927 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1932 __PACKAGE__-
>register_method({
1934 path
=> '{vmid}/status/start',
1938 description
=> "Start virtual machine.",
1940 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1943 additionalProperties
=> 0,
1945 node
=> get_standard_option
('pve-node'),
1946 vmid
=> get_standard_option
('pve-vmid',
1947 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1948 skiplock
=> get_standard_option
('skiplock'),
1949 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1950 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1953 enum
=> ['secure', 'insecure'],
1954 description
=> "Migration traffic is encrypted using an SSH " .
1955 "tunnel by default. On secure, completely private networks " .
1956 "this can be disabled to increase performance.",
1959 migration_network
=> {
1960 type
=> 'string', format
=> 'CIDR',
1961 description
=> "CIDR of the (sub) network that is used for migration.",
1964 machine
=> get_standard_option
('pve-qm-machine'),
1966 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1978 my $rpcenv = PVE
::RPCEnvironment
::get
();
1980 my $authuser = $rpcenv->get_user();
1982 my $node = extract_param
($param, 'node');
1984 my $vmid = extract_param
($param, 'vmid');
1986 my $machine = extract_param
($param, 'machine');
1988 my $stateuri = extract_param
($param, 'stateuri');
1989 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1990 if $stateuri && $authuser ne 'root@pam';
1992 my $skiplock = extract_param
($param, 'skiplock');
1993 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1994 if $skiplock && $authuser ne 'root@pam';
1996 my $migratedfrom = extract_param
($param, 'migratedfrom');
1997 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1998 if $migratedfrom && $authuser ne 'root@pam';
2000 my $migration_type = extract_param
($param, 'migration_type');
2001 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2002 if $migration_type && $authuser ne 'root@pam';
2004 my $migration_network = extract_param
($param, 'migration_network');
2005 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2006 if $migration_network && $authuser ne 'root@pam';
2008 my $targetstorage = extract_param
($param, 'targetstorage');
2009 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
2010 if $targetstorage && $authuser ne 'root@pam';
2012 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2013 if $targetstorage && !$migratedfrom;
2015 # read spice ticket from STDIN
2017 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2018 if (defined(my $line = <STDIN
>)) {
2020 $spice_ticket = $line;
2024 PVE
::Cluster
::check_cfs_quorum
();
2026 my $storecfg = PVE
::Storage
::config
();
2028 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2029 $rpcenv->{type
} ne 'ha') {
2034 my $service = "vm:$vmid";
2036 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2038 print "Requesting HA start for VM $vmid\n";
2040 PVE
::Tools
::run_command
($cmd);
2045 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2052 syslog
('info', "start VM $vmid: $upid\n");
2054 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2055 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2060 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2064 __PACKAGE__-
>register_method({
2066 path
=> '{vmid}/status/stop',
2070 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2071 "is akin to pulling the power plug of a running computer and may damage the VM data",
2073 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2076 additionalProperties
=> 0,
2078 node
=> get_standard_option
('pve-node'),
2079 vmid
=> get_standard_option
('pve-vmid',
2080 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2081 skiplock
=> get_standard_option
('skiplock'),
2082 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2084 description
=> "Wait maximal timeout seconds.",
2090 description
=> "Do not deactivate storage volumes.",
2103 my $rpcenv = PVE
::RPCEnvironment
::get
();
2105 my $authuser = $rpcenv->get_user();
2107 my $node = extract_param
($param, 'node');
2109 my $vmid = extract_param
($param, 'vmid');
2111 my $skiplock = extract_param
($param, 'skiplock');
2112 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2113 if $skiplock && $authuser ne 'root@pam';
2115 my $keepActive = extract_param
($param, 'keepActive');
2116 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2117 if $keepActive && $authuser ne 'root@pam';
2119 my $migratedfrom = extract_param
($param, 'migratedfrom');
2120 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2121 if $migratedfrom && $authuser ne 'root@pam';
2124 my $storecfg = PVE
::Storage
::config
();
2126 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2131 my $service = "vm:$vmid";
2133 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2135 print "Requesting HA stop for VM $vmid\n";
2137 PVE
::Tools
::run_command
($cmd);
2142 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2148 syslog
('info', "stop VM $vmid: $upid\n");
2150 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2151 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2156 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2160 __PACKAGE__-
>register_method({
2162 path
=> '{vmid}/status/reset',
2166 description
=> "Reset virtual machine.",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid',
2175 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2176 skiplock
=> get_standard_option
('skiplock'),
2185 my $rpcenv = PVE
::RPCEnvironment
::get
();
2187 my $authuser = $rpcenv->get_user();
2189 my $node = extract_param
($param, 'node');
2191 my $vmid = extract_param
($param, 'vmid');
2193 my $skiplock = extract_param
($param, 'skiplock');
2194 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2195 if $skiplock && $authuser ne 'root@pam';
2197 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2202 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2207 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2210 __PACKAGE__-
>register_method({
2211 name
=> 'vm_shutdown',
2212 path
=> '{vmid}/status/shutdown',
2216 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2217 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2219 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2222 additionalProperties
=> 0,
2224 node
=> get_standard_option
('pve-node'),
2225 vmid
=> get_standard_option
('pve-vmid',
2226 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2227 skiplock
=> get_standard_option
('skiplock'),
2229 description
=> "Wait maximal timeout seconds.",
2235 description
=> "Make sure the VM stops.",
2241 description
=> "Do not deactivate storage volumes.",
2254 my $rpcenv = PVE
::RPCEnvironment
::get
();
2256 my $authuser = $rpcenv->get_user();
2258 my $node = extract_param
($param, 'node');
2260 my $vmid = extract_param
($param, 'vmid');
2262 my $skiplock = extract_param
($param, 'skiplock');
2263 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2264 if $skiplock && $authuser ne 'root@pam';
2266 my $keepActive = extract_param
($param, 'keepActive');
2267 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2268 if $keepActive && $authuser ne 'root@pam';
2270 my $storecfg = PVE
::Storage
::config
();
2274 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2275 # otherwise, we will infer a shutdown command, but run into the timeout,
2276 # then when the vm is resumed, it will instantly shutdown
2278 # checking the qmp status here to get feedback to the gui/cli/api
2279 # and the status query should not take too long
2282 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2286 if (!$err && $qmpstatus->{status
} eq "paused") {
2287 if ($param->{forceStop
}) {
2288 warn "VM is paused - stop instead of shutdown\n";
2291 die "VM is paused - cannot shutdown\n";
2295 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2296 ($rpcenv->{type
} ne 'ha')) {
2301 my $service = "vm:$vmid";
2303 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2305 print "Requesting HA stop for VM $vmid\n";
2307 PVE
::Tools
::run_command
($cmd);
2312 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2319 syslog
('info', "shutdown VM $vmid: $upid\n");
2321 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2322 $shutdown, $param->{forceStop
}, $keepActive);
2327 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2331 __PACKAGE__-
>register_method({
2332 name
=> 'vm_suspend',
2333 path
=> '{vmid}/status/suspend',
2337 description
=> "Suspend virtual machine.",
2339 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2342 additionalProperties
=> 0,
2344 node
=> get_standard_option
('pve-node'),
2345 vmid
=> get_standard_option
('pve-vmid',
2346 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2347 skiplock
=> get_standard_option
('skiplock'),
2356 my $rpcenv = PVE
::RPCEnvironment
::get
();
2358 my $authuser = $rpcenv->get_user();
2360 my $node = extract_param
($param, 'node');
2362 my $vmid = extract_param
($param, 'vmid');
2364 my $skiplock = extract_param
($param, 'skiplock');
2365 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2366 if $skiplock && $authuser ne 'root@pam';
2368 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2373 syslog
('info', "suspend VM $vmid: $upid\n");
2375 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2380 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2383 __PACKAGE__-
>register_method({
2384 name
=> 'vm_resume',
2385 path
=> '{vmid}/status/resume',
2389 description
=> "Resume virtual machine.",
2391 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2394 additionalProperties
=> 0,
2396 node
=> get_standard_option
('pve-node'),
2397 vmid
=> get_standard_option
('pve-vmid',
2398 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2399 skiplock
=> get_standard_option
('skiplock'),
2400 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2410 my $rpcenv = PVE
::RPCEnvironment
::get
();
2412 my $authuser = $rpcenv->get_user();
2414 my $node = extract_param
($param, 'node');
2416 my $vmid = extract_param
($param, 'vmid');
2418 my $skiplock = extract_param
($param, 'skiplock');
2419 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2420 if $skiplock && $authuser ne 'root@pam';
2422 my $nocheck = extract_param
($param, 'nocheck');
2424 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2429 syslog
('info', "resume VM $vmid: $upid\n");
2431 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2436 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2439 __PACKAGE__-
>register_method({
2440 name
=> 'vm_sendkey',
2441 path
=> '{vmid}/sendkey',
2445 description
=> "Send key event to virtual machine.",
2447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2450 additionalProperties
=> 0,
2452 node
=> get_standard_option
('pve-node'),
2453 vmid
=> get_standard_option
('pve-vmid',
2454 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2455 skiplock
=> get_standard_option
('skiplock'),
2457 description
=> "The key (qemu monitor encoding).",
2462 returns
=> { type
=> 'null'},
2466 my $rpcenv = PVE
::RPCEnvironment
::get
();
2468 my $authuser = $rpcenv->get_user();
2470 my $node = extract_param
($param, 'node');
2472 my $vmid = extract_param
($param, 'vmid');
2474 my $skiplock = extract_param
($param, 'skiplock');
2475 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2476 if $skiplock && $authuser ne 'root@pam';
2478 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2483 __PACKAGE__-
>register_method({
2484 name
=> 'vm_feature',
2485 path
=> '{vmid}/feature',
2489 description
=> "Check if feature for virtual machine is available.",
2491 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2494 additionalProperties
=> 0,
2496 node
=> get_standard_option
('pve-node'),
2497 vmid
=> get_standard_option
('pve-vmid'),
2499 description
=> "Feature to check.",
2501 enum
=> [ 'snapshot', 'clone', 'copy' ],
2503 snapname
=> get_standard_option
('pve-snapshot-name', {
2511 hasFeature
=> { type
=> 'boolean' },
2514 items
=> { type
=> 'string' },
2521 my $node = extract_param
($param, 'node');
2523 my $vmid = extract_param
($param, 'vmid');
2525 my $snapname = extract_param
($param, 'snapname');
2527 my $feature = extract_param
($param, 'feature');
2529 my $running = PVE
::QemuServer
::check_running
($vmid);
2531 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2534 my $snap = $conf->{snapshots
}->{$snapname};
2535 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2538 my $storecfg = PVE
::Storage
::config
();
2540 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2541 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2544 hasFeature
=> $hasFeature,
2545 nodes
=> [ keys %$nodelist ],
2549 __PACKAGE__-
>register_method({
2551 path
=> '{vmid}/clone',
2555 description
=> "Create a copy of virtual machine/template.",
2557 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2558 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2559 "'Datastore.AllocateSpace' on any used storage.",
2562 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2564 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2565 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2570 additionalProperties
=> 0,
2572 node
=> get_standard_option
('pve-node'),
2573 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2574 newid
=> get_standard_option
('pve-vmid', {
2575 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2576 description
=> 'VMID for the clone.' }),
2579 type
=> 'string', format
=> 'dns-name',
2580 description
=> "Set a name for the new VM.",
2585 description
=> "Description for the new VM.",
2589 type
=> 'string', format
=> 'pve-poolid',
2590 description
=> "Add the new VM to the specified pool.",
2592 snapname
=> get_standard_option
('pve-snapshot-name', {
2595 storage
=> get_standard_option
('pve-storage-id', {
2596 description
=> "Target storage for full clone.",
2600 description
=> "Target format for file storage. Only valid for full clone.",
2603 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2608 description
=> "Create a full copy of all disks. This is always done when " .
2609 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2611 target
=> get_standard_option
('pve-node', {
2612 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2623 my $rpcenv = PVE
::RPCEnvironment
::get
();
2625 my $authuser = $rpcenv->get_user();
2627 my $node = extract_param
($param, 'node');
2629 my $vmid = extract_param
($param, 'vmid');
2631 my $newid = extract_param
($param, 'newid');
2633 my $pool = extract_param
($param, 'pool');
2635 if (defined($pool)) {
2636 $rpcenv->check_pool_exist($pool);
2639 my $snapname = extract_param
($param, 'snapname');
2641 my $storage = extract_param
($param, 'storage');
2643 my $format = extract_param
($param, 'format');
2645 my $target = extract_param
($param, 'target');
2647 my $localnode = PVE
::INotify
::nodename
();
2649 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2651 PVE
::Cluster
::check_node_exists
($target) if $target;
2653 my $storecfg = PVE
::Storage
::config
();
2656 # check if storage is enabled on local node
2657 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2659 # check if storage is available on target node
2660 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2661 # clone only works if target storage is shared
2662 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2663 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2667 PVE
::Cluster
::check_cfs_quorum
();
2669 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2671 # exclusive lock if VM is running - else shared lock is enough;
2672 my $shared_lock = $running ?
0 : 1;
2676 # do all tests after lock
2677 # we also try to do all tests before we fork the worker
2679 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2681 PVE
::QemuConfig-
>check_lock($conf);
2683 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2685 die "unexpected state change\n" if $verify_running != $running;
2687 die "snapshot '$snapname' does not exist\n"
2688 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2690 my $full = extract_param
($param, 'full');
2691 if (!defined($full)) {
2692 $full = !PVE
::QemuConfig-
>is_template($conf);
2695 die "parameter 'storage' not allowed for linked clones\n"
2696 if defined($storage) && !$full;
2698 die "parameter 'format' not allowed for linked clones\n"
2699 if defined($format) && !$full;
2701 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2703 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2705 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2707 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2709 die "unable to create VM $newid: config file already exists\n"
2712 my $newconf = { lock => 'clone' };
2717 foreach my $opt (keys %$oldconf) {
2718 my $value = $oldconf->{$opt};
2720 # do not copy snapshot related info
2721 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2722 $opt eq 'vmstate' || $opt eq 'snapstate';
2724 # no need to copy unused images, because VMID(owner) changes anyways
2725 next if $opt =~ m/^unused\d+$/;
2727 # always change MAC! address
2728 if ($opt =~ m/^net(\d+)$/) {
2729 my $net = PVE
::QemuServer
::parse_net
($value);
2730 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2731 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2732 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2733 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2734 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2735 die "unable to parse drive options for '$opt'\n" if !$drive;
2736 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2737 $newconf->{$opt} = $value; # simply copy configuration
2739 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2740 die "Full clone feature is not supported for drive '$opt'\n"
2741 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2742 $fullclone->{$opt} = 1;
2744 # not full means clone instead of copy
2745 die "Linked clone feature is not supported for drive '$opt'\n"
2746 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2748 $drives->{$opt} = $drive;
2749 push @$vollist, $drive->{file
};
2752 # copy everything else
2753 $newconf->{$opt} = $value;
2757 # auto generate a new uuid
2758 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2759 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2760 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2762 # auto generate a new vmgenid if the option was set
2763 if ($newconf->{vmgenid
}) {
2764 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2767 delete $newconf->{template
};
2769 if ($param->{name
}) {
2770 $newconf->{name
} = $param->{name
};
2772 if ($oldconf->{name
}) {
2773 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2775 $newconf->{name
} = "Copy-of-VM-$vmid";
2779 if ($param->{description
}) {
2780 $newconf->{description
} = $param->{description
};
2783 # create empty/temp config - this fails if VM already exists on other node
2784 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2789 my $newvollist = [];
2796 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2798 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2800 my $total_jobs = scalar(keys %{$drives});
2803 foreach my $opt (keys %$drives) {
2804 my $drive = $drives->{$opt};
2805 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2807 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2808 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2809 $jobs, $skipcomplete, $oldconf->{agent
});
2811 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2813 PVE
::QemuConfig-
>write_config($newid, $newconf);
2817 delete $newconf->{lock};
2819 # do not write pending changes
2820 if (my @changes = keys %{$newconf->{pending
}}) {
2821 my $pending = join(',', @changes);
2822 warn "found pending changes for '$pending', discarding for clone\n";
2823 delete $newconf->{pending
};
2826 PVE
::QemuConfig-
>write_config($newid, $newconf);
2829 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2830 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2831 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2833 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2834 die "Failed to move config to node '$target' - rename failed: $!\n"
2835 if !rename($conffile, $newconffile);
2838 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2843 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2845 sleep 1; # some storage like rbd need to wait before release volume - really?
2847 foreach my $volid (@$newvollist) {
2848 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2851 die "clone failed: $err";
2857 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2859 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2862 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2863 # Aquire exclusive lock lock for $newid
2864 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2869 __PACKAGE__-
>register_method({
2870 name
=> 'move_vm_disk',
2871 path
=> '{vmid}/move_disk',
2875 description
=> "Move volume to different storage.",
2877 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2879 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2880 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2884 additionalProperties
=> 0,
2886 node
=> get_standard_option
('pve-node'),
2887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2890 description
=> "The disk you want to move.",
2891 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2893 storage
=> get_standard_option
('pve-storage-id', {
2894 description
=> "Target storage.",
2895 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2899 description
=> "Target Format.",
2900 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2905 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2911 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2919 description
=> "the task ID.",
2924 my $rpcenv = PVE
::RPCEnvironment
::get
();
2926 my $authuser = $rpcenv->get_user();
2928 my $node = extract_param
($param, 'node');
2930 my $vmid = extract_param
($param, 'vmid');
2932 my $digest = extract_param
($param, 'digest');
2934 my $disk = extract_param
($param, 'disk');
2936 my $storeid = extract_param
($param, 'storage');
2938 my $format = extract_param
($param, 'format');
2940 my $storecfg = PVE
::Storage
::config
();
2942 my $updatefn = sub {
2944 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2946 PVE
::QemuConfig-
>check_lock($conf);
2948 die "checksum missmatch (file change by other user?)\n"
2949 if $digest && $digest ne $conf->{digest
};
2951 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2953 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2955 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2957 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2960 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2961 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2965 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2966 (!$format || !$oldfmt || $oldfmt eq $format);
2968 # this only checks snapshots because $disk is passed!
2969 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2970 die "you can't move a disk with snapshots and delete the source\n"
2971 if $snapshotted && $param->{delete};
2973 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2975 my $running = PVE
::QemuServer
::check_running
($vmid);
2977 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2981 my $newvollist = [];
2987 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2989 warn "moving disk with snapshots, snapshots will not be moved!\n"
2992 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2993 $vmid, $storeid, $format, 1, $newvollist);
2995 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2997 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2999 # convert moved disk to base if part of template
3000 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3001 if PVE
::QemuConfig-
>is_template($conf);
3003 PVE
::QemuConfig-
>write_config($vmid, $conf);
3005 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3006 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
3010 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3011 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3018 foreach my $volid (@$newvollist) {
3019 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3022 die "storage migration failed: $err";
3025 if ($param->{delete}) {
3027 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3028 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3034 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3037 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3040 __PACKAGE__-
>register_method({
3041 name
=> 'migrate_vm',
3042 path
=> '{vmid}/migrate',
3046 description
=> "Migrate virtual machine. Creates a new migration task.",
3048 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3051 additionalProperties
=> 0,
3053 node
=> get_standard_option
('pve-node'),
3054 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3055 target
=> get_standard_option
('pve-node', {
3056 description
=> "Target node.",
3057 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3061 description
=> "Use online/live migration.",
3066 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3071 enum
=> ['secure', 'insecure'],
3072 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3075 migration_network
=> {
3076 type
=> 'string', format
=> 'CIDR',
3077 description
=> "CIDR of the (sub) network that is used for migration.",
3080 "with-local-disks" => {
3082 description
=> "Enable live storage migration for local disk",
3085 targetstorage
=> get_standard_option
('pve-storage-id', {
3086 description
=> "Default target storage.",
3088 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3094 description
=> "the task ID.",
3099 my $rpcenv = PVE
::RPCEnvironment
::get
();
3101 my $authuser = $rpcenv->get_user();
3103 my $target = extract_param
($param, 'target');
3105 my $localnode = PVE
::INotify
::nodename
();
3106 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3108 PVE
::Cluster
::check_cfs_quorum
();
3110 PVE
::Cluster
::check_node_exists
($target);
3112 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3114 my $vmid = extract_param
($param, 'vmid');
3116 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3117 if !$param->{online
} && $param->{targetstorage
};
3119 raise_param_exc
({ force
=> "Only root may use this option." })
3120 if $param->{force
} && $authuser ne 'root@pam';
3122 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3123 if $param->{migration_type
} && $authuser ne 'root@pam';
3125 # allow root only until better network permissions are available
3126 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3127 if $param->{migration_network
} && $authuser ne 'root@pam';
3130 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3132 # try to detect errors early
3134 PVE
::QemuConfig-
>check_lock($conf);
3136 if (PVE
::QemuServer
::check_running
($vmid)) {
3137 die "cant migrate running VM without --online\n"
3138 if !$param->{online
};
3141 my $storecfg = PVE
::Storage
::config
();
3143 if( $param->{targetstorage
}) {
3144 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3146 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3149 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3154 my $service = "vm:$vmid";
3156 my $cmd = ['ha-manager', 'migrate', $service, $target];
3158 print "Requesting HA migration for VM $vmid to node $target\n";
3160 PVE
::Tools
::run_command
($cmd);
3165 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3170 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3174 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3177 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3182 __PACKAGE__-
>register_method({
3184 path
=> '{vmid}/monitor',
3188 description
=> "Execute Qemu monitor commands.",
3190 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3191 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3194 additionalProperties
=> 0,
3196 node
=> get_standard_option
('pve-node'),
3197 vmid
=> get_standard_option
('pve-vmid'),
3200 description
=> "The monitor command.",
3204 returns
=> { type
=> 'string'},
3208 my $rpcenv = PVE
::RPCEnvironment
::get
();
3209 my $authuser = $rpcenv->get_user();
3212 my $command = shift;
3213 return $command =~ m/^\s*info(\s+|$)/
3214 || $command =~ m/^\s*help\s*$/;
3217 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3218 if !&$is_ro($param->{command
});
3220 my $vmid = $param->{vmid
};
3222 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3226 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3228 $res = "ERROR: $@" if $@;
3233 __PACKAGE__-
>register_method({
3234 name
=> 'resize_vm',
3235 path
=> '{vmid}/resize',
3239 description
=> "Extend volume size.",
3241 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3244 additionalProperties
=> 0,
3246 node
=> get_standard_option
('pve-node'),
3247 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3248 skiplock
=> get_standard_option
('skiplock'),
3251 description
=> "The disk you want to resize.",
3252 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3256 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3257 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.",
3261 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3267 returns
=> { type
=> 'null'},
3271 my $rpcenv = PVE
::RPCEnvironment
::get
();
3273 my $authuser = $rpcenv->get_user();
3275 my $node = extract_param
($param, 'node');
3277 my $vmid = extract_param
($param, 'vmid');
3279 my $digest = extract_param
($param, 'digest');
3281 my $disk = extract_param
($param, 'disk');
3283 my $sizestr = extract_param
($param, 'size');
3285 my $skiplock = extract_param
($param, 'skiplock');
3286 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3287 if $skiplock && $authuser ne 'root@pam';
3289 my $storecfg = PVE
::Storage
::config
();
3291 my $updatefn = sub {
3293 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3295 die "checksum missmatch (file change by other user?)\n"
3296 if $digest && $digest ne $conf->{digest
};
3297 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3299 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3301 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3303 my (undef, undef, undef, undef, undef, undef, $format) =
3304 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3306 die "can't resize volume: $disk if snapshot exists\n"
3307 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3309 my $volid = $drive->{file
};
3311 die "disk '$disk' has no associated volume\n" if !$volid;
3313 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3315 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3317 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3319 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3320 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3322 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3323 my ($ext, $newsize, $unit) = ($1, $2, $4);
3326 $newsize = $newsize * 1024;
3327 } elsif ($unit eq 'M') {
3328 $newsize = $newsize * 1024 * 1024;
3329 } elsif ($unit eq 'G') {
3330 $newsize = $newsize * 1024 * 1024 * 1024;
3331 } elsif ($unit eq 'T') {
3332 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3335 $newsize += $size if $ext;
3336 $newsize = int($newsize);
3338 die "shrinking disks is not supported\n" if $newsize < $size;
3340 return if $size == $newsize;
3342 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3344 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3346 $drive->{size
} = $newsize;
3347 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3349 PVE
::QemuConfig-
>write_config($vmid, $conf);
3352 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3356 __PACKAGE__-
>register_method({
3357 name
=> 'snapshot_list',
3358 path
=> '{vmid}/snapshot',
3360 description
=> "List all snapshots.",
3362 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3365 protected
=> 1, # qemu pid files are only readable by root
3367 additionalProperties
=> 0,
3369 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3370 node
=> get_standard_option
('pve-node'),
3379 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3383 description
=> "Snapshot includes RAM.",
3388 description
=> "Snapshot description.",
3392 description
=> "Snapshot creation time",
3394 renderer
=> 'timestamp',
3398 description
=> "Parent snapshot identifier.",
3404 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3409 my $vmid = $param->{vmid
};
3411 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3412 my $snaphash = $conf->{snapshots
} || {};
3416 foreach my $name (keys %$snaphash) {
3417 my $d = $snaphash->{$name};
3420 snaptime
=> $d->{snaptime
} || 0,
3421 vmstate
=> $d->{vmstate
} ?
1 : 0,
3422 description
=> $d->{description
} || '',
3424 $item->{parent
} = $d->{parent
} if $d->{parent
};
3425 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3429 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3432 digest
=> $conf->{digest
},
3433 running
=> $running,
3434 description
=> "You are here!",
3436 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3438 push @$res, $current;
3443 __PACKAGE__-
>register_method({
3445 path
=> '{vmid}/snapshot',
3449 description
=> "Snapshot a VM.",
3451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3454 additionalProperties
=> 0,
3456 node
=> get_standard_option
('pve-node'),
3457 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3458 snapname
=> get_standard_option
('pve-snapshot-name'),
3462 description
=> "Save the vmstate",
3467 description
=> "A textual description or comment.",
3473 description
=> "the task ID.",
3478 my $rpcenv = PVE
::RPCEnvironment
::get
();
3480 my $authuser = $rpcenv->get_user();
3482 my $node = extract_param
($param, 'node');
3484 my $vmid = extract_param
($param, 'vmid');
3486 my $snapname = extract_param
($param, 'snapname');
3488 die "unable to use snapshot name 'current' (reserved name)\n"
3489 if $snapname eq 'current';
3492 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3493 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3494 $param->{description
});
3497 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3500 __PACKAGE__-
>register_method({
3501 name
=> 'snapshot_cmd_idx',
3502 path
=> '{vmid}/snapshot/{snapname}',
3509 additionalProperties
=> 0,
3511 vmid
=> get_standard_option
('pve-vmid'),
3512 node
=> get_standard_option
('pve-node'),
3513 snapname
=> get_standard_option
('pve-snapshot-name'),
3522 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3529 push @$res, { cmd
=> 'rollback' };
3530 push @$res, { cmd
=> 'config' };
3535 __PACKAGE__-
>register_method({
3536 name
=> 'update_snapshot_config',
3537 path
=> '{vmid}/snapshot/{snapname}/config',
3541 description
=> "Update snapshot metadata.",
3543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3546 additionalProperties
=> 0,
3548 node
=> get_standard_option
('pve-node'),
3549 vmid
=> get_standard_option
('pve-vmid'),
3550 snapname
=> get_standard_option
('pve-snapshot-name'),
3554 description
=> "A textual description or comment.",
3558 returns
=> { type
=> 'null' },
3562 my $rpcenv = PVE
::RPCEnvironment
::get
();
3564 my $authuser = $rpcenv->get_user();
3566 my $vmid = extract_param
($param, 'vmid');
3568 my $snapname = extract_param
($param, 'snapname');
3570 return undef if !defined($param->{description
});
3572 my $updatefn = sub {
3574 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3576 PVE
::QemuConfig-
>check_lock($conf);
3578 my $snap = $conf->{snapshots
}->{$snapname};
3580 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3582 $snap->{description
} = $param->{description
} if defined($param->{description
});
3584 PVE
::QemuConfig-
>write_config($vmid, $conf);
3587 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3592 __PACKAGE__-
>register_method({
3593 name
=> 'get_snapshot_config',
3594 path
=> '{vmid}/snapshot/{snapname}/config',
3597 description
=> "Get snapshot configuration",
3599 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3602 additionalProperties
=> 0,
3604 node
=> get_standard_option
('pve-node'),
3605 vmid
=> get_standard_option
('pve-vmid'),
3606 snapname
=> get_standard_option
('pve-snapshot-name'),
3609 returns
=> { type
=> "object" },
3613 my $rpcenv = PVE
::RPCEnvironment
::get
();
3615 my $authuser = $rpcenv->get_user();
3617 my $vmid = extract_param
($param, 'vmid');
3619 my $snapname = extract_param
($param, 'snapname');
3621 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3623 my $snap = $conf->{snapshots
}->{$snapname};
3625 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3630 __PACKAGE__-
>register_method({
3632 path
=> '{vmid}/snapshot/{snapname}/rollback',
3636 description
=> "Rollback VM state to specified snapshot.",
3638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3641 additionalProperties
=> 0,
3643 node
=> get_standard_option
('pve-node'),
3644 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3645 snapname
=> get_standard_option
('pve-snapshot-name'),
3650 description
=> "the task ID.",
3655 my $rpcenv = PVE
::RPCEnvironment
::get
();
3657 my $authuser = $rpcenv->get_user();
3659 my $node = extract_param
($param, 'node');
3661 my $vmid = extract_param
($param, 'vmid');
3663 my $snapname = extract_param
($param, 'snapname');
3666 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3667 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3671 # hold migration lock, this makes sure that nobody create replication snapshots
3672 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3675 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3678 __PACKAGE__-
>register_method({
3679 name
=> 'delsnapshot',
3680 path
=> '{vmid}/snapshot/{snapname}',
3684 description
=> "Delete a VM snapshot.",
3686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3689 additionalProperties
=> 0,
3691 node
=> get_standard_option
('pve-node'),
3692 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3693 snapname
=> get_standard_option
('pve-snapshot-name'),
3697 description
=> "For removal from config file, even if removing disk snapshots fails.",
3703 description
=> "the task ID.",
3708 my $rpcenv = PVE
::RPCEnvironment
::get
();
3710 my $authuser = $rpcenv->get_user();
3712 my $node = extract_param
($param, 'node');
3714 my $vmid = extract_param
($param, 'vmid');
3716 my $snapname = extract_param
($param, 'snapname');
3719 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3720 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3723 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3726 __PACKAGE__-
>register_method({
3728 path
=> '{vmid}/template',
3732 description
=> "Create a Template.",
3734 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3735 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3738 additionalProperties
=> 0,
3740 node
=> get_standard_option
('pve-node'),
3741 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3745 description
=> "If you want to convert only 1 disk to base image.",
3746 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3751 returns
=> { type
=> 'null'},
3755 my $rpcenv = PVE
::RPCEnvironment
::get
();
3757 my $authuser = $rpcenv->get_user();
3759 my $node = extract_param
($param, 'node');
3761 my $vmid = extract_param
($param, 'vmid');
3763 my $disk = extract_param
($param, 'disk');
3765 my $updatefn = sub {
3767 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3769 PVE
::QemuConfig-
>check_lock($conf);
3771 die "unable to create template, because VM contains snapshots\n"
3772 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3774 die "you can't convert a template to a template\n"
3775 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3777 die "you can't convert a VM to template if VM is running\n"
3778 if PVE
::QemuServer
::check_running
($vmid);
3781 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3784 $conf->{template
} = 1;
3785 PVE
::QemuConfig-
>write_config($vmid, $conf);
3787 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3790 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);