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
});
1897 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1902 __PACKAGE__-
>register_method({
1904 path
=> '{vmid}/status/start',
1908 description
=> "Start virtual machine.",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid',
1917 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1918 skiplock
=> get_standard_option
('skiplock'),
1919 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1920 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1923 enum
=> ['secure', 'insecure'],
1924 description
=> "Migration traffic is encrypted using an SSH " .
1925 "tunnel by default. On secure, completely private networks " .
1926 "this can be disabled to increase performance.",
1929 migration_network
=> {
1930 type
=> 'string', format
=> 'CIDR',
1931 description
=> "CIDR of the (sub) network that is used for migration.",
1934 machine
=> get_standard_option
('pve-qm-machine'),
1936 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1948 my $rpcenv = PVE
::RPCEnvironment
::get
();
1950 my $authuser = $rpcenv->get_user();
1952 my $node = extract_param
($param, 'node');
1954 my $vmid = extract_param
($param, 'vmid');
1956 my $machine = extract_param
($param, 'machine');
1958 my $stateuri = extract_param
($param, 'stateuri');
1959 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1960 if $stateuri && $authuser ne 'root@pam';
1962 my $skiplock = extract_param
($param, 'skiplock');
1963 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1964 if $skiplock && $authuser ne 'root@pam';
1966 my $migratedfrom = extract_param
($param, 'migratedfrom');
1967 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1968 if $migratedfrom && $authuser ne 'root@pam';
1970 my $migration_type = extract_param
($param, 'migration_type');
1971 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1972 if $migration_type && $authuser ne 'root@pam';
1974 my $migration_network = extract_param
($param, 'migration_network');
1975 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1976 if $migration_network && $authuser ne 'root@pam';
1978 my $targetstorage = extract_param
($param, 'targetstorage');
1979 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1980 if $targetstorage && $authuser ne 'root@pam';
1982 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1983 if $targetstorage && !$migratedfrom;
1985 # read spice ticket from STDIN
1987 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1988 if (defined(my $line = <STDIN
>)) {
1990 $spice_ticket = $line;
1994 PVE
::Cluster
::check_cfs_quorum
();
1996 my $storecfg = PVE
::Storage
::config
();
1998 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1999 $rpcenv->{type
} ne 'ha') {
2004 my $service = "vm:$vmid";
2006 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2008 print "Requesting HA start for VM $vmid\n";
2010 PVE
::Tools
::run_command
($cmd);
2015 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2022 syslog
('info', "start VM $vmid: $upid\n");
2024 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2025 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2030 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2034 __PACKAGE__-
>register_method({
2036 path
=> '{vmid}/status/stop',
2040 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2041 "is akin to pulling the power plug of a running computer and may damage the VM data",
2043 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid',
2050 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2051 skiplock
=> get_standard_option
('skiplock'),
2052 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2054 description
=> "Wait maximal timeout seconds.",
2060 description
=> "Do not deactivate storage volumes.",
2073 my $rpcenv = PVE
::RPCEnvironment
::get
();
2075 my $authuser = $rpcenv->get_user();
2077 my $node = extract_param
($param, 'node');
2079 my $vmid = extract_param
($param, 'vmid');
2081 my $skiplock = extract_param
($param, 'skiplock');
2082 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2083 if $skiplock && $authuser ne 'root@pam';
2085 my $keepActive = extract_param
($param, 'keepActive');
2086 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2087 if $keepActive && $authuser ne 'root@pam';
2089 my $migratedfrom = extract_param
($param, 'migratedfrom');
2090 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2091 if $migratedfrom && $authuser ne 'root@pam';
2094 my $storecfg = PVE
::Storage
::config
();
2096 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2101 my $service = "vm:$vmid";
2103 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2105 print "Requesting HA stop for VM $vmid\n";
2107 PVE
::Tools
::run_command
($cmd);
2112 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2118 syslog
('info', "stop VM $vmid: $upid\n");
2120 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2121 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2126 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2130 __PACKAGE__-
>register_method({
2132 path
=> '{vmid}/status/reset',
2136 description
=> "Reset virtual machine.",
2138 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2141 additionalProperties
=> 0,
2143 node
=> get_standard_option
('pve-node'),
2144 vmid
=> get_standard_option
('pve-vmid',
2145 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2146 skiplock
=> get_standard_option
('skiplock'),
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $skiplock = extract_param
($param, 'skiplock');
2164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2165 if $skiplock && $authuser ne 'root@pam';
2167 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2172 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2177 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2180 __PACKAGE__-
>register_method({
2181 name
=> 'vm_shutdown',
2182 path
=> '{vmid}/status/shutdown',
2186 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2187 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2189 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2192 additionalProperties
=> 0,
2194 node
=> get_standard_option
('pve-node'),
2195 vmid
=> get_standard_option
('pve-vmid',
2196 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2197 skiplock
=> get_standard_option
('skiplock'),
2199 description
=> "Wait maximal timeout seconds.",
2205 description
=> "Make sure the VM stops.",
2211 description
=> "Do not deactivate storage volumes.",
2224 my $rpcenv = PVE
::RPCEnvironment
::get
();
2226 my $authuser = $rpcenv->get_user();
2228 my $node = extract_param
($param, 'node');
2230 my $vmid = extract_param
($param, 'vmid');
2232 my $skiplock = extract_param
($param, 'skiplock');
2233 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2234 if $skiplock && $authuser ne 'root@pam';
2236 my $keepActive = extract_param
($param, 'keepActive');
2237 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2238 if $keepActive && $authuser ne 'root@pam';
2240 my $storecfg = PVE
::Storage
::config
();
2244 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2245 # otherwise, we will infer a shutdown command, but run into the timeout,
2246 # then when the vm is resumed, it will instantly shutdown
2248 # checking the qmp status here to get feedback to the gui/cli/api
2249 # and the status query should not take too long
2252 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2256 if (!$err && $qmpstatus->{status
} eq "paused") {
2257 if ($param->{forceStop
}) {
2258 warn "VM is paused - stop instead of shutdown\n";
2261 die "VM is paused - cannot shutdown\n";
2265 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2266 ($rpcenv->{type
} ne 'ha')) {
2271 my $service = "vm:$vmid";
2273 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2275 print "Requesting HA stop for VM $vmid\n";
2277 PVE
::Tools
::run_command
($cmd);
2282 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2289 syslog
('info', "shutdown VM $vmid: $upid\n");
2291 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2292 $shutdown, $param->{forceStop
}, $keepActive);
2297 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2301 __PACKAGE__-
>register_method({
2302 name
=> 'vm_suspend',
2303 path
=> '{vmid}/status/suspend',
2307 description
=> "Suspend virtual machine.",
2309 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2312 additionalProperties
=> 0,
2314 node
=> get_standard_option
('pve-node'),
2315 vmid
=> get_standard_option
('pve-vmid',
2316 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2317 skiplock
=> get_standard_option
('skiplock'),
2326 my $rpcenv = PVE
::RPCEnvironment
::get
();
2328 my $authuser = $rpcenv->get_user();
2330 my $node = extract_param
($param, 'node');
2332 my $vmid = extract_param
($param, 'vmid');
2334 my $skiplock = extract_param
($param, 'skiplock');
2335 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2336 if $skiplock && $authuser ne 'root@pam';
2338 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2343 syslog
('info', "suspend VM $vmid: $upid\n");
2345 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2350 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2353 __PACKAGE__-
>register_method({
2354 name
=> 'vm_resume',
2355 path
=> '{vmid}/status/resume',
2359 description
=> "Resume virtual machine.",
2361 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2364 additionalProperties
=> 0,
2366 node
=> get_standard_option
('pve-node'),
2367 vmid
=> get_standard_option
('pve-vmid',
2368 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2369 skiplock
=> get_standard_option
('skiplock'),
2370 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2380 my $rpcenv = PVE
::RPCEnvironment
::get
();
2382 my $authuser = $rpcenv->get_user();
2384 my $node = extract_param
($param, 'node');
2386 my $vmid = extract_param
($param, 'vmid');
2388 my $skiplock = extract_param
($param, 'skiplock');
2389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2390 if $skiplock && $authuser ne 'root@pam';
2392 my $nocheck = extract_param
($param, 'nocheck');
2394 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2399 syslog
('info', "resume VM $vmid: $upid\n");
2401 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2406 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2409 __PACKAGE__-
>register_method({
2410 name
=> 'vm_sendkey',
2411 path
=> '{vmid}/sendkey',
2415 description
=> "Send key event to virtual machine.",
2417 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2420 additionalProperties
=> 0,
2422 node
=> get_standard_option
('pve-node'),
2423 vmid
=> get_standard_option
('pve-vmid',
2424 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2425 skiplock
=> get_standard_option
('skiplock'),
2427 description
=> "The key (qemu monitor encoding).",
2432 returns
=> { type
=> 'null'},
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2438 my $authuser = $rpcenv->get_user();
2440 my $node = extract_param
($param, 'node');
2442 my $vmid = extract_param
($param, 'vmid');
2444 my $skiplock = extract_param
($param, 'skiplock');
2445 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2446 if $skiplock && $authuser ne 'root@pam';
2448 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2453 __PACKAGE__-
>register_method({
2454 name
=> 'vm_feature',
2455 path
=> '{vmid}/feature',
2459 description
=> "Check if feature for virtual machine is available.",
2461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2464 additionalProperties
=> 0,
2466 node
=> get_standard_option
('pve-node'),
2467 vmid
=> get_standard_option
('pve-vmid'),
2469 description
=> "Feature to check.",
2471 enum
=> [ 'snapshot', 'clone', 'copy' ],
2473 snapname
=> get_standard_option
('pve-snapshot-name', {
2481 hasFeature
=> { type
=> 'boolean' },
2484 items
=> { type
=> 'string' },
2491 my $node = extract_param
($param, 'node');
2493 my $vmid = extract_param
($param, 'vmid');
2495 my $snapname = extract_param
($param, 'snapname');
2497 my $feature = extract_param
($param, 'feature');
2499 my $running = PVE
::QemuServer
::check_running
($vmid);
2501 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2504 my $snap = $conf->{snapshots
}->{$snapname};
2505 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2508 my $storecfg = PVE
::Storage
::config
();
2510 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2511 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2514 hasFeature
=> $hasFeature,
2515 nodes
=> [ keys %$nodelist ],
2519 __PACKAGE__-
>register_method({
2521 path
=> '{vmid}/clone',
2525 description
=> "Create a copy of virtual machine/template.",
2527 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2528 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2529 "'Datastore.AllocateSpace' on any used storage.",
2532 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2534 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2535 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2544 newid
=> get_standard_option
('pve-vmid', {
2545 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2546 description
=> 'VMID for the clone.' }),
2549 type
=> 'string', format
=> 'dns-name',
2550 description
=> "Set a name for the new VM.",
2555 description
=> "Description for the new VM.",
2559 type
=> 'string', format
=> 'pve-poolid',
2560 description
=> "Add the new VM to the specified pool.",
2562 snapname
=> get_standard_option
('pve-snapshot-name', {
2565 storage
=> get_standard_option
('pve-storage-id', {
2566 description
=> "Target storage for full clone.",
2570 description
=> "Target format for file storage. Only valid for full clone.",
2573 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2578 description
=> "Create a full copy of all disks. This is always done when " .
2579 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2581 target
=> get_standard_option
('pve-node', {
2582 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2593 my $rpcenv = PVE
::RPCEnvironment
::get
();
2595 my $authuser = $rpcenv->get_user();
2597 my $node = extract_param
($param, 'node');
2599 my $vmid = extract_param
($param, 'vmid');
2601 my $newid = extract_param
($param, 'newid');
2603 my $pool = extract_param
($param, 'pool');
2605 if (defined($pool)) {
2606 $rpcenv->check_pool_exist($pool);
2609 my $snapname = extract_param
($param, 'snapname');
2611 my $storage = extract_param
($param, 'storage');
2613 my $format = extract_param
($param, 'format');
2615 my $target = extract_param
($param, 'target');
2617 my $localnode = PVE
::INotify
::nodename
();
2619 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2621 PVE
::Cluster
::check_node_exists
($target) if $target;
2623 my $storecfg = PVE
::Storage
::config
();
2626 # check if storage is enabled on local node
2627 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2629 # check if storage is available on target node
2630 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2631 # clone only works if target storage is shared
2632 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2633 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2637 PVE
::Cluster
::check_cfs_quorum
();
2639 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2641 # exclusive lock if VM is running - else shared lock is enough;
2642 my $shared_lock = $running ?
0 : 1;
2646 # do all tests after lock
2647 # we also try to do all tests before we fork the worker
2649 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2651 PVE
::QemuConfig-
>check_lock($conf);
2653 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2655 die "unexpected state change\n" if $verify_running != $running;
2657 die "snapshot '$snapname' does not exist\n"
2658 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2660 my $full = extract_param
($param, 'full');
2661 if (!defined($full)) {
2662 $full = !PVE
::QemuConfig-
>is_template($conf);
2665 die "parameter 'storage' not allowed for linked clones\n"
2666 if defined($storage) && !$full;
2668 die "parameter 'format' not allowed for linked clones\n"
2669 if defined($format) && !$full;
2671 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2673 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2675 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2677 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2679 die "unable to create VM $newid: config file already exists\n"
2682 my $newconf = { lock => 'clone' };
2687 foreach my $opt (keys %$oldconf) {
2688 my $value = $oldconf->{$opt};
2690 # do not copy snapshot related info
2691 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2692 $opt eq 'vmstate' || $opt eq 'snapstate';
2694 # no need to copy unused images, because VMID(owner) changes anyways
2695 next if $opt =~ m/^unused\d+$/;
2697 # always change MAC! address
2698 if ($opt =~ m/^net(\d+)$/) {
2699 my $net = PVE
::QemuServer
::parse_net
($value);
2700 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2701 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2702 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2703 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2704 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2705 die "unable to parse drive options for '$opt'\n" if !$drive;
2706 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2707 $newconf->{$opt} = $value; # simply copy configuration
2709 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2710 die "Full clone feature is not supported for drive '$opt'\n"
2711 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2712 $fullclone->{$opt} = 1;
2714 # not full means clone instead of copy
2715 die "Linked clone feature is not supported for drive '$opt'\n"
2716 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2718 $drives->{$opt} = $drive;
2719 push @$vollist, $drive->{file
};
2722 # copy everything else
2723 $newconf->{$opt} = $value;
2727 # auto generate a new uuid
2728 my ($uuid, $uuid_str);
2729 UUID
::generate
($uuid);
2730 UUID
::unparse
($uuid, $uuid_str);
2731 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2732 $smbios1->{uuid
} = $uuid_str;
2733 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2735 delete $newconf->{template
};
2737 if ($param->{name
}) {
2738 $newconf->{name
} = $param->{name
};
2740 if ($oldconf->{name
}) {
2741 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2743 $newconf->{name
} = "Copy-of-VM-$vmid";
2747 if ($param->{description
}) {
2748 $newconf->{description
} = $param->{description
};
2751 # create empty/temp config - this fails if VM already exists on other node
2752 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2757 my $newvollist = [];
2764 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2766 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2768 my $total_jobs = scalar(keys %{$drives});
2771 foreach my $opt (keys %$drives) {
2772 my $drive = $drives->{$opt};
2773 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2775 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2776 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2777 $jobs, $skipcomplete, $oldconf->{agent
});
2779 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2781 PVE
::QemuConfig-
>write_config($newid, $newconf);
2785 delete $newconf->{lock};
2787 # do not write pending changes
2788 if ($newconf->{pending
}) {
2789 warn "found pending changes, discarding for clone\n";
2790 delete $newconf->{pending
};
2793 PVE
::QemuConfig-
>write_config($newid, $newconf);
2796 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2797 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2798 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2800 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2801 die "Failed to move config to node '$target' - rename failed: $!\n"
2802 if !rename($conffile, $newconffile);
2805 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2810 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2812 sleep 1; # some storage like rbd need to wait before release volume - really?
2814 foreach my $volid (@$newvollist) {
2815 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2818 die "clone failed: $err";
2824 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2826 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2829 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2830 # Aquire exclusive lock lock for $newid
2831 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2836 __PACKAGE__-
>register_method({
2837 name
=> 'move_vm_disk',
2838 path
=> '{vmid}/move_disk',
2842 description
=> "Move volume to different storage.",
2844 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2846 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2847 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2851 additionalProperties
=> 0,
2853 node
=> get_standard_option
('pve-node'),
2854 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2857 description
=> "The disk you want to move.",
2858 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2860 storage
=> get_standard_option
('pve-storage-id', {
2861 description
=> "Target storage.",
2862 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2866 description
=> "Target Format.",
2867 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2872 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2878 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2886 description
=> "the task ID.",
2891 my $rpcenv = PVE
::RPCEnvironment
::get
();
2893 my $authuser = $rpcenv->get_user();
2895 my $node = extract_param
($param, 'node');
2897 my $vmid = extract_param
($param, 'vmid');
2899 my $digest = extract_param
($param, 'digest');
2901 my $disk = extract_param
($param, 'disk');
2903 my $storeid = extract_param
($param, 'storage');
2905 my $format = extract_param
($param, 'format');
2907 my $storecfg = PVE
::Storage
::config
();
2909 my $updatefn = sub {
2911 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2913 PVE
::QemuConfig-
>check_lock($conf);
2915 die "checksum missmatch (file change by other user?)\n"
2916 if $digest && $digest ne $conf->{digest
};
2918 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2920 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2922 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2924 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2927 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2928 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2932 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2933 (!$format || !$oldfmt || $oldfmt eq $format);
2935 # this only checks snapshots because $disk is passed!
2936 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2937 die "you can't move a disk with snapshots and delete the source\n"
2938 if $snapshotted && $param->{delete};
2940 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2942 my $running = PVE
::QemuServer
::check_running
($vmid);
2944 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2948 my $newvollist = [];
2954 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2956 warn "moving disk with snapshots, snapshots will not be moved!\n"
2959 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2960 $vmid, $storeid, $format, 1, $newvollist);
2962 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2964 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2966 # convert moved disk to base if part of template
2967 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2968 if PVE
::QemuConfig-
>is_template($conf);
2970 PVE
::QemuConfig-
>write_config($vmid, $conf);
2973 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2974 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2981 foreach my $volid (@$newvollist) {
2982 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2985 die "storage migration failed: $err";
2988 if ($param->{delete}) {
2990 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2991 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2997 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3000 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3003 __PACKAGE__-
>register_method({
3004 name
=> 'migrate_vm',
3005 path
=> '{vmid}/migrate',
3009 description
=> "Migrate virtual machine. Creates a new migration task.",
3011 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3014 additionalProperties
=> 0,
3016 node
=> get_standard_option
('pve-node'),
3017 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3018 target
=> get_standard_option
('pve-node', {
3019 description
=> "Target node.",
3020 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3024 description
=> "Use online/live migration.",
3029 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3034 enum
=> ['secure', 'insecure'],
3035 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3038 migration_network
=> {
3039 type
=> 'string', format
=> 'CIDR',
3040 description
=> "CIDR of the (sub) network that is used for migration.",
3043 "with-local-disks" => {
3045 description
=> "Enable live storage migration for local disk",
3048 targetstorage
=> get_standard_option
('pve-storage-id', {
3049 description
=> "Default target storage.",
3051 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3057 description
=> "the task ID.",
3062 my $rpcenv = PVE
::RPCEnvironment
::get
();
3064 my $authuser = $rpcenv->get_user();
3066 my $target = extract_param
($param, 'target');
3068 my $localnode = PVE
::INotify
::nodename
();
3069 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3071 PVE
::Cluster
::check_cfs_quorum
();
3073 PVE
::Cluster
::check_node_exists
($target);
3075 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3077 my $vmid = extract_param
($param, 'vmid');
3079 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3080 if !$param->{online
} && $param->{targetstorage
};
3082 raise_param_exc
({ force
=> "Only root may use this option." })
3083 if $param->{force
} && $authuser ne 'root@pam';
3085 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3086 if $param->{migration_type
} && $authuser ne 'root@pam';
3088 # allow root only until better network permissions are available
3089 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3090 if $param->{migration_network
} && $authuser ne 'root@pam';
3093 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3095 # try to detect errors early
3097 PVE
::QemuConfig-
>check_lock($conf);
3099 if (PVE
::QemuServer
::check_running
($vmid)) {
3100 die "cant migrate running VM without --online\n"
3101 if !$param->{online
};
3104 my $storecfg = PVE
::Storage
::config
();
3106 if( $param->{targetstorage
}) {
3107 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3109 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3112 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3117 my $service = "vm:$vmid";
3119 my $cmd = ['ha-manager', 'migrate', $service, $target];
3121 print "Requesting HA migration for VM $vmid to node $target\n";
3123 PVE
::Tools
::run_command
($cmd);
3128 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3133 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3137 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3140 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3145 __PACKAGE__-
>register_method({
3147 path
=> '{vmid}/monitor',
3151 description
=> "Execute Qemu monitor commands.",
3153 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3154 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3157 additionalProperties
=> 0,
3159 node
=> get_standard_option
('pve-node'),
3160 vmid
=> get_standard_option
('pve-vmid'),
3163 description
=> "The monitor command.",
3167 returns
=> { type
=> 'string'},
3171 my $rpcenv = PVE
::RPCEnvironment
::get
();
3172 my $authuser = $rpcenv->get_user();
3175 my $command = shift;
3176 return $command =~ m/^\s*info(\s+|$)/
3177 || $command =~ m/^\s*help\s*$/;
3180 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3181 if !&$is_ro($param->{command
});
3183 my $vmid = $param->{vmid
};
3185 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3189 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3191 $res = "ERROR: $@" if $@;
3196 __PACKAGE__-
>register_method({
3197 name
=> 'resize_vm',
3198 path
=> '{vmid}/resize',
3202 description
=> "Extend volume size.",
3204 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3207 additionalProperties
=> 0,
3209 node
=> get_standard_option
('pve-node'),
3210 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3211 skiplock
=> get_standard_option
('skiplock'),
3214 description
=> "The disk you want to resize.",
3215 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3219 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3220 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.",
3224 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3230 returns
=> { type
=> 'null'},
3234 my $rpcenv = PVE
::RPCEnvironment
::get
();
3236 my $authuser = $rpcenv->get_user();
3238 my $node = extract_param
($param, 'node');
3240 my $vmid = extract_param
($param, 'vmid');
3242 my $digest = extract_param
($param, 'digest');
3244 my $disk = extract_param
($param, 'disk');
3246 my $sizestr = extract_param
($param, 'size');
3248 my $skiplock = extract_param
($param, 'skiplock');
3249 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3250 if $skiplock && $authuser ne 'root@pam';
3252 my $storecfg = PVE
::Storage
::config
();
3254 my $updatefn = sub {
3256 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3258 die "checksum missmatch (file change by other user?)\n"
3259 if $digest && $digest ne $conf->{digest
};
3260 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3262 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3264 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3266 my (undef, undef, undef, undef, undef, undef, $format) =
3267 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3269 die "can't resize volume: $disk if snapshot exists\n"
3270 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3272 my $volid = $drive->{file
};
3274 die "disk '$disk' has no associated volume\n" if !$volid;
3276 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3278 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3280 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3282 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3283 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3285 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3286 my ($ext, $newsize, $unit) = ($1, $2, $4);
3289 $newsize = $newsize * 1024;
3290 } elsif ($unit eq 'M') {
3291 $newsize = $newsize * 1024 * 1024;
3292 } elsif ($unit eq 'G') {
3293 $newsize = $newsize * 1024 * 1024 * 1024;
3294 } elsif ($unit eq 'T') {
3295 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3298 $newsize += $size if $ext;
3299 $newsize = int($newsize);
3301 die "shrinking disks is not supported\n" if $newsize < $size;
3303 return if $size == $newsize;
3305 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3307 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3309 $drive->{size
} = $newsize;
3310 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3312 PVE
::QemuConfig-
>write_config($vmid, $conf);
3315 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3319 __PACKAGE__-
>register_method({
3320 name
=> 'snapshot_list',
3321 path
=> '{vmid}/snapshot',
3323 description
=> "List all snapshots.",
3325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3328 protected
=> 1, # qemu pid files are only readable by root
3330 additionalProperties
=> 0,
3332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3333 node
=> get_standard_option
('pve-node'),
3342 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3346 description
=> "Snapshot includes RAM.",
3351 description
=> "Snapshot description.",
3355 description
=> "Snapshot creation time",
3357 renderer
=> 'timestamp',
3361 description
=> "Parent snapshot identifier.",
3367 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3372 my $vmid = $param->{vmid
};
3374 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3375 my $snaphash = $conf->{snapshots
} || {};
3379 foreach my $name (keys %$snaphash) {
3380 my $d = $snaphash->{$name};
3383 snaptime
=> $d->{snaptime
} || 0,
3384 vmstate
=> $d->{vmstate
} ?
1 : 0,
3385 description
=> $d->{description
} || '',
3387 $item->{parent
} = $d->{parent
} if $d->{parent
};
3388 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3392 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3395 digest
=> $conf->{digest
},
3396 running
=> $running,
3397 description
=> "You are here!",
3399 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3401 push @$res, $current;
3406 __PACKAGE__-
>register_method({
3408 path
=> '{vmid}/snapshot',
3412 description
=> "Snapshot a VM.",
3414 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3417 additionalProperties
=> 0,
3419 node
=> get_standard_option
('pve-node'),
3420 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3421 snapname
=> get_standard_option
('pve-snapshot-name'),
3425 description
=> "Save the vmstate",
3430 description
=> "A textual description or comment.",
3436 description
=> "the task ID.",
3441 my $rpcenv = PVE
::RPCEnvironment
::get
();
3443 my $authuser = $rpcenv->get_user();
3445 my $node = extract_param
($param, 'node');
3447 my $vmid = extract_param
($param, 'vmid');
3449 my $snapname = extract_param
($param, 'snapname');
3451 die "unable to use snapshot name 'current' (reserved name)\n"
3452 if $snapname eq 'current';
3455 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3456 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3457 $param->{description
});
3460 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3463 __PACKAGE__-
>register_method({
3464 name
=> 'snapshot_cmd_idx',
3465 path
=> '{vmid}/snapshot/{snapname}',
3472 additionalProperties
=> 0,
3474 vmid
=> get_standard_option
('pve-vmid'),
3475 node
=> get_standard_option
('pve-node'),
3476 snapname
=> get_standard_option
('pve-snapshot-name'),
3485 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3492 push @$res, { cmd
=> 'rollback' };
3493 push @$res, { cmd
=> 'config' };
3498 __PACKAGE__-
>register_method({
3499 name
=> 'update_snapshot_config',
3500 path
=> '{vmid}/snapshot/{snapname}/config',
3504 description
=> "Update snapshot metadata.",
3506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3509 additionalProperties
=> 0,
3511 node
=> get_standard_option
('pve-node'),
3512 vmid
=> get_standard_option
('pve-vmid'),
3513 snapname
=> get_standard_option
('pve-snapshot-name'),
3517 description
=> "A textual description or comment.",
3521 returns
=> { type
=> 'null' },
3525 my $rpcenv = PVE
::RPCEnvironment
::get
();
3527 my $authuser = $rpcenv->get_user();
3529 my $vmid = extract_param
($param, 'vmid');
3531 my $snapname = extract_param
($param, 'snapname');
3533 return undef if !defined($param->{description
});
3535 my $updatefn = sub {
3537 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3539 PVE
::QemuConfig-
>check_lock($conf);
3541 my $snap = $conf->{snapshots
}->{$snapname};
3543 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3545 $snap->{description
} = $param->{description
} if defined($param->{description
});
3547 PVE
::QemuConfig-
>write_config($vmid, $conf);
3550 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3555 __PACKAGE__-
>register_method({
3556 name
=> 'get_snapshot_config',
3557 path
=> '{vmid}/snapshot/{snapname}/config',
3560 description
=> "Get snapshot configuration",
3562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3565 additionalProperties
=> 0,
3567 node
=> get_standard_option
('pve-node'),
3568 vmid
=> get_standard_option
('pve-vmid'),
3569 snapname
=> get_standard_option
('pve-snapshot-name'),
3572 returns
=> { type
=> "object" },
3576 my $rpcenv = PVE
::RPCEnvironment
::get
();
3578 my $authuser = $rpcenv->get_user();
3580 my $vmid = extract_param
($param, 'vmid');
3582 my $snapname = extract_param
($param, 'snapname');
3584 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3586 my $snap = $conf->{snapshots
}->{$snapname};
3588 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3593 __PACKAGE__-
>register_method({
3595 path
=> '{vmid}/snapshot/{snapname}/rollback',
3599 description
=> "Rollback VM state to specified snapshot.",
3601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3604 additionalProperties
=> 0,
3606 node
=> get_standard_option
('pve-node'),
3607 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3608 snapname
=> get_standard_option
('pve-snapshot-name'),
3613 description
=> "the task ID.",
3618 my $rpcenv = PVE
::RPCEnvironment
::get
();
3620 my $authuser = $rpcenv->get_user();
3622 my $node = extract_param
($param, 'node');
3624 my $vmid = extract_param
($param, 'vmid');
3626 my $snapname = extract_param
($param, 'snapname');
3629 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3630 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3634 # hold migration lock, this makes sure that nobody create replication snapshots
3635 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3638 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3641 __PACKAGE__-
>register_method({
3642 name
=> 'delsnapshot',
3643 path
=> '{vmid}/snapshot/{snapname}',
3647 description
=> "Delete a VM snapshot.",
3649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3652 additionalProperties
=> 0,
3654 node
=> get_standard_option
('pve-node'),
3655 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3656 snapname
=> get_standard_option
('pve-snapshot-name'),
3660 description
=> "For removal from config file, even if removing disk snapshots fails.",
3666 description
=> "the task ID.",
3671 my $rpcenv = PVE
::RPCEnvironment
::get
();
3673 my $authuser = $rpcenv->get_user();
3675 my $node = extract_param
($param, 'node');
3677 my $vmid = extract_param
($param, 'vmid');
3679 my $snapname = extract_param
($param, 'snapname');
3682 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3683 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3686 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3689 __PACKAGE__-
>register_method({
3691 path
=> '{vmid}/template',
3695 description
=> "Create a Template.",
3697 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3698 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3701 additionalProperties
=> 0,
3703 node
=> get_standard_option
('pve-node'),
3704 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3708 description
=> "If you want to convert only 1 disk to base image.",
3709 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3714 returns
=> { type
=> 'null'},
3718 my $rpcenv = PVE
::RPCEnvironment
::get
();
3720 my $authuser = $rpcenv->get_user();
3722 my $node = extract_param
($param, 'node');
3724 my $vmid = extract_param
($param, 'vmid');
3726 my $disk = extract_param
($param, 'disk');
3728 my $updatefn = sub {
3730 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3732 PVE
::QemuConfig-
>check_lock($conf);
3734 die "unable to create template, because VM contains snapshots\n"
3735 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3737 die "you can't convert a template to a template\n"
3738 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3740 die "you can't convert a VM to template if VM is running\n"
3741 if PVE
::QemuServer
::check_running
($vmid);
3744 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3747 $conf->{template
} = 1;
3748 PVE
::QemuConfig-
>write_config($vmid, $conf);
3750 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3753 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);