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, $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);
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);
590 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
592 if (!$conf->{bootdisk
}) {
593 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
594 $conf->{bootdisk
} = $firstdisk if $firstdisk;
597 # auto generate uuid if user did not specify smbios1 option
598 if (!$conf->{smbios1
}) {
599 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
602 PVE
::QemuConfig-
>write_config($vmid, $conf);
608 foreach my $volid (@$vollist) {
609 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
615 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
618 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
620 if ($start_after_create) {
621 print "Execute autostart\n";
622 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
627 my ($code, $worker_name);
629 $worker_name = 'qmrestore';
631 eval { $restorefn->() };
633 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
639 $worker_name = 'qmcreate';
641 eval { $createfn->() };
644 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
646 or die "failed to remove config file: $@\n";
654 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
657 __PACKAGE__-
>register_method({
662 description
=> "Directory index",
667 additionalProperties
=> 0,
669 node
=> get_standard_option
('pve-node'),
670 vmid
=> get_standard_option
('pve-vmid'),
678 subdir
=> { type
=> 'string' },
681 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
687 { subdir
=> 'config' },
688 { subdir
=> 'pending' },
689 { subdir
=> 'status' },
690 { subdir
=> 'unlink' },
691 { subdir
=> 'vncproxy' },
692 { subdir
=> 'termproxy' },
693 { subdir
=> 'migrate' },
694 { subdir
=> 'resize' },
695 { subdir
=> 'move' },
697 { subdir
=> 'rrddata' },
698 { subdir
=> 'monitor' },
699 { subdir
=> 'agent' },
700 { subdir
=> 'snapshot' },
701 { subdir
=> 'spiceproxy' },
702 { subdir
=> 'sendkey' },
703 { subdir
=> 'firewall' },
709 __PACKAGE__-
>register_method ({
710 subclass
=> "PVE::API2::Firewall::VM",
711 path
=> '{vmid}/firewall',
714 __PACKAGE__-
>register_method ({
715 subclass
=> "PVE::API2::Qemu::Agent",
716 path
=> '{vmid}/agent',
719 __PACKAGE__-
>register_method({
721 path
=> '{vmid}/rrd',
723 protected
=> 1, # fixme: can we avoid that?
725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
727 description
=> "Read VM RRD statistics (returns PNG)",
729 additionalProperties
=> 0,
731 node
=> get_standard_option
('pve-node'),
732 vmid
=> get_standard_option
('pve-vmid'),
734 description
=> "Specify the time frame you are interested in.",
736 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
739 description
=> "The list of datasources you want to display.",
740 type
=> 'string', format
=> 'pve-configid-list',
743 description
=> "The RRD consolidation function",
745 enum
=> [ 'AVERAGE', 'MAX' ],
753 filename
=> { type
=> 'string' },
759 return PVE
::Cluster
::create_rrd_graph
(
760 "pve2-vm/$param->{vmid}", $param->{timeframe
},
761 $param->{ds
}, $param->{cf
});
765 __PACKAGE__-
>register_method({
767 path
=> '{vmid}/rrddata',
769 protected
=> 1, # fixme: can we avoid that?
771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
773 description
=> "Read VM RRD statistics",
775 additionalProperties
=> 0,
777 node
=> get_standard_option
('pve-node'),
778 vmid
=> get_standard_option
('pve-vmid'),
780 description
=> "Specify the time frame you are interested in.",
782 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
785 description
=> "The RRD consolidation function",
787 enum
=> [ 'AVERAGE', 'MAX' ],
802 return PVE
::Cluster
::create_rrd_data
(
803 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
807 __PACKAGE__-
>register_method({
809 path
=> '{vmid}/config',
812 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
814 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 additionalProperties
=> 0,
819 node
=> get_standard_option
('pve-node'),
820 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
822 description
=> "Get current values (instead of pending values).",
830 description
=> "The current VM configuration.",
832 properties
=> PVE
::QemuServer
::json_config_properties
({
835 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
842 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
844 delete $conf->{snapshots
};
846 if (!$param->{current
}) {
847 foreach my $opt (keys %{$conf->{pending
}}) {
848 next if $opt eq 'delete';
849 my $value = $conf->{pending
}->{$opt};
850 next if ref($value); # just to be sure
851 $conf->{$opt} = $value;
853 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
854 foreach my $opt (keys %$pending_delete_hash) {
855 delete $conf->{$opt} if $conf->{$opt};
859 delete $conf->{pending
};
861 # hide cloudinit password
862 if ($conf->{cipassword
}) {
863 $conf->{cipassword
} = '**********';
869 __PACKAGE__-
>register_method({
870 name
=> 'vm_pending',
871 path
=> '{vmid}/pending',
874 description
=> "Get virtual machine configuration, including pending changes.",
876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
879 additionalProperties
=> 0,
881 node
=> get_standard_option
('pve-node'),
882 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
891 description
=> "Configuration option name.",
895 description
=> "Current value.",
900 description
=> "Pending value.",
905 description
=> "Indicates a pending delete request if present and not 0. " .
906 "The value 2 indicates a force-delete request.",
918 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
920 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
924 foreach my $opt (keys %$conf) {
925 next if ref($conf->{$opt});
926 my $item = { key
=> $opt };
927 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
928 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
929 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
931 # hide cloudinit password
932 if ($opt eq 'cipassword') {
933 $item->{value
} = '**********' if defined($item->{value
});
934 # the trailing space so that the pending string is different
935 $item->{pending
} = '********** ' if defined($item->{pending
});
940 foreach my $opt (keys %{$conf->{pending
}}) {
941 next if $opt eq 'delete';
942 next if ref($conf->{pending
}->{$opt}); # just to be sure
943 next if defined($conf->{$opt});
944 my $item = { key
=> $opt };
945 $item->{pending
} = $conf->{pending
}->{$opt};
947 # hide cloudinit password
948 if ($opt eq 'cipassword') {
949 $item->{pending
} = '**********' if defined($item->{pending
});
954 while (my ($opt, $force) = each %$pending_delete_hash) {
955 next if $conf->{pending
}->{$opt}; # just to be sure
956 next if $conf->{$opt};
957 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
964 # POST/PUT {vmid}/config implementation
966 # The original API used PUT (idempotent) an we assumed that all operations
967 # are fast. But it turned out that almost any configuration change can
968 # involve hot-plug actions, or disk alloc/free. Such actions can take long
969 # time to complete and have side effects (not idempotent).
971 # The new implementation uses POST and forks a worker process. We added
972 # a new option 'background_delay'. If specified we wait up to
973 # 'background_delay' second for the worker task to complete. It returns null
974 # if the task is finished within that time, else we return the UPID.
976 my $update_vm_api = sub {
977 my ($param, $sync) = @_;
979 my $rpcenv = PVE
::RPCEnvironment
::get
();
981 my $authuser = $rpcenv->get_user();
983 my $node = extract_param
($param, 'node');
985 my $vmid = extract_param
($param, 'vmid');
987 my $digest = extract_param
($param, 'digest');
989 my $background_delay = extract_param
($param, 'background_delay');
991 if (defined(my $cipassword = $param->{cipassword
})) {
992 # Same logic as in cloud-init (but with the regex fixed...)
993 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
994 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
997 my @paramarr = (); # used for log message
998 foreach my $key (sort keys %$param) {
999 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1000 push @paramarr, "-$key", $value;
1003 my $skiplock = extract_param
($param, 'skiplock');
1004 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1005 if $skiplock && $authuser ne 'root@pam';
1007 my $delete_str = extract_param
($param, 'delete');
1009 my $revert_str = extract_param
($param, 'revert');
1011 my $force = extract_param
($param, 'force');
1013 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1014 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1015 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1018 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1020 my $storecfg = PVE
::Storage
::config
();
1022 my $defaults = PVE
::QemuServer
::load_defaults
();
1024 &$resolve_cdrom_alias($param);
1026 # now try to verify all parameters
1029 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1030 if (!PVE
::QemuServer
::option_exists
($opt)) {
1031 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1034 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1035 "-revert $opt' at the same time" })
1036 if defined($param->{$opt});
1038 $revert->{$opt} = 1;
1042 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1043 $opt = 'ide2' if $opt eq 'cdrom';
1045 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1046 "-delete $opt' at the same time" })
1047 if defined($param->{$opt});
1049 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1050 "-revert $opt' at the same time" })
1053 if (!PVE
::QemuServer
::option_exists
($opt)) {
1054 raise_param_exc
({ delete => "unknown option '$opt'" });
1060 my $repl_conf = PVE
::ReplicationConfig-
>new();
1061 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1062 my $check_replication = sub {
1064 return if !$is_replicated;
1065 my $volid = $drive->{file
};
1066 return if !$volid || !($drive->{replicate
}//1);
1067 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1068 my ($storeid, $format);
1069 if ($volid =~ $NEW_DISK_RE) {
1071 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1073 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1074 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1076 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1077 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1078 return if $scfg->{shared
};
1079 die "cannot add non-replicatable volume to a replicated VM\n";
1082 foreach my $opt (keys %$param) {
1083 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1084 # cleanup drive path
1085 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1086 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1087 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1088 $check_replication->($drive);
1089 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1090 } elsif ($opt =~ m/^net(\d+)$/) {
1092 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1093 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1097 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1099 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1101 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1103 my $updatefn = sub {
1105 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1107 die "checksum missmatch (file change by other user?)\n"
1108 if $digest && $digest ne $conf->{digest
};
1110 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1112 foreach my $opt (keys %$revert) {
1113 if (defined($conf->{$opt})) {
1114 $param->{$opt} = $conf->{$opt};
1115 } elsif (defined($conf->{pending
}->{$opt})) {
1120 if ($param->{memory
} || defined($param->{balloon
})) {
1121 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1122 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1124 die "balloon value too large (must be smaller than assigned memory)\n"
1125 if $balloon && $balloon > $maxmem;
1128 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1132 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1134 # write updates to pending section
1136 my $modified = {}; # record what $option we modify
1138 foreach my $opt (@delete) {
1139 $modified->{$opt} = 1;
1140 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1141 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1142 warn "cannot delete '$opt' - not set in current configuration!\n";
1143 $modified->{$opt} = 0;
1147 if ($opt =~ m/^unused/) {
1148 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1149 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1151 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1152 delete $conf->{$opt};
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1155 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1156 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1157 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1158 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1159 if defined($conf->{pending
}->{$opt});
1160 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1164 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 foreach my $opt (keys %$param) { # add/change
1169 $modified->{$opt} = 1;
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1171 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1173 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1174 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1175 # FIXME: cloudinit: CDROM or Disk?
1176 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1177 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1182 if defined($conf->{pending
}->{$opt});
1184 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1186 $conf->{pending
}->{$opt} = $param->{$opt};
1188 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1192 # remove pending changes when nothing changed
1193 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1194 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1195 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1197 return if !scalar(keys %{$conf->{pending
}});
1199 my $running = PVE
::QemuServer
::check_running
($vmid);
1201 # apply pending changes
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1207 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1208 raise_param_exc
($errors) if scalar(keys %$errors);
1210 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1220 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1222 if ($background_delay) {
1224 # Note: It would be better to do that in the Event based HTTPServer
1225 # to avoid blocking call to sleep.
1227 my $end_time = time() + $background_delay;
1229 my $task = PVE
::Tools
::upid_decode
($upid);
1232 while (time() < $end_time) {
1233 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1235 sleep(1); # this gets interrupted when child process ends
1239 my $status = PVE
::Tools
::upid_read_status
($upid);
1240 return undef if $status eq 'OK';
1249 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1252 my $vm_config_perm_list = [
1257 'VM.Config.Network',
1259 'VM.Config.Options',
1262 __PACKAGE__-
>register_method({
1263 name
=> 'update_vm_async',
1264 path
=> '{vmid}/config',
1268 description
=> "Set virtual machine options (asynchrounous API).",
1270 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1273 additionalProperties
=> 0,
1274 properties
=> PVE
::QemuServer
::json_config_properties
(
1276 node
=> get_standard_option
('pve-node'),
1277 vmid
=> get_standard_option
('pve-vmid'),
1278 skiplock
=> get_standard_option
('skiplock'),
1280 type
=> 'string', format
=> 'pve-configid-list',
1281 description
=> "A list of settings you want to delete.",
1285 type
=> 'string', format
=> 'pve-configid-list',
1286 description
=> "Revert a pending change.",
1291 description
=> $opt_force_description,
1293 requires
=> 'delete',
1297 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1301 background_delay
=> {
1303 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1314 code
=> $update_vm_api,
1317 __PACKAGE__-
>register_method({
1318 name
=> 'update_vm',
1319 path
=> '{vmid}/config',
1323 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1325 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1328 additionalProperties
=> 0,
1329 properties
=> PVE
::QemuServer
::json_config_properties
(
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1333 skiplock
=> get_standard_option
('skiplock'),
1335 type
=> 'string', format
=> 'pve-configid-list',
1336 description
=> "A list of settings you want to delete.",
1340 type
=> 'string', format
=> 'pve-configid-list',
1341 description
=> "Revert a pending change.",
1346 description
=> $opt_force_description,
1348 requires
=> 'delete',
1352 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1358 returns
=> { type
=> 'null' },
1361 &$update_vm_api($param, 1);
1367 __PACKAGE__-
>register_method({
1368 name
=> 'destroy_vm',
1373 description
=> "Destroy the vm (also delete all used/owned volumes).",
1375 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1382 skiplock
=> get_standard_option
('skiplock'),
1391 my $rpcenv = PVE
::RPCEnvironment
::get
();
1393 my $authuser = $rpcenv->get_user();
1395 my $vmid = $param->{vmid
};
1397 my $skiplock = $param->{skiplock
};
1398 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1399 if $skiplock && $authuser ne 'root@pam';
1402 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1404 my $storecfg = PVE
::Storage
::config
();
1406 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1408 die "unable to remove VM $vmid - used in HA resources\n"
1409 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1411 # do not allow destroy if there are replication jobs
1412 my $repl_conf = PVE
::ReplicationConfig-
>new();
1413 $repl_conf->check_for_existing_jobs($vmid);
1415 # early tests (repeat after locking)
1416 die "VM $vmid is running - destroy failed\n"
1417 if PVE
::QemuServer
::check_running
($vmid);
1422 syslog
('info', "destroy VM $vmid: $upid\n");
1424 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1426 PVE
::AccessControl
::remove_vm_access
($vmid);
1428 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1431 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1434 __PACKAGE__-
>register_method({
1436 path
=> '{vmid}/unlink',
1440 description
=> "Unlink/delete disk images.",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1450 type
=> 'string', format
=> 'pve-configid-list',
1451 description
=> "A list of disk IDs you want to delete.",
1455 description
=> $opt_force_description,
1460 returns
=> { type
=> 'null'},
1464 $param->{delete} = extract_param
($param, 'idlist');
1466 __PACKAGE__-
>update_vm($param);
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/vncproxy',
1479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1481 description
=> "Creates a TCP VNC proxy connections.",
1483 additionalProperties
=> 0,
1485 node
=> get_standard_option
('pve-node'),
1486 vmid
=> get_standard_option
('pve-vmid'),
1490 description
=> "starts websockify instead of vncproxy",
1495 additionalProperties
=> 0,
1497 user
=> { type
=> 'string' },
1498 ticket
=> { type
=> 'string' },
1499 cert
=> { type
=> 'string' },
1500 port
=> { type
=> 'integer' },
1501 upid
=> { type
=> 'string' },
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $vmid = $param->{vmid
};
1512 my $node = $param->{node
};
1513 my $websocket = $param->{websocket
};
1515 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1517 my $authpath = "/vms/$vmid";
1519 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1521 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1524 my ($remip, $family);
1527 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1528 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1529 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1530 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1532 $family = PVE
::Tools
::get_host_address_family
($node);
1535 my $port = PVE
::Tools
::next_vnc_port
($family);
1542 syslog
('info', "starting vnc proxy $upid\n");
1546 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1549 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1551 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1552 '-timeout', $timeout, '-authpath', $authpath,
1553 '-perm', 'Sys.Console'];
1555 if ($param->{websocket
}) {
1556 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1557 push @$cmd, '-notls', '-listen', 'localhost';
1560 push @$cmd, '-c', @$remcmd, @$termcmd;
1562 PVE
::Tools
::run_command
($cmd);
1566 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1568 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1570 my $sock = IO
::Socket
::IP-
>new(
1575 GetAddrInfoFlags
=> 0,
1576 ) or die "failed to create socket: $!\n";
1577 # Inside the worker we shouldn't have any previous alarms
1578 # running anyway...:
1580 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1582 accept(my $cli, $sock) or die "connection failed: $!\n";
1585 if (PVE
::Tools
::run_command
($cmd,
1586 output
=> '>&'.fileno($cli),
1587 input
=> '<&'.fileno($cli),
1590 die "Failed to run vncproxy.\n";
1597 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1599 PVE
::Tools
::wait_for_vnc_port
($port);
1610 __PACKAGE__-
>register_method({
1611 name
=> 'termproxy',
1612 path
=> '{vmid}/termproxy',
1616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1618 description
=> "Creates a TCP proxy connections.",
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1627 enum
=> [qw(serial0 serial1 serial2 serial3)],
1628 description
=> "opens a serial terminal (defaults to display)",
1633 additionalProperties
=> 0,
1635 user
=> { type
=> 'string' },
1636 ticket
=> { type
=> 'string' },
1637 port
=> { type
=> 'integer' },
1638 upid
=> { type
=> 'string' },
1644 my $rpcenv = PVE
::RPCEnvironment
::get
();
1646 my $authuser = $rpcenv->get_user();
1648 my $vmid = $param->{vmid
};
1649 my $node = $param->{node
};
1650 my $serial = $param->{serial
};
1652 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1654 if (!defined($serial)) {
1655 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1656 $serial = $conf->{vga
};
1660 my $authpath = "/vms/$vmid";
1662 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1664 my ($remip, $family);
1666 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1667 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1669 $family = PVE
::Tools
::get_host_address_family
($node);
1672 my $port = PVE
::Tools
::next_vnc_port
($family);
1674 my $remcmd = $remip ?
1675 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1677 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1678 push @$termcmd, '-iface', $serial if $serial;
1683 syslog
('info', "starting qemu termproxy $upid\n");
1685 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1686 '--perm', 'VM.Console', '--'];
1687 push @$cmd, @$remcmd, @$termcmd;
1689 PVE
::Tools
::run_command
($cmd);
1692 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1694 PVE
::Tools
::wait_for_vnc_port
($port);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'vncwebsocket',
1706 path
=> '{vmid}/vncwebsocket',
1709 description
=> "You also need to pass a valid ticket (vncticket).",
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 description
=> "Opens a weksocket for VNC traffic.",
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1719 description
=> "Ticket from previous call to vncproxy.",
1724 description
=> "Port number returned by previous vncproxy call.",
1734 port
=> { type
=> 'string' },
1740 my $rpcenv = PVE
::RPCEnvironment
::get
();
1742 my $authuser = $rpcenv->get_user();
1744 my $vmid = $param->{vmid
};
1745 my $node = $param->{node
};
1747 my $authpath = "/vms/$vmid";
1749 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1751 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1753 # Note: VNC ports are acessible from outside, so we do not gain any
1754 # security if we verify that $param->{port} belongs to VM $vmid. This
1755 # check is done by verifying the VNC ticket (inside VNC protocol).
1757 my $port = $param->{port
};
1759 return { port
=> $port };
1762 __PACKAGE__-
>register_method({
1763 name
=> 'spiceproxy',
1764 path
=> '{vmid}/spiceproxy',
1769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1771 description
=> "Returns a SPICE configuration to connect to the VM.",
1773 additionalProperties
=> 0,
1775 node
=> get_standard_option
('pve-node'),
1776 vmid
=> get_standard_option
('pve-vmid'),
1777 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1780 returns
=> get_standard_option
('remote-viewer-config'),
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $vmid = $param->{vmid
};
1789 my $node = $param->{node
};
1790 my $proxy = $param->{proxy
};
1792 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1793 my $title = "VM $vmid";
1794 $title .= " - ". $conf->{name
} if $conf->{name
};
1796 my $port = PVE
::QemuServer
::spice_port
($vmid);
1798 my ($ticket, undef, $remote_viewer_config) =
1799 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1801 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1802 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1804 return $remote_viewer_config;
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/status',
1812 description
=> "Directory index",
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1828 subdir
=> { type
=> 'string' },
1831 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1840 { subdir
=> 'current' },
1841 { subdir
=> 'start' },
1842 { subdir
=> 'stop' },
1848 __PACKAGE__-
>register_method({
1849 name
=> 'vm_status',
1850 path
=> '{vmid}/status/current',
1853 protected
=> 1, # qemu pid files are only readable by root
1854 description
=> "Get virtual machine status.",
1856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1859 additionalProperties
=> 0,
1861 node
=> get_standard_option
('pve-node'),
1862 vmid
=> get_standard_option
('pve-vmid'),
1868 %$PVE::QemuServer
::vmstatus_return_properties
,
1870 description
=> "HA manager service status.",
1874 description
=> "Qemu VGA configuration supports spice.",
1879 description
=> "Qemu GuestAgent enabled in config.",
1889 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1891 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1892 my $status = $vmstatus->{$param->{vmid
}};
1894 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1896 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1898 $status->{agent
} = 1 if $conf->{agent
};
1903 __PACKAGE__-
>register_method({
1905 path
=> '{vmid}/status/start',
1909 description
=> "Start virtual machine.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid',
1918 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1919 skiplock
=> get_standard_option
('skiplock'),
1920 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1921 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1924 enum
=> ['secure', 'insecure'],
1925 description
=> "Migration traffic is encrypted using an SSH " .
1926 "tunnel by default. On secure, completely private networks " .
1927 "this can be disabled to increase performance.",
1930 migration_network
=> {
1931 type
=> 'string', format
=> 'CIDR',
1932 description
=> "CIDR of the (sub) network that is used for migration.",
1935 machine
=> get_standard_option
('pve-qm-machine'),
1937 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1949 my $rpcenv = PVE
::RPCEnvironment
::get
();
1951 my $authuser = $rpcenv->get_user();
1953 my $node = extract_param
($param, 'node');
1955 my $vmid = extract_param
($param, 'vmid');
1957 my $machine = extract_param
($param, 'machine');
1959 my $stateuri = extract_param
($param, 'stateuri');
1960 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1961 if $stateuri && $authuser ne 'root@pam';
1963 my $skiplock = extract_param
($param, 'skiplock');
1964 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1965 if $skiplock && $authuser ne 'root@pam';
1967 my $migratedfrom = extract_param
($param, 'migratedfrom');
1968 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1969 if $migratedfrom && $authuser ne 'root@pam';
1971 my $migration_type = extract_param
($param, 'migration_type');
1972 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1973 if $migration_type && $authuser ne 'root@pam';
1975 my $migration_network = extract_param
($param, 'migration_network');
1976 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1977 if $migration_network && $authuser ne 'root@pam';
1979 my $targetstorage = extract_param
($param, 'targetstorage');
1980 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1981 if $targetstorage && $authuser ne 'root@pam';
1983 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1984 if $targetstorage && !$migratedfrom;
1986 # read spice ticket from STDIN
1988 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1989 if (defined(my $line = <STDIN
>)) {
1991 $spice_ticket = $line;
1995 PVE
::Cluster
::check_cfs_quorum
();
1997 my $storecfg = PVE
::Storage
::config
();
1999 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2000 $rpcenv->{type
} ne 'ha') {
2005 my $service = "vm:$vmid";
2007 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2009 print "Requesting HA start for VM $vmid\n";
2011 PVE
::Tools
::run_command
($cmd);
2016 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2023 syslog
('info', "start VM $vmid: $upid\n");
2025 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2026 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2031 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2035 __PACKAGE__-
>register_method({
2037 path
=> '{vmid}/status/stop',
2041 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2042 "is akin to pulling the power plug of a running computer and may damage the VM data",
2044 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2047 additionalProperties
=> 0,
2049 node
=> get_standard_option
('pve-node'),
2050 vmid
=> get_standard_option
('pve-vmid',
2051 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2052 skiplock
=> get_standard_option
('skiplock'),
2053 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2055 description
=> "Wait maximal timeout seconds.",
2061 description
=> "Do not deactivate storage volumes.",
2074 my $rpcenv = PVE
::RPCEnvironment
::get
();
2076 my $authuser = $rpcenv->get_user();
2078 my $node = extract_param
($param, 'node');
2080 my $vmid = extract_param
($param, 'vmid');
2082 my $skiplock = extract_param
($param, 'skiplock');
2083 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2084 if $skiplock && $authuser ne 'root@pam';
2086 my $keepActive = extract_param
($param, 'keepActive');
2087 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2088 if $keepActive && $authuser ne 'root@pam';
2090 my $migratedfrom = extract_param
($param, 'migratedfrom');
2091 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2092 if $migratedfrom && $authuser ne 'root@pam';
2095 my $storecfg = PVE
::Storage
::config
();
2097 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2102 my $service = "vm:$vmid";
2104 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2106 print "Requesting HA stop for VM $vmid\n";
2108 PVE
::Tools
::run_command
($cmd);
2113 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2119 syslog
('info', "stop VM $vmid: $upid\n");
2121 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2122 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2127 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2131 __PACKAGE__-
>register_method({
2133 path
=> '{vmid}/status/reset',
2137 description
=> "Reset virtual machine.",
2139 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2142 additionalProperties
=> 0,
2144 node
=> get_standard_option
('pve-node'),
2145 vmid
=> get_standard_option
('pve-vmid',
2146 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2147 skiplock
=> get_standard_option
('skiplock'),
2156 my $rpcenv = PVE
::RPCEnvironment
::get
();
2158 my $authuser = $rpcenv->get_user();
2160 my $node = extract_param
($param, 'node');
2162 my $vmid = extract_param
($param, 'vmid');
2164 my $skiplock = extract_param
($param, 'skiplock');
2165 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2166 if $skiplock && $authuser ne 'root@pam';
2168 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2173 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2178 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2181 __PACKAGE__-
>register_method({
2182 name
=> 'vm_shutdown',
2183 path
=> '{vmid}/status/shutdown',
2187 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2188 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2190 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2193 additionalProperties
=> 0,
2195 node
=> get_standard_option
('pve-node'),
2196 vmid
=> get_standard_option
('pve-vmid',
2197 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2198 skiplock
=> get_standard_option
('skiplock'),
2200 description
=> "Wait maximal timeout seconds.",
2206 description
=> "Make sure the VM stops.",
2212 description
=> "Do not deactivate storage volumes.",
2225 my $rpcenv = PVE
::RPCEnvironment
::get
();
2227 my $authuser = $rpcenv->get_user();
2229 my $node = extract_param
($param, 'node');
2231 my $vmid = extract_param
($param, 'vmid');
2233 my $skiplock = extract_param
($param, 'skiplock');
2234 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2235 if $skiplock && $authuser ne 'root@pam';
2237 my $keepActive = extract_param
($param, 'keepActive');
2238 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2239 if $keepActive && $authuser ne 'root@pam';
2241 my $storecfg = PVE
::Storage
::config
();
2245 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2246 # otherwise, we will infer a shutdown command, but run into the timeout,
2247 # then when the vm is resumed, it will instantly shutdown
2249 # checking the qmp status here to get feedback to the gui/cli/api
2250 # and the status query should not take too long
2253 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2257 if (!$err && $qmpstatus->{status
} eq "paused") {
2258 if ($param->{forceStop
}) {
2259 warn "VM is paused - stop instead of shutdown\n";
2262 die "VM is paused - cannot shutdown\n";
2266 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2267 ($rpcenv->{type
} ne 'ha')) {
2272 my $service = "vm:$vmid";
2274 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2276 print "Requesting HA stop for VM $vmid\n";
2278 PVE
::Tools
::run_command
($cmd);
2283 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2290 syslog
('info', "shutdown VM $vmid: $upid\n");
2292 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2293 $shutdown, $param->{forceStop
}, $keepActive);
2298 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2302 __PACKAGE__-
>register_method({
2303 name
=> 'vm_suspend',
2304 path
=> '{vmid}/status/suspend',
2308 description
=> "Suspend virtual machine.",
2310 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid',
2317 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2318 skiplock
=> get_standard_option
('skiplock'),
2327 my $rpcenv = PVE
::RPCEnvironment
::get
();
2329 my $authuser = $rpcenv->get_user();
2331 my $node = extract_param
($param, 'node');
2333 my $vmid = extract_param
($param, 'vmid');
2335 my $skiplock = extract_param
($param, 'skiplock');
2336 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2337 if $skiplock && $authuser ne 'root@pam';
2339 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2344 syslog
('info', "suspend VM $vmid: $upid\n");
2346 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2351 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2354 __PACKAGE__-
>register_method({
2355 name
=> 'vm_resume',
2356 path
=> '{vmid}/status/resume',
2360 description
=> "Resume virtual machine.",
2362 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2365 additionalProperties
=> 0,
2367 node
=> get_standard_option
('pve-node'),
2368 vmid
=> get_standard_option
('pve-vmid',
2369 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2370 skiplock
=> get_standard_option
('skiplock'),
2371 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2381 my $rpcenv = PVE
::RPCEnvironment
::get
();
2383 my $authuser = $rpcenv->get_user();
2385 my $node = extract_param
($param, 'node');
2387 my $vmid = extract_param
($param, 'vmid');
2389 my $skiplock = extract_param
($param, 'skiplock');
2390 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2391 if $skiplock && $authuser ne 'root@pam';
2393 my $nocheck = extract_param
($param, 'nocheck');
2395 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2400 syslog
('info', "resume VM $vmid: $upid\n");
2402 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2407 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2410 __PACKAGE__-
>register_method({
2411 name
=> 'vm_sendkey',
2412 path
=> '{vmid}/sendkey',
2416 description
=> "Send key event to virtual machine.",
2418 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2421 additionalProperties
=> 0,
2423 node
=> get_standard_option
('pve-node'),
2424 vmid
=> get_standard_option
('pve-vmid',
2425 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2426 skiplock
=> get_standard_option
('skiplock'),
2428 description
=> "The key (qemu monitor encoding).",
2433 returns
=> { type
=> 'null'},
2437 my $rpcenv = PVE
::RPCEnvironment
::get
();
2439 my $authuser = $rpcenv->get_user();
2441 my $node = extract_param
($param, 'node');
2443 my $vmid = extract_param
($param, 'vmid');
2445 my $skiplock = extract_param
($param, 'skiplock');
2446 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2447 if $skiplock && $authuser ne 'root@pam';
2449 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2454 __PACKAGE__-
>register_method({
2455 name
=> 'vm_feature',
2456 path
=> '{vmid}/feature',
2460 description
=> "Check if feature for virtual machine is available.",
2462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2465 additionalProperties
=> 0,
2467 node
=> get_standard_option
('pve-node'),
2468 vmid
=> get_standard_option
('pve-vmid'),
2470 description
=> "Feature to check.",
2472 enum
=> [ 'snapshot', 'clone', 'copy' ],
2474 snapname
=> get_standard_option
('pve-snapshot-name', {
2482 hasFeature
=> { type
=> 'boolean' },
2485 items
=> { type
=> 'string' },
2492 my $node = extract_param
($param, 'node');
2494 my $vmid = extract_param
($param, 'vmid');
2496 my $snapname = extract_param
($param, 'snapname');
2498 my $feature = extract_param
($param, 'feature');
2500 my $running = PVE
::QemuServer
::check_running
($vmid);
2502 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2505 my $snap = $conf->{snapshots
}->{$snapname};
2506 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2509 my $storecfg = PVE
::Storage
::config
();
2511 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2512 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2515 hasFeature
=> $hasFeature,
2516 nodes
=> [ keys %$nodelist ],
2520 __PACKAGE__-
>register_method({
2522 path
=> '{vmid}/clone',
2526 description
=> "Create a copy of virtual machine/template.",
2528 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2529 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2530 "'Datastore.AllocateSpace' on any used storage.",
2533 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2535 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2536 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2541 additionalProperties
=> 0,
2543 node
=> get_standard_option
('pve-node'),
2544 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2545 newid
=> get_standard_option
('pve-vmid', {
2546 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2547 description
=> 'VMID for the clone.' }),
2550 type
=> 'string', format
=> 'dns-name',
2551 description
=> "Set a name for the new VM.",
2556 description
=> "Description for the new VM.",
2560 type
=> 'string', format
=> 'pve-poolid',
2561 description
=> "Add the new VM to the specified pool.",
2563 snapname
=> get_standard_option
('pve-snapshot-name', {
2566 storage
=> get_standard_option
('pve-storage-id', {
2567 description
=> "Target storage for full clone.",
2571 description
=> "Target format for file storage. Only valid for full clone.",
2574 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2579 description
=> "Create a full copy of all disks. This is always done when " .
2580 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2582 target
=> get_standard_option
('pve-node', {
2583 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2594 my $rpcenv = PVE
::RPCEnvironment
::get
();
2596 my $authuser = $rpcenv->get_user();
2598 my $node = extract_param
($param, 'node');
2600 my $vmid = extract_param
($param, 'vmid');
2602 my $newid = extract_param
($param, 'newid');
2604 my $pool = extract_param
($param, 'pool');
2606 if (defined($pool)) {
2607 $rpcenv->check_pool_exist($pool);
2610 my $snapname = extract_param
($param, 'snapname');
2612 my $storage = extract_param
($param, 'storage');
2614 my $format = extract_param
($param, 'format');
2616 my $target = extract_param
($param, 'target');
2618 my $localnode = PVE
::INotify
::nodename
();
2620 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2622 PVE
::Cluster
::check_node_exists
($target) if $target;
2624 my $storecfg = PVE
::Storage
::config
();
2627 # check if storage is enabled on local node
2628 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2630 # check if storage is available on target node
2631 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2632 # clone only works if target storage is shared
2633 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2634 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2638 PVE
::Cluster
::check_cfs_quorum
();
2640 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2642 # exclusive lock if VM is running - else shared lock is enough;
2643 my $shared_lock = $running ?
0 : 1;
2647 # do all tests after lock
2648 # we also try to do all tests before we fork the worker
2650 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2652 PVE
::QemuConfig-
>check_lock($conf);
2654 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2656 die "unexpected state change\n" if $verify_running != $running;
2658 die "snapshot '$snapname' does not exist\n"
2659 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2661 my $full = extract_param
($param, 'full');
2662 if (!defined($full)) {
2663 $full = !PVE
::QemuConfig-
>is_template($conf);
2666 die "parameter 'storage' not allowed for linked clones\n"
2667 if defined($storage) && !$full;
2669 die "parameter 'format' not allowed for linked clones\n"
2670 if defined($format) && !$full;
2672 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2674 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2676 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2678 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2680 die "unable to create VM $newid: config file already exists\n"
2683 my $newconf = { lock => 'clone' };
2688 foreach my $opt (keys %$oldconf) {
2689 my $value = $oldconf->{$opt};
2691 # do not copy snapshot related info
2692 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2693 $opt eq 'vmstate' || $opt eq 'snapstate';
2695 # no need to copy unused images, because VMID(owner) changes anyways
2696 next if $opt =~ m/^unused\d+$/;
2698 # always change MAC! address
2699 if ($opt =~ m/^net(\d+)$/) {
2700 my $net = PVE
::QemuServer
::parse_net
($value);
2701 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2702 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2703 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2704 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2705 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2706 die "unable to parse drive options for '$opt'\n" if !$drive;
2707 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2708 $newconf->{$opt} = $value; # simply copy configuration
2710 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2711 die "Full clone feature is not supported for drive '$opt'\n"
2712 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2713 $fullclone->{$opt} = 1;
2715 # not full means clone instead of copy
2716 die "Linked clone feature is not supported for drive '$opt'\n"
2717 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2719 $drives->{$opt} = $drive;
2720 push @$vollist, $drive->{file
};
2723 # copy everything else
2724 $newconf->{$opt} = $value;
2728 # auto generate a new uuid
2729 my ($uuid, $uuid_str);
2730 UUID
::generate
($uuid);
2731 UUID
::unparse
($uuid, $uuid_str);
2732 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2733 $smbios1->{uuid
} = $uuid_str;
2734 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2736 delete $newconf->{template
};
2738 if ($param->{name
}) {
2739 $newconf->{name
} = $param->{name
};
2741 if ($oldconf->{name
}) {
2742 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2744 $newconf->{name
} = "Copy-of-VM-$vmid";
2748 if ($param->{description
}) {
2749 $newconf->{description
} = $param->{description
};
2752 # create empty/temp config - this fails if VM already exists on other node
2753 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2758 my $newvollist = [];
2765 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2767 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2769 my $total_jobs = scalar(keys %{$drives});
2772 foreach my $opt (keys %$drives) {
2773 my $drive = $drives->{$opt};
2774 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2776 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2777 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2778 $jobs, $skipcomplete, $oldconf->{agent
});
2780 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2782 PVE
::QemuConfig-
>write_config($newid, $newconf);
2786 delete $newconf->{lock};
2788 # do not write pending changes
2789 if ($newconf->{pending
}) {
2790 warn "found pending changes, discarding for clone\n";
2791 delete $newconf->{pending
};
2794 PVE
::QemuConfig-
>write_config($newid, $newconf);
2797 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2798 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2799 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2801 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2802 die "Failed to move config to node '$target' - rename failed: $!\n"
2803 if !rename($conffile, $newconffile);
2806 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2811 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2813 sleep 1; # some storage like rbd need to wait before release volume - really?
2815 foreach my $volid (@$newvollist) {
2816 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2819 die "clone failed: $err";
2825 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2827 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2830 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2831 # Aquire exclusive lock lock for $newid
2832 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2837 __PACKAGE__-
>register_method({
2838 name
=> 'move_vm_disk',
2839 path
=> '{vmid}/move_disk',
2843 description
=> "Move volume to different storage.",
2845 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2847 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2848 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2852 additionalProperties
=> 0,
2854 node
=> get_standard_option
('pve-node'),
2855 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2858 description
=> "The disk you want to move.",
2859 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2861 storage
=> get_standard_option
('pve-storage-id', {
2862 description
=> "Target storage.",
2863 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2867 description
=> "Target Format.",
2868 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2873 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2879 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2887 description
=> "the task ID.",
2892 my $rpcenv = PVE
::RPCEnvironment
::get
();
2894 my $authuser = $rpcenv->get_user();
2896 my $node = extract_param
($param, 'node');
2898 my $vmid = extract_param
($param, 'vmid');
2900 my $digest = extract_param
($param, 'digest');
2902 my $disk = extract_param
($param, 'disk');
2904 my $storeid = extract_param
($param, 'storage');
2906 my $format = extract_param
($param, 'format');
2908 my $storecfg = PVE
::Storage
::config
();
2910 my $updatefn = sub {
2912 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2914 PVE
::QemuConfig-
>check_lock($conf);
2916 die "checksum missmatch (file change by other user?)\n"
2917 if $digest && $digest ne $conf->{digest
};
2919 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2921 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2923 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2925 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2928 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2929 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2933 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2934 (!$format || !$oldfmt || $oldfmt eq $format);
2936 # this only checks snapshots because $disk is passed!
2937 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2938 die "you can't move a disk with snapshots and delete the source\n"
2939 if $snapshotted && $param->{delete};
2941 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2943 my $running = PVE
::QemuServer
::check_running
($vmid);
2945 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2949 my $newvollist = [];
2955 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2957 warn "moving disk with snapshots, snapshots will not be moved!\n"
2960 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2961 $vmid, $storeid, $format, 1, $newvollist);
2963 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2965 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2967 # convert moved disk to base if part of template
2968 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2969 if PVE
::QemuConfig-
>is_template($conf);
2971 PVE
::QemuConfig-
>write_config($vmid, $conf);
2974 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2975 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2982 foreach my $volid (@$newvollist) {
2983 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2986 die "storage migration failed: $err";
2989 if ($param->{delete}) {
2991 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2992 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2998 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3001 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3004 __PACKAGE__-
>register_method({
3005 name
=> 'migrate_vm',
3006 path
=> '{vmid}/migrate',
3010 description
=> "Migrate virtual machine. Creates a new migration task.",
3012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3015 additionalProperties
=> 0,
3017 node
=> get_standard_option
('pve-node'),
3018 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3019 target
=> get_standard_option
('pve-node', {
3020 description
=> "Target node.",
3021 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3025 description
=> "Use online/live migration.",
3030 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3035 enum
=> ['secure', 'insecure'],
3036 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3039 migration_network
=> {
3040 type
=> 'string', format
=> 'CIDR',
3041 description
=> "CIDR of the (sub) network that is used for migration.",
3044 "with-local-disks" => {
3046 description
=> "Enable live storage migration for local disk",
3049 targetstorage
=> get_standard_option
('pve-storage-id', {
3050 description
=> "Default target storage.",
3052 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3058 description
=> "the task ID.",
3063 my $rpcenv = PVE
::RPCEnvironment
::get
();
3065 my $authuser = $rpcenv->get_user();
3067 my $target = extract_param
($param, 'target');
3069 my $localnode = PVE
::INotify
::nodename
();
3070 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3072 PVE
::Cluster
::check_cfs_quorum
();
3074 PVE
::Cluster
::check_node_exists
($target);
3076 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3078 my $vmid = extract_param
($param, 'vmid');
3080 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3081 if !$param->{online
} && $param->{targetstorage
};
3083 raise_param_exc
({ force
=> "Only root may use this option." })
3084 if $param->{force
} && $authuser ne 'root@pam';
3086 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3087 if $param->{migration_type
} && $authuser ne 'root@pam';
3089 # allow root only until better network permissions are available
3090 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3091 if $param->{migration_network
} && $authuser ne 'root@pam';
3094 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3096 # try to detect errors early
3098 PVE
::QemuConfig-
>check_lock($conf);
3100 if (PVE
::QemuServer
::check_running
($vmid)) {
3101 die "cant migrate running VM without --online\n"
3102 if !$param->{online
};
3105 my $storecfg = PVE
::Storage
::config
();
3107 if( $param->{targetstorage
}) {
3108 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3110 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3113 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3118 my $service = "vm:$vmid";
3120 my $cmd = ['ha-manager', 'migrate', $service, $target];
3122 print "Requesting HA migration for VM $vmid to node $target\n";
3124 PVE
::Tools
::run_command
($cmd);
3129 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3134 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3138 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3141 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3146 __PACKAGE__-
>register_method({
3148 path
=> '{vmid}/monitor',
3152 description
=> "Execute Qemu monitor commands.",
3154 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3155 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3158 additionalProperties
=> 0,
3160 node
=> get_standard_option
('pve-node'),
3161 vmid
=> get_standard_option
('pve-vmid'),
3164 description
=> "The monitor command.",
3168 returns
=> { type
=> 'string'},
3172 my $rpcenv = PVE
::RPCEnvironment
::get
();
3173 my $authuser = $rpcenv->get_user();
3176 my $command = shift;
3177 return $command =~ m/^\s*info(\s+|$)/
3178 || $command =~ m/^\s*help\s*$/;
3181 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3182 if !&$is_ro($param->{command
});
3184 my $vmid = $param->{vmid
};
3186 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3190 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3192 $res = "ERROR: $@" if $@;
3197 __PACKAGE__-
>register_method({
3198 name
=> 'resize_vm',
3199 path
=> '{vmid}/resize',
3203 description
=> "Extend volume size.",
3205 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3208 additionalProperties
=> 0,
3210 node
=> get_standard_option
('pve-node'),
3211 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3212 skiplock
=> get_standard_option
('skiplock'),
3215 description
=> "The disk you want to resize.",
3216 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3220 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3221 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.",
3225 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3231 returns
=> { type
=> 'null'},
3235 my $rpcenv = PVE
::RPCEnvironment
::get
();
3237 my $authuser = $rpcenv->get_user();
3239 my $node = extract_param
($param, 'node');
3241 my $vmid = extract_param
($param, 'vmid');
3243 my $digest = extract_param
($param, 'digest');
3245 my $disk = extract_param
($param, 'disk');
3247 my $sizestr = extract_param
($param, 'size');
3249 my $skiplock = extract_param
($param, 'skiplock');
3250 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3251 if $skiplock && $authuser ne 'root@pam';
3253 my $storecfg = PVE
::Storage
::config
();
3255 my $updatefn = sub {
3257 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3259 die "checksum missmatch (file change by other user?)\n"
3260 if $digest && $digest ne $conf->{digest
};
3261 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3263 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3265 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3267 my (undef, undef, undef, undef, undef, undef, $format) =
3268 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3270 die "can't resize volume: $disk if snapshot exists\n"
3271 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3273 my $volid = $drive->{file
};
3275 die "disk '$disk' has no associated volume\n" if !$volid;
3277 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3279 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3281 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3283 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3284 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3286 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3287 my ($ext, $newsize, $unit) = ($1, $2, $4);
3290 $newsize = $newsize * 1024;
3291 } elsif ($unit eq 'M') {
3292 $newsize = $newsize * 1024 * 1024;
3293 } elsif ($unit eq 'G') {
3294 $newsize = $newsize * 1024 * 1024 * 1024;
3295 } elsif ($unit eq 'T') {
3296 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3299 $newsize += $size if $ext;
3300 $newsize = int($newsize);
3302 die "shrinking disks is not supported\n" if $newsize < $size;
3304 return if $size == $newsize;
3306 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3308 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3310 $drive->{size
} = $newsize;
3311 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3313 PVE
::QemuConfig-
>write_config($vmid, $conf);
3316 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3320 __PACKAGE__-
>register_method({
3321 name
=> 'snapshot_list',
3322 path
=> '{vmid}/snapshot',
3324 description
=> "List all snapshots.",
3326 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3329 protected
=> 1, # qemu pid files are only readable by root
3331 additionalProperties
=> 0,
3333 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3334 node
=> get_standard_option
('pve-node'),
3343 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3347 description
=> "Snapshot includes RAM.",
3352 description
=> "Snapshot description.",
3356 description
=> "Snapshot creation time",
3358 renderer
=> 'timestamp',
3362 description
=> "Parent snapshot identifier.",
3368 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3373 my $vmid = $param->{vmid
};
3375 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3376 my $snaphash = $conf->{snapshots
} || {};
3380 foreach my $name (keys %$snaphash) {
3381 my $d = $snaphash->{$name};
3384 snaptime
=> $d->{snaptime
} || 0,
3385 vmstate
=> $d->{vmstate
} ?
1 : 0,
3386 description
=> $d->{description
} || '',
3388 $item->{parent
} = $d->{parent
} if $d->{parent
};
3389 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3393 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3396 digest
=> $conf->{digest
},
3397 running
=> $running,
3398 description
=> "You are here!",
3400 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3402 push @$res, $current;
3407 __PACKAGE__-
>register_method({
3409 path
=> '{vmid}/snapshot',
3413 description
=> "Snapshot a VM.",
3415 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3418 additionalProperties
=> 0,
3420 node
=> get_standard_option
('pve-node'),
3421 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3422 snapname
=> get_standard_option
('pve-snapshot-name'),
3426 description
=> "Save the vmstate",
3431 description
=> "A textual description or comment.",
3437 description
=> "the task ID.",
3442 my $rpcenv = PVE
::RPCEnvironment
::get
();
3444 my $authuser = $rpcenv->get_user();
3446 my $node = extract_param
($param, 'node');
3448 my $vmid = extract_param
($param, 'vmid');
3450 my $snapname = extract_param
($param, 'snapname');
3452 die "unable to use snapshot name 'current' (reserved name)\n"
3453 if $snapname eq 'current';
3456 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3457 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3458 $param->{description
});
3461 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3464 __PACKAGE__-
>register_method({
3465 name
=> 'snapshot_cmd_idx',
3466 path
=> '{vmid}/snapshot/{snapname}',
3473 additionalProperties
=> 0,
3475 vmid
=> get_standard_option
('pve-vmid'),
3476 node
=> get_standard_option
('pve-node'),
3477 snapname
=> get_standard_option
('pve-snapshot-name'),
3486 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3493 push @$res, { cmd
=> 'rollback' };
3494 push @$res, { cmd
=> 'config' };
3499 __PACKAGE__-
>register_method({
3500 name
=> 'update_snapshot_config',
3501 path
=> '{vmid}/snapshot/{snapname}/config',
3505 description
=> "Update snapshot metadata.",
3507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3510 additionalProperties
=> 0,
3512 node
=> get_standard_option
('pve-node'),
3513 vmid
=> get_standard_option
('pve-vmid'),
3514 snapname
=> get_standard_option
('pve-snapshot-name'),
3518 description
=> "A textual description or comment.",
3522 returns
=> { type
=> 'null' },
3526 my $rpcenv = PVE
::RPCEnvironment
::get
();
3528 my $authuser = $rpcenv->get_user();
3530 my $vmid = extract_param
($param, 'vmid');
3532 my $snapname = extract_param
($param, 'snapname');
3534 return undef if !defined($param->{description
});
3536 my $updatefn = sub {
3538 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3540 PVE
::QemuConfig-
>check_lock($conf);
3542 my $snap = $conf->{snapshots
}->{$snapname};
3544 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3546 $snap->{description
} = $param->{description
} if defined($param->{description
});
3548 PVE
::QemuConfig-
>write_config($vmid, $conf);
3551 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3556 __PACKAGE__-
>register_method({
3557 name
=> 'get_snapshot_config',
3558 path
=> '{vmid}/snapshot/{snapname}/config',
3561 description
=> "Get snapshot configuration",
3563 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3566 additionalProperties
=> 0,
3568 node
=> get_standard_option
('pve-node'),
3569 vmid
=> get_standard_option
('pve-vmid'),
3570 snapname
=> get_standard_option
('pve-snapshot-name'),
3573 returns
=> { type
=> "object" },
3577 my $rpcenv = PVE
::RPCEnvironment
::get
();
3579 my $authuser = $rpcenv->get_user();
3581 my $vmid = extract_param
($param, 'vmid');
3583 my $snapname = extract_param
($param, 'snapname');
3585 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3587 my $snap = $conf->{snapshots
}->{$snapname};
3589 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3594 __PACKAGE__-
>register_method({
3596 path
=> '{vmid}/snapshot/{snapname}/rollback',
3600 description
=> "Rollback VM state to specified snapshot.",
3602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3605 additionalProperties
=> 0,
3607 node
=> get_standard_option
('pve-node'),
3608 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3609 snapname
=> get_standard_option
('pve-snapshot-name'),
3614 description
=> "the task ID.",
3619 my $rpcenv = PVE
::RPCEnvironment
::get
();
3621 my $authuser = $rpcenv->get_user();
3623 my $node = extract_param
($param, 'node');
3625 my $vmid = extract_param
($param, 'vmid');
3627 my $snapname = extract_param
($param, 'snapname');
3630 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3631 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3635 # hold migration lock, this makes sure that nobody create replication snapshots
3636 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3639 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3642 __PACKAGE__-
>register_method({
3643 name
=> 'delsnapshot',
3644 path
=> '{vmid}/snapshot/{snapname}',
3648 description
=> "Delete a VM snapshot.",
3650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3653 additionalProperties
=> 0,
3655 node
=> get_standard_option
('pve-node'),
3656 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3657 snapname
=> get_standard_option
('pve-snapshot-name'),
3661 description
=> "For removal from config file, even if removing disk snapshots fails.",
3667 description
=> "the task ID.",
3672 my $rpcenv = PVE
::RPCEnvironment
::get
();
3674 my $authuser = $rpcenv->get_user();
3676 my $node = extract_param
($param, 'node');
3678 my $vmid = extract_param
($param, 'vmid');
3680 my $snapname = extract_param
($param, 'snapname');
3683 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3684 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3687 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3690 __PACKAGE__-
>register_method({
3692 path
=> '{vmid}/template',
3696 description
=> "Create a Template.",
3698 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3699 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3702 additionalProperties
=> 0,
3704 node
=> get_standard_option
('pve-node'),
3705 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3709 description
=> "If you want to convert only 1 disk to base image.",
3710 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3715 returns
=> { type
=> 'null'},
3719 my $rpcenv = PVE
::RPCEnvironment
::get
();
3721 my $authuser = $rpcenv->get_user();
3723 my $node = extract_param
($param, 'node');
3725 my $vmid = extract_param
($param, 'vmid');
3727 my $disk = extract_param
($param, 'disk');
3729 my $updatefn = sub {
3731 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3733 PVE
::QemuConfig-
>check_lock($conf);
3735 die "unable to create template, because VM contains snapshots\n"
3736 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3738 die "you can't convert a template to a template\n"
3739 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3741 die "you can't convert a VM to template if VM is running\n"
3742 if PVE
::QemuServer
::check_running
($vmid);
3745 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3748 $conf->{template
} = 1;
3749 PVE
::QemuConfig-
>write_config($vmid, $conf);
3751 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3754 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);