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).",
835 description
=> "The current VM configuration.",
837 properties
=> PVE
::QemuServer
::json_config_properties
({
840 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
847 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
849 delete $conf->{snapshots
};
851 if (!$param->{current
}) {
852 foreach my $opt (keys %{$conf->{pending
}}) {
853 next if $opt eq 'delete';
854 my $value = $conf->{pending
}->{$opt};
855 next if ref($value); # just to be sure
856 $conf->{$opt} = $value;
858 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
859 foreach my $opt (keys %$pending_delete_hash) {
860 delete $conf->{$opt} if $conf->{$opt};
864 delete $conf->{pending
};
866 # hide cloudinit password
867 if ($conf->{cipassword
}) {
868 $conf->{cipassword
} = '**********';
874 __PACKAGE__-
>register_method({
875 name
=> 'vm_pending',
876 path
=> '{vmid}/pending',
879 description
=> "Get virtual machine configuration, including pending changes.",
881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
884 additionalProperties
=> 0,
886 node
=> get_standard_option
('pve-node'),
887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
896 description
=> "Configuration option name.",
900 description
=> "Current value.",
905 description
=> "Pending value.",
910 description
=> "Indicates a pending delete request if present and not 0. " .
911 "The value 2 indicates a force-delete request.",
923 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
925 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
929 foreach my $opt (keys %$conf) {
930 next if ref($conf->{$opt});
931 my $item = { key
=> $opt };
932 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
933 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
934 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
936 # hide cloudinit password
937 if ($opt eq 'cipassword') {
938 $item->{value
} = '**********' if defined($item->{value
});
939 # the trailing space so that the pending string is different
940 $item->{pending
} = '********** ' if defined($item->{pending
});
945 foreach my $opt (keys %{$conf->{pending
}}) {
946 next if $opt eq 'delete';
947 next if ref($conf->{pending
}->{$opt}); # just to be sure
948 next if defined($conf->{$opt});
949 my $item = { key
=> $opt };
950 $item->{pending
} = $conf->{pending
}->{$opt};
952 # hide cloudinit password
953 if ($opt eq 'cipassword') {
954 $item->{pending
} = '**********' if defined($item->{pending
});
959 while (my ($opt, $force) = each %$pending_delete_hash) {
960 next if $conf->{pending
}->{$opt}; # just to be sure
961 next if $conf->{$opt};
962 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1073 my ($storeid, $format);
1074 if ($volid =~ $NEW_DISK_RE) {
1076 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1078 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1079 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1081 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1082 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1083 return if $scfg->{shared
};
1084 die "cannot add non-replicatable volume to a replicated VM\n";
1087 foreach my $opt (keys %$param) {
1088 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1089 # cleanup drive path
1090 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1091 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1092 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1093 $check_replication->($drive);
1094 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1095 } elsif ($opt =~ m/^net(\d+)$/) {
1097 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1098 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1099 } elsif ($opt eq 'vmgenid') {
1100 if ($param->{$opt} eq '1') {
1101 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1108 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1110 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1112 my $updatefn = sub {
1114 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1116 die "checksum missmatch (file change by other user?)\n"
1117 if $digest && $digest ne $conf->{digest
};
1119 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1121 foreach my $opt (keys %$revert) {
1122 if (defined($conf->{$opt})) {
1123 $param->{$opt} = $conf->{$opt};
1124 } elsif (defined($conf->{pending
}->{$opt})) {
1129 if ($param->{memory
} || defined($param->{balloon
})) {
1130 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1131 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1133 die "balloon value too large (must be smaller than assigned memory)\n"
1134 if $balloon && $balloon > $maxmem;
1137 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1141 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1143 # write updates to pending section
1145 my $modified = {}; # record what $option we modify
1147 foreach my $opt (@delete) {
1148 $modified->{$opt} = 1;
1149 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1150 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1151 warn "cannot delete '$opt' - not set in current configuration!\n";
1152 $modified->{$opt} = 0;
1156 if ($opt =~ m/^unused/) {
1157 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1158 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1159 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1160 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1161 delete $conf->{$opt};
1162 PVE
::QemuConfig-
>write_config($vmid, $conf);
1164 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1165 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1166 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1167 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1168 if defined($conf->{pending
}->{$opt});
1169 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1170 PVE
::QemuConfig-
>write_config($vmid, $conf);
1172 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1173 PVE
::QemuConfig-
>write_config($vmid, $conf);
1177 foreach my $opt (keys %$param) { # add/change
1178 $modified->{$opt} = 1;
1179 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1180 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1182 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1184 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1185 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1186 # FIXME: cloudinit: CDROM or Disk?
1187 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1188 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1190 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1192 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1193 if defined($conf->{pending
}->{$opt});
1195 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1197 $conf->{pending
}->{$opt} = $param->{$opt};
1199 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1200 PVE
::QemuConfig-
>write_config($vmid, $conf);
1203 # remove pending changes when nothing changed
1204 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1205 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1206 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1208 return if !scalar(keys %{$conf->{pending
}});
1210 my $running = PVE
::QemuServer
::check_running
($vmid);
1212 # apply pending changes
1214 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1218 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1219 raise_param_exc
($errors) if scalar(keys %$errors);
1221 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1231 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1233 if ($background_delay) {
1235 # Note: It would be better to do that in the Event based HTTPServer
1236 # to avoid blocking call to sleep.
1238 my $end_time = time() + $background_delay;
1240 my $task = PVE
::Tools
::upid_decode
($upid);
1243 while (time() < $end_time) {
1244 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1246 sleep(1); # this gets interrupted when child process ends
1250 my $status = PVE
::Tools
::upid_read_status
($upid);
1251 return undef if $status eq 'OK';
1260 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1263 my $vm_config_perm_list = [
1268 'VM.Config.Network',
1270 'VM.Config.Options',
1273 __PACKAGE__-
>register_method({
1274 name
=> 'update_vm_async',
1275 path
=> '{vmid}/config',
1279 description
=> "Set virtual machine options (asynchrounous API).",
1281 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1284 additionalProperties
=> 0,
1285 properties
=> PVE
::QemuServer
::json_config_properties
(
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid'),
1289 skiplock
=> get_standard_option
('skiplock'),
1291 type
=> 'string', format
=> 'pve-configid-list',
1292 description
=> "A list of settings you want to delete.",
1296 type
=> 'string', format
=> 'pve-configid-list',
1297 description
=> "Revert a pending change.",
1302 description
=> $opt_force_description,
1304 requires
=> 'delete',
1308 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1312 background_delay
=> {
1314 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1325 code
=> $update_vm_api,
1328 __PACKAGE__-
>register_method({
1329 name
=> 'update_vm',
1330 path
=> '{vmid}/config',
1334 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1336 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1339 additionalProperties
=> 0,
1340 properties
=> PVE
::QemuServer
::json_config_properties
(
1342 node
=> get_standard_option
('pve-node'),
1343 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1344 skiplock
=> get_standard_option
('skiplock'),
1346 type
=> 'string', format
=> 'pve-configid-list',
1347 description
=> "A list of settings you want to delete.",
1351 type
=> 'string', format
=> 'pve-configid-list',
1352 description
=> "Revert a pending change.",
1357 description
=> $opt_force_description,
1359 requires
=> 'delete',
1363 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1369 returns
=> { type
=> 'null' },
1372 &$update_vm_api($param, 1);
1378 __PACKAGE__-
>register_method({
1379 name
=> 'destroy_vm',
1384 description
=> "Destroy the vm (also delete all used/owned volumes).",
1386 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1389 additionalProperties
=> 0,
1391 node
=> get_standard_option
('pve-node'),
1392 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1393 skiplock
=> get_standard_option
('skiplock'),
1402 my $rpcenv = PVE
::RPCEnvironment
::get
();
1404 my $authuser = $rpcenv->get_user();
1406 my $vmid = $param->{vmid
};
1408 my $skiplock = $param->{skiplock
};
1409 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1410 if $skiplock && $authuser ne 'root@pam';
1413 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1415 my $storecfg = PVE
::Storage
::config
();
1417 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1419 die "unable to remove VM $vmid - used in HA resources\n"
1420 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1422 # do not allow destroy if there are replication jobs
1423 my $repl_conf = PVE
::ReplicationConfig-
>new();
1424 $repl_conf->check_for_existing_jobs($vmid);
1426 # early tests (repeat after locking)
1427 die "VM $vmid is running - destroy failed\n"
1428 if PVE
::QemuServer
::check_running
($vmid);
1433 syslog
('info', "destroy VM $vmid: $upid\n");
1435 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1437 PVE
::AccessControl
::remove_vm_access
($vmid);
1439 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1442 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1445 __PACKAGE__-
>register_method({
1447 path
=> '{vmid}/unlink',
1451 description
=> "Unlink/delete disk images.",
1453 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1456 additionalProperties
=> 0,
1458 node
=> get_standard_option
('pve-node'),
1459 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1461 type
=> 'string', format
=> 'pve-configid-list',
1462 description
=> "A list of disk IDs you want to delete.",
1466 description
=> $opt_force_description,
1471 returns
=> { type
=> 'null'},
1475 $param->{delete} = extract_param
($param, 'idlist');
1477 __PACKAGE__-
>update_vm($param);
1484 __PACKAGE__-
>register_method({
1486 path
=> '{vmid}/vncproxy',
1490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1492 description
=> "Creates a TCP VNC proxy connections.",
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid'),
1501 description
=> "starts websockify instead of vncproxy",
1506 additionalProperties
=> 0,
1508 user
=> { type
=> 'string' },
1509 ticket
=> { type
=> 'string' },
1510 cert
=> { type
=> 'string' },
1511 port
=> { type
=> 'integer' },
1512 upid
=> { type
=> 'string' },
1518 my $rpcenv = PVE
::RPCEnvironment
::get
();
1520 my $authuser = $rpcenv->get_user();
1522 my $vmid = $param->{vmid
};
1523 my $node = $param->{node
};
1524 my $websocket = $param->{websocket
};
1526 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1527 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1529 my $authpath = "/vms/$vmid";
1531 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1533 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1539 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1540 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1541 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1542 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1543 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1545 $family = PVE
::Tools
::get_host_address_family
($node);
1548 my $port = PVE
::Tools
::next_vnc_port
($family);
1555 syslog
('info', "starting vnc proxy $upid\n");
1561 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1563 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1564 '-timeout', $timeout, '-authpath', $authpath,
1565 '-perm', 'Sys.Console'];
1567 if ($param->{websocket
}) {
1568 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1569 push @$cmd, '-notls', '-listen', 'localhost';
1572 push @$cmd, '-c', @$remcmd, @$termcmd;
1574 PVE
::Tools
::run_command
($cmd);
1578 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1580 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1582 my $sock = IO
::Socket
::IP-
>new(
1587 GetAddrInfoFlags
=> 0,
1588 ) or die "failed to create socket: $!\n";
1589 # Inside the worker we shouldn't have any previous alarms
1590 # running anyway...:
1592 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1594 accept(my $cli, $sock) or die "connection failed: $!\n";
1597 if (PVE
::Tools
::run_command
($cmd,
1598 output
=> '>&'.fileno($cli),
1599 input
=> '<&'.fileno($cli),
1602 die "Failed to run vncproxy.\n";
1609 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1611 PVE
::Tools
::wait_for_vnc_port
($port);
1622 __PACKAGE__-
>register_method({
1623 name
=> 'termproxy',
1624 path
=> '{vmid}/termproxy',
1628 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1630 description
=> "Creates a TCP proxy connections.",
1632 additionalProperties
=> 0,
1634 node
=> get_standard_option
('pve-node'),
1635 vmid
=> get_standard_option
('pve-vmid'),
1639 enum
=> [qw(serial0 serial1 serial2 serial3)],
1640 description
=> "opens a serial terminal (defaults to display)",
1645 additionalProperties
=> 0,
1647 user
=> { type
=> 'string' },
1648 ticket
=> { type
=> 'string' },
1649 port
=> { type
=> 'integer' },
1650 upid
=> { type
=> 'string' },
1656 my $rpcenv = PVE
::RPCEnvironment
::get
();
1658 my $authuser = $rpcenv->get_user();
1660 my $vmid = $param->{vmid
};
1661 my $node = $param->{node
};
1662 my $serial = $param->{serial
};
1664 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1666 if (!defined($serial)) {
1667 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1668 $serial = $conf->{vga
};
1672 my $authpath = "/vms/$vmid";
1674 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1679 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1680 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1681 my $sshinfo = PVE
::Cluster
::get_ssh_info
($node);
1682 $remcmd = PVE
::Cluster
::ssh_info_to_command
($sshinfo, '-t');
1683 push @$remcmd, '--';
1685 $family = PVE
::Tools
::get_host_address_family
($node);
1688 my $port = PVE
::Tools
::next_vnc_port
($family);
1690 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1691 push @$termcmd, '-iface', $serial if $serial;
1696 syslog
('info', "starting qemu termproxy $upid\n");
1698 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1699 '--perm', 'VM.Console', '--'];
1700 push @$cmd, @$remcmd, @$termcmd;
1702 PVE
::Tools
::run_command
($cmd);
1705 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1707 PVE
::Tools
::wait_for_vnc_port
($port);
1717 __PACKAGE__-
>register_method({
1718 name
=> 'vncwebsocket',
1719 path
=> '{vmid}/vncwebsocket',
1722 description
=> "You also need to pass a valid ticket (vncticket).",
1723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1725 description
=> "Opens a weksocket for VNC traffic.",
1727 additionalProperties
=> 0,
1729 node
=> get_standard_option
('pve-node'),
1730 vmid
=> get_standard_option
('pve-vmid'),
1732 description
=> "Ticket from previous call to vncproxy.",
1737 description
=> "Port number returned by previous vncproxy call.",
1747 port
=> { type
=> 'string' },
1753 my $rpcenv = PVE
::RPCEnvironment
::get
();
1755 my $authuser = $rpcenv->get_user();
1757 my $vmid = $param->{vmid
};
1758 my $node = $param->{node
};
1760 my $authpath = "/vms/$vmid";
1762 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1764 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1766 # Note: VNC ports are acessible from outside, so we do not gain any
1767 # security if we verify that $param->{port} belongs to VM $vmid. This
1768 # check is done by verifying the VNC ticket (inside VNC protocol).
1770 my $port = $param->{port
};
1772 return { port
=> $port };
1775 __PACKAGE__-
>register_method({
1776 name
=> 'spiceproxy',
1777 path
=> '{vmid}/spiceproxy',
1782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1784 description
=> "Returns a SPICE configuration to connect to the VM.",
1786 additionalProperties
=> 0,
1788 node
=> get_standard_option
('pve-node'),
1789 vmid
=> get_standard_option
('pve-vmid'),
1790 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1793 returns
=> get_standard_option
('remote-viewer-config'),
1797 my $rpcenv = PVE
::RPCEnvironment
::get
();
1799 my $authuser = $rpcenv->get_user();
1801 my $vmid = $param->{vmid
};
1802 my $node = $param->{node
};
1803 my $proxy = $param->{proxy
};
1805 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1806 my $title = "VM $vmid";
1807 $title .= " - ". $conf->{name
} if $conf->{name
};
1809 my $port = PVE
::QemuServer
::spice_port
($vmid);
1811 my ($ticket, undef, $remote_viewer_config) =
1812 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1814 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1815 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1817 return $remote_viewer_config;
1820 __PACKAGE__-
>register_method({
1822 path
=> '{vmid}/status',
1825 description
=> "Directory index",
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1833 vmid
=> get_standard_option
('pve-vmid'),
1841 subdir
=> { type
=> 'string' },
1844 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1850 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1853 { subdir
=> 'current' },
1854 { subdir
=> 'start' },
1855 { subdir
=> 'stop' },
1861 __PACKAGE__-
>register_method({
1862 name
=> 'vm_status',
1863 path
=> '{vmid}/status/current',
1866 protected
=> 1, # qemu pid files are only readable by root
1867 description
=> "Get virtual machine status.",
1869 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1872 additionalProperties
=> 0,
1874 node
=> get_standard_option
('pve-node'),
1875 vmid
=> get_standard_option
('pve-vmid'),
1881 %$PVE::QemuServer
::vmstatus_return_properties
,
1883 description
=> "HA manager service status.",
1887 description
=> "Qemu VGA configuration supports spice.",
1892 description
=> "Qemu GuestAgent enabled in config.",
1902 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1904 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1905 my $status = $vmstatus->{$param->{vmid
}};
1907 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1909 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1910 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1915 __PACKAGE__-
>register_method({
1917 path
=> '{vmid}/status/start',
1921 description
=> "Start virtual machine.",
1923 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1926 additionalProperties
=> 0,
1928 node
=> get_standard_option
('pve-node'),
1929 vmid
=> get_standard_option
('pve-vmid',
1930 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1931 skiplock
=> get_standard_option
('skiplock'),
1932 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1933 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1936 enum
=> ['secure', 'insecure'],
1937 description
=> "Migration traffic is encrypted using an SSH " .
1938 "tunnel by default. On secure, completely private networks " .
1939 "this can be disabled to increase performance.",
1942 migration_network
=> {
1943 type
=> 'string', format
=> 'CIDR',
1944 description
=> "CIDR of the (sub) network that is used for migration.",
1947 machine
=> get_standard_option
('pve-qm-machine'),
1949 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1961 my $rpcenv = PVE
::RPCEnvironment
::get
();
1963 my $authuser = $rpcenv->get_user();
1965 my $node = extract_param
($param, 'node');
1967 my $vmid = extract_param
($param, 'vmid');
1969 my $machine = extract_param
($param, 'machine');
1971 my $stateuri = extract_param
($param, 'stateuri');
1972 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1973 if $stateuri && $authuser ne 'root@pam';
1975 my $skiplock = extract_param
($param, 'skiplock');
1976 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1977 if $skiplock && $authuser ne 'root@pam';
1979 my $migratedfrom = extract_param
($param, 'migratedfrom');
1980 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1981 if $migratedfrom && $authuser ne 'root@pam';
1983 my $migration_type = extract_param
($param, 'migration_type');
1984 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1985 if $migration_type && $authuser ne 'root@pam';
1987 my $migration_network = extract_param
($param, 'migration_network');
1988 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1989 if $migration_network && $authuser ne 'root@pam';
1991 my $targetstorage = extract_param
($param, 'targetstorage');
1992 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1993 if $targetstorage && $authuser ne 'root@pam';
1995 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1996 if $targetstorage && !$migratedfrom;
1998 # read spice ticket from STDIN
2000 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2001 if (defined(my $line = <STDIN
>)) {
2003 $spice_ticket = $line;
2007 PVE
::Cluster
::check_cfs_quorum
();
2009 my $storecfg = PVE
::Storage
::config
();
2011 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2012 $rpcenv->{type
} ne 'ha') {
2017 my $service = "vm:$vmid";
2019 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2021 print "Requesting HA start for VM $vmid\n";
2023 PVE
::Tools
::run_command
($cmd);
2028 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2035 syslog
('info', "start VM $vmid: $upid\n");
2037 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2038 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2043 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2047 __PACKAGE__-
>register_method({
2049 path
=> '{vmid}/status/stop',
2053 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2054 "is akin to pulling the power plug of a running computer and may damage the VM data",
2056 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2059 additionalProperties
=> 0,
2061 node
=> get_standard_option
('pve-node'),
2062 vmid
=> get_standard_option
('pve-vmid',
2063 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2064 skiplock
=> get_standard_option
('skiplock'),
2065 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2067 description
=> "Wait maximal timeout seconds.",
2073 description
=> "Do not deactivate storage volumes.",
2086 my $rpcenv = PVE
::RPCEnvironment
::get
();
2088 my $authuser = $rpcenv->get_user();
2090 my $node = extract_param
($param, 'node');
2092 my $vmid = extract_param
($param, 'vmid');
2094 my $skiplock = extract_param
($param, 'skiplock');
2095 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2096 if $skiplock && $authuser ne 'root@pam';
2098 my $keepActive = extract_param
($param, 'keepActive');
2099 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2100 if $keepActive && $authuser ne 'root@pam';
2102 my $migratedfrom = extract_param
($param, 'migratedfrom');
2103 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2104 if $migratedfrom && $authuser ne 'root@pam';
2107 my $storecfg = PVE
::Storage
::config
();
2109 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2114 my $service = "vm:$vmid";
2116 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2118 print "Requesting HA stop for VM $vmid\n";
2120 PVE
::Tools
::run_command
($cmd);
2125 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2131 syslog
('info', "stop VM $vmid: $upid\n");
2133 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2134 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2139 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2143 __PACKAGE__-
>register_method({
2145 path
=> '{vmid}/status/reset',
2149 description
=> "Reset virtual machine.",
2151 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2154 additionalProperties
=> 0,
2156 node
=> get_standard_option
('pve-node'),
2157 vmid
=> get_standard_option
('pve-vmid',
2158 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2159 skiplock
=> get_standard_option
('skiplock'),
2168 my $rpcenv = PVE
::RPCEnvironment
::get
();
2170 my $authuser = $rpcenv->get_user();
2172 my $node = extract_param
($param, 'node');
2174 my $vmid = extract_param
($param, 'vmid');
2176 my $skiplock = extract_param
($param, 'skiplock');
2177 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2178 if $skiplock && $authuser ne 'root@pam';
2180 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2185 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2190 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2193 __PACKAGE__-
>register_method({
2194 name
=> 'vm_shutdown',
2195 path
=> '{vmid}/status/shutdown',
2199 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2200 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2202 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2205 additionalProperties
=> 0,
2207 node
=> get_standard_option
('pve-node'),
2208 vmid
=> get_standard_option
('pve-vmid',
2209 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2210 skiplock
=> get_standard_option
('skiplock'),
2212 description
=> "Wait maximal timeout seconds.",
2218 description
=> "Make sure the VM stops.",
2224 description
=> "Do not deactivate storage volumes.",
2237 my $rpcenv = PVE
::RPCEnvironment
::get
();
2239 my $authuser = $rpcenv->get_user();
2241 my $node = extract_param
($param, 'node');
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $skiplock = extract_param
($param, 'skiplock');
2246 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2247 if $skiplock && $authuser ne 'root@pam';
2249 my $keepActive = extract_param
($param, 'keepActive');
2250 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2251 if $keepActive && $authuser ne 'root@pam';
2253 my $storecfg = PVE
::Storage
::config
();
2257 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2258 # otherwise, we will infer a shutdown command, but run into the timeout,
2259 # then when the vm is resumed, it will instantly shutdown
2261 # checking the qmp status here to get feedback to the gui/cli/api
2262 # and the status query should not take too long
2265 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2269 if (!$err && $qmpstatus->{status
} eq "paused") {
2270 if ($param->{forceStop
}) {
2271 warn "VM is paused - stop instead of shutdown\n";
2274 die "VM is paused - cannot shutdown\n";
2278 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2279 ($rpcenv->{type
} ne 'ha')) {
2284 my $service = "vm:$vmid";
2286 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2288 print "Requesting HA stop for VM $vmid\n";
2290 PVE
::Tools
::run_command
($cmd);
2295 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2302 syslog
('info', "shutdown VM $vmid: $upid\n");
2304 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2305 $shutdown, $param->{forceStop
}, $keepActive);
2310 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2314 __PACKAGE__-
>register_method({
2315 name
=> 'vm_suspend',
2316 path
=> '{vmid}/status/suspend',
2320 description
=> "Suspend virtual machine.",
2322 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2325 additionalProperties
=> 0,
2327 node
=> get_standard_option
('pve-node'),
2328 vmid
=> get_standard_option
('pve-vmid',
2329 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2330 skiplock
=> get_standard_option
('skiplock'),
2339 my $rpcenv = PVE
::RPCEnvironment
::get
();
2341 my $authuser = $rpcenv->get_user();
2343 my $node = extract_param
($param, 'node');
2345 my $vmid = extract_param
($param, 'vmid');
2347 my $skiplock = extract_param
($param, 'skiplock');
2348 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2349 if $skiplock && $authuser ne 'root@pam';
2351 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2356 syslog
('info', "suspend VM $vmid: $upid\n");
2358 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2363 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2366 __PACKAGE__-
>register_method({
2367 name
=> 'vm_resume',
2368 path
=> '{vmid}/status/resume',
2372 description
=> "Resume virtual machine.",
2374 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2377 additionalProperties
=> 0,
2379 node
=> get_standard_option
('pve-node'),
2380 vmid
=> get_standard_option
('pve-vmid',
2381 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2382 skiplock
=> get_standard_option
('skiplock'),
2383 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2393 my $rpcenv = PVE
::RPCEnvironment
::get
();
2395 my $authuser = $rpcenv->get_user();
2397 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $skiplock = extract_param
($param, 'skiplock');
2402 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2403 if $skiplock && $authuser ne 'root@pam';
2405 my $nocheck = extract_param
($param, 'nocheck');
2407 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2412 syslog
('info', "resume VM $vmid: $upid\n");
2414 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2419 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2422 __PACKAGE__-
>register_method({
2423 name
=> 'vm_sendkey',
2424 path
=> '{vmid}/sendkey',
2428 description
=> "Send key event to virtual machine.",
2430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid',
2437 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2438 skiplock
=> get_standard_option
('skiplock'),
2440 description
=> "The key (qemu monitor encoding).",
2445 returns
=> { type
=> 'null'},
2449 my $rpcenv = PVE
::RPCEnvironment
::get
();
2451 my $authuser = $rpcenv->get_user();
2453 my $node = extract_param
($param, 'node');
2455 my $vmid = extract_param
($param, 'vmid');
2457 my $skiplock = extract_param
($param, 'skiplock');
2458 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2459 if $skiplock && $authuser ne 'root@pam';
2461 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2466 __PACKAGE__-
>register_method({
2467 name
=> 'vm_feature',
2468 path
=> '{vmid}/feature',
2472 description
=> "Check if feature for virtual machine is available.",
2474 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2477 additionalProperties
=> 0,
2479 node
=> get_standard_option
('pve-node'),
2480 vmid
=> get_standard_option
('pve-vmid'),
2482 description
=> "Feature to check.",
2484 enum
=> [ 'snapshot', 'clone', 'copy' ],
2486 snapname
=> get_standard_option
('pve-snapshot-name', {
2494 hasFeature
=> { type
=> 'boolean' },
2497 items
=> { type
=> 'string' },
2504 my $node = extract_param
($param, 'node');
2506 my $vmid = extract_param
($param, 'vmid');
2508 my $snapname = extract_param
($param, 'snapname');
2510 my $feature = extract_param
($param, 'feature');
2512 my $running = PVE
::QemuServer
::check_running
($vmid);
2514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2517 my $snap = $conf->{snapshots
}->{$snapname};
2518 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2521 my $storecfg = PVE
::Storage
::config
();
2523 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2524 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2527 hasFeature
=> $hasFeature,
2528 nodes
=> [ keys %$nodelist ],
2532 __PACKAGE__-
>register_method({
2534 path
=> '{vmid}/clone',
2538 description
=> "Create a copy of virtual machine/template.",
2540 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2541 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2542 "'Datastore.AllocateSpace' on any used storage.",
2545 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2547 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2548 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2553 additionalProperties
=> 0,
2555 node
=> get_standard_option
('pve-node'),
2556 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2557 newid
=> get_standard_option
('pve-vmid', {
2558 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2559 description
=> 'VMID for the clone.' }),
2562 type
=> 'string', format
=> 'dns-name',
2563 description
=> "Set a name for the new VM.",
2568 description
=> "Description for the new VM.",
2572 type
=> 'string', format
=> 'pve-poolid',
2573 description
=> "Add the new VM to the specified pool.",
2575 snapname
=> get_standard_option
('pve-snapshot-name', {
2578 storage
=> get_standard_option
('pve-storage-id', {
2579 description
=> "Target storage for full clone.",
2583 description
=> "Target format for file storage. Only valid for full clone.",
2586 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2591 description
=> "Create a full copy of all disks. This is always done when " .
2592 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2594 target
=> get_standard_option
('pve-node', {
2595 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2606 my $rpcenv = PVE
::RPCEnvironment
::get
();
2608 my $authuser = $rpcenv->get_user();
2610 my $node = extract_param
($param, 'node');
2612 my $vmid = extract_param
($param, 'vmid');
2614 my $newid = extract_param
($param, 'newid');
2616 my $pool = extract_param
($param, 'pool');
2618 if (defined($pool)) {
2619 $rpcenv->check_pool_exist($pool);
2622 my $snapname = extract_param
($param, 'snapname');
2624 my $storage = extract_param
($param, 'storage');
2626 my $format = extract_param
($param, 'format');
2628 my $target = extract_param
($param, 'target');
2630 my $localnode = PVE
::INotify
::nodename
();
2632 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2634 PVE
::Cluster
::check_node_exists
($target) if $target;
2636 my $storecfg = PVE
::Storage
::config
();
2639 # check if storage is enabled on local node
2640 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2642 # check if storage is available on target node
2643 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2644 # clone only works if target storage is shared
2645 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2646 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2650 PVE
::Cluster
::check_cfs_quorum
();
2652 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2654 # exclusive lock if VM is running - else shared lock is enough;
2655 my $shared_lock = $running ?
0 : 1;
2659 # do all tests after lock
2660 # we also try to do all tests before we fork the worker
2662 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2664 PVE
::QemuConfig-
>check_lock($conf);
2666 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2668 die "unexpected state change\n" if $verify_running != $running;
2670 die "snapshot '$snapname' does not exist\n"
2671 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2673 my $full = extract_param
($param, 'full');
2674 if (!defined($full)) {
2675 $full = !PVE
::QemuConfig-
>is_template($conf);
2678 die "parameter 'storage' not allowed for linked clones\n"
2679 if defined($storage) && !$full;
2681 die "parameter 'format' not allowed for linked clones\n"
2682 if defined($format) && !$full;
2684 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2686 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2688 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2690 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2692 die "unable to create VM $newid: config file already exists\n"
2695 my $newconf = { lock => 'clone' };
2700 foreach my $opt (keys %$oldconf) {
2701 my $value = $oldconf->{$opt};
2703 # do not copy snapshot related info
2704 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2705 $opt eq 'vmstate' || $opt eq 'snapstate';
2707 # no need to copy unused images, because VMID(owner) changes anyways
2708 next if $opt =~ m/^unused\d+$/;
2710 # always change MAC! address
2711 if ($opt =~ m/^net(\d+)$/) {
2712 my $net = PVE
::QemuServer
::parse_net
($value);
2713 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2714 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2715 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2716 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2717 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2718 die "unable to parse drive options for '$opt'\n" if !$drive;
2719 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2720 $newconf->{$opt} = $value; # simply copy configuration
2722 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2723 die "Full clone feature is not supported for drive '$opt'\n"
2724 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2725 $fullclone->{$opt} = 1;
2727 # not full means clone instead of copy
2728 die "Linked clone feature is not supported for drive '$opt'\n"
2729 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2731 $drives->{$opt} = $drive;
2732 push @$vollist, $drive->{file
};
2735 # copy everything else
2736 $newconf->{$opt} = $value;
2740 # auto generate a new uuid
2741 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2742 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2743 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2745 # auto generate a new vmgenid if the option was set
2746 if ($newconf->{vmgenid
}) {
2747 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2750 delete $newconf->{template
};
2752 if ($param->{name
}) {
2753 $newconf->{name
} = $param->{name
};
2755 if ($oldconf->{name
}) {
2756 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2758 $newconf->{name
} = "Copy-of-VM-$vmid";
2762 if ($param->{description
}) {
2763 $newconf->{description
} = $param->{description
};
2766 # create empty/temp config - this fails if VM already exists on other node
2767 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2772 my $newvollist = [];
2779 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2781 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2783 my $total_jobs = scalar(keys %{$drives});
2786 foreach my $opt (keys %$drives) {
2787 my $drive = $drives->{$opt};
2788 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2790 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2791 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2792 $jobs, $skipcomplete, $oldconf->{agent
});
2794 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2796 PVE
::QemuConfig-
>write_config($newid, $newconf);
2800 delete $newconf->{lock};
2802 # do not write pending changes
2803 if (my @changes = keys %{$newconf->{pending
}}) {
2804 my $pending = join(',', @changes);
2805 warn "found pending changes for '$pending', discarding for clone\n";
2806 delete $newconf->{pending
};
2809 PVE
::QemuConfig-
>write_config($newid, $newconf);
2812 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2813 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2814 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2816 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2817 die "Failed to move config to node '$target' - rename failed: $!\n"
2818 if !rename($conffile, $newconffile);
2821 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2826 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2828 sleep 1; # some storage like rbd need to wait before release volume - really?
2830 foreach my $volid (@$newvollist) {
2831 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2834 die "clone failed: $err";
2840 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2842 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2845 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2846 # Aquire exclusive lock lock for $newid
2847 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2852 __PACKAGE__-
>register_method({
2853 name
=> 'move_vm_disk',
2854 path
=> '{vmid}/move_disk',
2858 description
=> "Move volume to different storage.",
2860 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2862 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2863 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2867 additionalProperties
=> 0,
2869 node
=> get_standard_option
('pve-node'),
2870 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2873 description
=> "The disk you want to move.",
2874 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2876 storage
=> get_standard_option
('pve-storage-id', {
2877 description
=> "Target storage.",
2878 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2882 description
=> "Target Format.",
2883 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2888 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2894 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2902 description
=> "the task ID.",
2907 my $rpcenv = PVE
::RPCEnvironment
::get
();
2909 my $authuser = $rpcenv->get_user();
2911 my $node = extract_param
($param, 'node');
2913 my $vmid = extract_param
($param, 'vmid');
2915 my $digest = extract_param
($param, 'digest');
2917 my $disk = extract_param
($param, 'disk');
2919 my $storeid = extract_param
($param, 'storage');
2921 my $format = extract_param
($param, 'format');
2923 my $storecfg = PVE
::Storage
::config
();
2925 my $updatefn = sub {
2927 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2929 PVE
::QemuConfig-
>check_lock($conf);
2931 die "checksum missmatch (file change by other user?)\n"
2932 if $digest && $digest ne $conf->{digest
};
2934 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2936 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2938 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2940 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2943 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2944 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2948 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2949 (!$format || !$oldfmt || $oldfmt eq $format);
2951 # this only checks snapshots because $disk is passed!
2952 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2953 die "you can't move a disk with snapshots and delete the source\n"
2954 if $snapshotted && $param->{delete};
2956 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2958 my $running = PVE
::QemuServer
::check_running
($vmid);
2960 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2964 my $newvollist = [];
2970 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2972 warn "moving disk with snapshots, snapshots will not be moved!\n"
2975 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2976 $vmid, $storeid, $format, 1, $newvollist);
2978 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2980 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2982 # convert moved disk to base if part of template
2983 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2984 if PVE
::QemuConfig-
>is_template($conf);
2986 PVE
::QemuConfig-
>write_config($vmid, $conf);
2988 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2989 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2993 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2994 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3001 foreach my $volid (@$newvollist) {
3002 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3005 die "storage migration failed: $err";
3008 if ($param->{delete}) {
3010 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3011 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3017 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3020 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3023 __PACKAGE__-
>register_method({
3024 name
=> 'migrate_vm',
3025 path
=> '{vmid}/migrate',
3029 description
=> "Migrate virtual machine. Creates a new migration task.",
3031 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3034 additionalProperties
=> 0,
3036 node
=> get_standard_option
('pve-node'),
3037 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3038 target
=> get_standard_option
('pve-node', {
3039 description
=> "Target node.",
3040 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3044 description
=> "Use online/live migration.",
3049 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3054 enum
=> ['secure', 'insecure'],
3055 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3058 migration_network
=> {
3059 type
=> 'string', format
=> 'CIDR',
3060 description
=> "CIDR of the (sub) network that is used for migration.",
3063 "with-local-disks" => {
3065 description
=> "Enable live storage migration for local disk",
3068 targetstorage
=> get_standard_option
('pve-storage-id', {
3069 description
=> "Default target storage.",
3071 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3077 description
=> "the task ID.",
3082 my $rpcenv = PVE
::RPCEnvironment
::get
();
3084 my $authuser = $rpcenv->get_user();
3086 my $target = extract_param
($param, 'target');
3088 my $localnode = PVE
::INotify
::nodename
();
3089 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3091 PVE
::Cluster
::check_cfs_quorum
();
3093 PVE
::Cluster
::check_node_exists
($target);
3095 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3097 my $vmid = extract_param
($param, 'vmid');
3099 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3100 if !$param->{online
} && $param->{targetstorage
};
3102 raise_param_exc
({ force
=> "Only root may use this option." })
3103 if $param->{force
} && $authuser ne 'root@pam';
3105 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3106 if $param->{migration_type
} && $authuser ne 'root@pam';
3108 # allow root only until better network permissions are available
3109 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3110 if $param->{migration_network
} && $authuser ne 'root@pam';
3113 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3115 # try to detect errors early
3117 PVE
::QemuConfig-
>check_lock($conf);
3119 if (PVE
::QemuServer
::check_running
($vmid)) {
3120 die "cant migrate running VM without --online\n"
3121 if !$param->{online
};
3124 my $storecfg = PVE
::Storage
::config
();
3126 if( $param->{targetstorage
}) {
3127 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3129 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3132 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3137 my $service = "vm:$vmid";
3139 my $cmd = ['ha-manager', 'migrate', $service, $target];
3141 print "Requesting HA migration for VM $vmid to node $target\n";
3143 PVE
::Tools
::run_command
($cmd);
3148 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3153 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3157 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3160 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3165 __PACKAGE__-
>register_method({
3167 path
=> '{vmid}/monitor',
3171 description
=> "Execute Qemu monitor commands.",
3173 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3174 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3177 additionalProperties
=> 0,
3179 node
=> get_standard_option
('pve-node'),
3180 vmid
=> get_standard_option
('pve-vmid'),
3183 description
=> "The monitor command.",
3187 returns
=> { type
=> 'string'},
3191 my $rpcenv = PVE
::RPCEnvironment
::get
();
3192 my $authuser = $rpcenv->get_user();
3195 my $command = shift;
3196 return $command =~ m/^\s*info(\s+|$)/
3197 || $command =~ m/^\s*help\s*$/;
3200 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3201 if !&$is_ro($param->{command
});
3203 my $vmid = $param->{vmid
};
3205 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3209 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3211 $res = "ERROR: $@" if $@;
3216 __PACKAGE__-
>register_method({
3217 name
=> 'resize_vm',
3218 path
=> '{vmid}/resize',
3222 description
=> "Extend volume size.",
3224 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3227 additionalProperties
=> 0,
3229 node
=> get_standard_option
('pve-node'),
3230 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3231 skiplock
=> get_standard_option
('skiplock'),
3234 description
=> "The disk you want to resize.",
3235 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3239 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3240 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.",
3244 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3250 returns
=> { type
=> 'null'},
3254 my $rpcenv = PVE
::RPCEnvironment
::get
();
3256 my $authuser = $rpcenv->get_user();
3258 my $node = extract_param
($param, 'node');
3260 my $vmid = extract_param
($param, 'vmid');
3262 my $digest = extract_param
($param, 'digest');
3264 my $disk = extract_param
($param, 'disk');
3266 my $sizestr = extract_param
($param, 'size');
3268 my $skiplock = extract_param
($param, 'skiplock');
3269 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3270 if $skiplock && $authuser ne 'root@pam';
3272 my $storecfg = PVE
::Storage
::config
();
3274 my $updatefn = sub {
3276 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3278 die "checksum missmatch (file change by other user?)\n"
3279 if $digest && $digest ne $conf->{digest
};
3280 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3282 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3284 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3286 my (undef, undef, undef, undef, undef, undef, $format) =
3287 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3289 die "can't resize volume: $disk if snapshot exists\n"
3290 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3292 my $volid = $drive->{file
};
3294 die "disk '$disk' has no associated volume\n" if !$volid;
3296 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3298 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3300 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3302 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3303 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3305 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3306 my ($ext, $newsize, $unit) = ($1, $2, $4);
3309 $newsize = $newsize * 1024;
3310 } elsif ($unit eq 'M') {
3311 $newsize = $newsize * 1024 * 1024;
3312 } elsif ($unit eq 'G') {
3313 $newsize = $newsize * 1024 * 1024 * 1024;
3314 } elsif ($unit eq 'T') {
3315 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3318 $newsize += $size if $ext;
3319 $newsize = int($newsize);
3321 die "shrinking disks is not supported\n" if $newsize < $size;
3323 return if $size == $newsize;
3325 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3327 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3329 $drive->{size
} = $newsize;
3330 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3332 PVE
::QemuConfig-
>write_config($vmid, $conf);
3335 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3339 __PACKAGE__-
>register_method({
3340 name
=> 'snapshot_list',
3341 path
=> '{vmid}/snapshot',
3343 description
=> "List all snapshots.",
3345 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3348 protected
=> 1, # qemu pid files are only readable by root
3350 additionalProperties
=> 0,
3352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3353 node
=> get_standard_option
('pve-node'),
3362 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3366 description
=> "Snapshot includes RAM.",
3371 description
=> "Snapshot description.",
3375 description
=> "Snapshot creation time",
3377 renderer
=> 'timestamp',
3381 description
=> "Parent snapshot identifier.",
3387 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3392 my $vmid = $param->{vmid
};
3394 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3395 my $snaphash = $conf->{snapshots
} || {};
3399 foreach my $name (keys %$snaphash) {
3400 my $d = $snaphash->{$name};
3403 snaptime
=> $d->{snaptime
} || 0,
3404 vmstate
=> $d->{vmstate
} ?
1 : 0,
3405 description
=> $d->{description
} || '',
3407 $item->{parent
} = $d->{parent
} if $d->{parent
};
3408 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3412 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3415 digest
=> $conf->{digest
},
3416 running
=> $running,
3417 description
=> "You are here!",
3419 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3421 push @$res, $current;
3426 __PACKAGE__-
>register_method({
3428 path
=> '{vmid}/snapshot',
3432 description
=> "Snapshot a VM.",
3434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3437 additionalProperties
=> 0,
3439 node
=> get_standard_option
('pve-node'),
3440 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3441 snapname
=> get_standard_option
('pve-snapshot-name'),
3445 description
=> "Save the vmstate",
3450 description
=> "A textual description or comment.",
3456 description
=> "the task ID.",
3461 my $rpcenv = PVE
::RPCEnvironment
::get
();
3463 my $authuser = $rpcenv->get_user();
3465 my $node = extract_param
($param, 'node');
3467 my $vmid = extract_param
($param, 'vmid');
3469 my $snapname = extract_param
($param, 'snapname');
3471 die "unable to use snapshot name 'current' (reserved name)\n"
3472 if $snapname eq 'current';
3475 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3476 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3477 $param->{description
});
3480 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3483 __PACKAGE__-
>register_method({
3484 name
=> 'snapshot_cmd_idx',
3485 path
=> '{vmid}/snapshot/{snapname}',
3492 additionalProperties
=> 0,
3494 vmid
=> get_standard_option
('pve-vmid'),
3495 node
=> get_standard_option
('pve-node'),
3496 snapname
=> get_standard_option
('pve-snapshot-name'),
3505 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3512 push @$res, { cmd
=> 'rollback' };
3513 push @$res, { cmd
=> 'config' };
3518 __PACKAGE__-
>register_method({
3519 name
=> 'update_snapshot_config',
3520 path
=> '{vmid}/snapshot/{snapname}/config',
3524 description
=> "Update snapshot metadata.",
3526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3529 additionalProperties
=> 0,
3531 node
=> get_standard_option
('pve-node'),
3532 vmid
=> get_standard_option
('pve-vmid'),
3533 snapname
=> get_standard_option
('pve-snapshot-name'),
3537 description
=> "A textual description or comment.",
3541 returns
=> { type
=> 'null' },
3545 my $rpcenv = PVE
::RPCEnvironment
::get
();
3547 my $authuser = $rpcenv->get_user();
3549 my $vmid = extract_param
($param, 'vmid');
3551 my $snapname = extract_param
($param, 'snapname');
3553 return undef if !defined($param->{description
});
3555 my $updatefn = sub {
3557 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3559 PVE
::QemuConfig-
>check_lock($conf);
3561 my $snap = $conf->{snapshots
}->{$snapname};
3563 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3565 $snap->{description
} = $param->{description
} if defined($param->{description
});
3567 PVE
::QemuConfig-
>write_config($vmid, $conf);
3570 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3575 __PACKAGE__-
>register_method({
3576 name
=> 'get_snapshot_config',
3577 path
=> '{vmid}/snapshot/{snapname}/config',
3580 description
=> "Get snapshot configuration",
3582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3585 additionalProperties
=> 0,
3587 node
=> get_standard_option
('pve-node'),
3588 vmid
=> get_standard_option
('pve-vmid'),
3589 snapname
=> get_standard_option
('pve-snapshot-name'),
3592 returns
=> { type
=> "object" },
3596 my $rpcenv = PVE
::RPCEnvironment
::get
();
3598 my $authuser = $rpcenv->get_user();
3600 my $vmid = extract_param
($param, 'vmid');
3602 my $snapname = extract_param
($param, 'snapname');
3604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3606 my $snap = $conf->{snapshots
}->{$snapname};
3608 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3613 __PACKAGE__-
>register_method({
3615 path
=> '{vmid}/snapshot/{snapname}/rollback',
3619 description
=> "Rollback VM state to specified snapshot.",
3621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3624 additionalProperties
=> 0,
3626 node
=> get_standard_option
('pve-node'),
3627 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3628 snapname
=> get_standard_option
('pve-snapshot-name'),
3633 description
=> "the task ID.",
3638 my $rpcenv = PVE
::RPCEnvironment
::get
();
3640 my $authuser = $rpcenv->get_user();
3642 my $node = extract_param
($param, 'node');
3644 my $vmid = extract_param
($param, 'vmid');
3646 my $snapname = extract_param
($param, 'snapname');
3649 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3650 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3654 # hold migration lock, this makes sure that nobody create replication snapshots
3655 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3658 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3661 __PACKAGE__-
>register_method({
3662 name
=> 'delsnapshot',
3663 path
=> '{vmid}/snapshot/{snapname}',
3667 description
=> "Delete a VM snapshot.",
3669 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3672 additionalProperties
=> 0,
3674 node
=> get_standard_option
('pve-node'),
3675 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3676 snapname
=> get_standard_option
('pve-snapshot-name'),
3680 description
=> "For removal from config file, even if removing disk snapshots fails.",
3686 description
=> "the task ID.",
3691 my $rpcenv = PVE
::RPCEnvironment
::get
();
3693 my $authuser = $rpcenv->get_user();
3695 my $node = extract_param
($param, 'node');
3697 my $vmid = extract_param
($param, 'vmid');
3699 my $snapname = extract_param
($param, 'snapname');
3702 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3703 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3706 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3709 __PACKAGE__-
>register_method({
3711 path
=> '{vmid}/template',
3715 description
=> "Create a Template.",
3717 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3718 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3721 additionalProperties
=> 0,
3723 node
=> get_standard_option
('pve-node'),
3724 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3728 description
=> "If you want to convert only 1 disk to base image.",
3729 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3734 returns
=> { type
=> 'null'},
3738 my $rpcenv = PVE
::RPCEnvironment
::get
();
3740 my $authuser = $rpcenv->get_user();
3742 my $node = extract_param
($param, 'node');
3744 my $vmid = extract_param
($param, 'vmid');
3746 my $disk = extract_param
($param, 'disk');
3748 my $updatefn = sub {
3750 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3752 PVE
::QemuConfig-
>check_lock($conf);
3754 die "unable to create template, because VM contains snapshots\n"
3755 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3757 die "you can't convert a template to a template\n"
3758 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3760 die "you can't convert a VM to template if VM is running\n"
3761 if PVE
::QemuServer
::check_running
($vmid);
3764 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3767 $conf->{template
} = 1;
3768 PVE
::QemuConfig-
>write_config($vmid, $conf);
3770 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3773 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);