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 (my @changes = keys %{$newconf->{pending
}}) {
2789 my $pending = join(',', @changes);
2790 warn "found pending changes for '$pending', 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);
2973 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2974 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2978 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2979 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2986 foreach my $volid (@$newvollist) {
2987 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2990 die "storage migration failed: $err";
2993 if ($param->{delete}) {
2995 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2996 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3002 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3005 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3008 __PACKAGE__-
>register_method({
3009 name
=> 'migrate_vm',
3010 path
=> '{vmid}/migrate',
3014 description
=> "Migrate virtual machine. Creates a new migration task.",
3016 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3019 additionalProperties
=> 0,
3021 node
=> get_standard_option
('pve-node'),
3022 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3023 target
=> get_standard_option
('pve-node', {
3024 description
=> "Target node.",
3025 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3029 description
=> "Use online/live migration.",
3034 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3039 enum
=> ['secure', 'insecure'],
3040 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3043 migration_network
=> {
3044 type
=> 'string', format
=> 'CIDR',
3045 description
=> "CIDR of the (sub) network that is used for migration.",
3048 "with-local-disks" => {
3050 description
=> "Enable live storage migration for local disk",
3053 targetstorage
=> get_standard_option
('pve-storage-id', {
3054 description
=> "Default target storage.",
3056 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3062 description
=> "the task ID.",
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3071 my $target = extract_param
($param, 'target');
3073 my $localnode = PVE
::INotify
::nodename
();
3074 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3076 PVE
::Cluster
::check_cfs_quorum
();
3078 PVE
::Cluster
::check_node_exists
($target);
3080 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3082 my $vmid = extract_param
($param, 'vmid');
3084 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3085 if !$param->{online
} && $param->{targetstorage
};
3087 raise_param_exc
({ force
=> "Only root may use this option." })
3088 if $param->{force
} && $authuser ne 'root@pam';
3090 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3091 if $param->{migration_type
} && $authuser ne 'root@pam';
3093 # allow root only until better network permissions are available
3094 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3095 if $param->{migration_network
} && $authuser ne 'root@pam';
3098 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3100 # try to detect errors early
3102 PVE
::QemuConfig-
>check_lock($conf);
3104 if (PVE
::QemuServer
::check_running
($vmid)) {
3105 die "cant migrate running VM without --online\n"
3106 if !$param->{online
};
3109 my $storecfg = PVE
::Storage
::config
();
3111 if( $param->{targetstorage
}) {
3112 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3114 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3117 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3122 my $service = "vm:$vmid";
3124 my $cmd = ['ha-manager', 'migrate', $service, $target];
3126 print "Requesting HA migration for VM $vmid to node $target\n";
3128 PVE
::Tools
::run_command
($cmd);
3133 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3138 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3142 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3145 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3150 __PACKAGE__-
>register_method({
3152 path
=> '{vmid}/monitor',
3156 description
=> "Execute Qemu monitor commands.",
3158 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3159 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3162 additionalProperties
=> 0,
3164 node
=> get_standard_option
('pve-node'),
3165 vmid
=> get_standard_option
('pve-vmid'),
3168 description
=> "The monitor command.",
3172 returns
=> { type
=> 'string'},
3176 my $rpcenv = PVE
::RPCEnvironment
::get
();
3177 my $authuser = $rpcenv->get_user();
3180 my $command = shift;
3181 return $command =~ m/^\s*info(\s+|$)/
3182 || $command =~ m/^\s*help\s*$/;
3185 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3186 if !&$is_ro($param->{command
});
3188 my $vmid = $param->{vmid
};
3190 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3194 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3196 $res = "ERROR: $@" if $@;
3201 __PACKAGE__-
>register_method({
3202 name
=> 'resize_vm',
3203 path
=> '{vmid}/resize',
3207 description
=> "Extend volume size.",
3209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3212 additionalProperties
=> 0,
3214 node
=> get_standard_option
('pve-node'),
3215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3216 skiplock
=> get_standard_option
('skiplock'),
3219 description
=> "The disk you want to resize.",
3220 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3224 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3225 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.",
3229 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3235 returns
=> { type
=> 'null'},
3239 my $rpcenv = PVE
::RPCEnvironment
::get
();
3241 my $authuser = $rpcenv->get_user();
3243 my $node = extract_param
($param, 'node');
3245 my $vmid = extract_param
($param, 'vmid');
3247 my $digest = extract_param
($param, 'digest');
3249 my $disk = extract_param
($param, 'disk');
3251 my $sizestr = extract_param
($param, 'size');
3253 my $skiplock = extract_param
($param, 'skiplock');
3254 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3255 if $skiplock && $authuser ne 'root@pam';
3257 my $storecfg = PVE
::Storage
::config
();
3259 my $updatefn = sub {
3261 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3263 die "checksum missmatch (file change by other user?)\n"
3264 if $digest && $digest ne $conf->{digest
};
3265 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3267 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3269 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3271 my (undef, undef, undef, undef, undef, undef, $format) =
3272 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3274 die "can't resize volume: $disk if snapshot exists\n"
3275 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3277 my $volid = $drive->{file
};
3279 die "disk '$disk' has no associated volume\n" if !$volid;
3281 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3283 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3285 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3287 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3288 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3290 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3291 my ($ext, $newsize, $unit) = ($1, $2, $4);
3294 $newsize = $newsize * 1024;
3295 } elsif ($unit eq 'M') {
3296 $newsize = $newsize * 1024 * 1024;
3297 } elsif ($unit eq 'G') {
3298 $newsize = $newsize * 1024 * 1024 * 1024;
3299 } elsif ($unit eq 'T') {
3300 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3303 $newsize += $size if $ext;
3304 $newsize = int($newsize);
3306 die "shrinking disks is not supported\n" if $newsize < $size;
3308 return if $size == $newsize;
3310 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3312 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3314 $drive->{size
} = $newsize;
3315 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3317 PVE
::QemuConfig-
>write_config($vmid, $conf);
3320 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3324 __PACKAGE__-
>register_method({
3325 name
=> 'snapshot_list',
3326 path
=> '{vmid}/snapshot',
3328 description
=> "List all snapshots.",
3330 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3333 protected
=> 1, # qemu pid files are only readable by root
3335 additionalProperties
=> 0,
3337 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3338 node
=> get_standard_option
('pve-node'),
3347 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3351 description
=> "Snapshot includes RAM.",
3356 description
=> "Snapshot description.",
3360 description
=> "Snapshot creation time",
3362 renderer
=> 'timestamp',
3366 description
=> "Parent snapshot identifier.",
3372 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3377 my $vmid = $param->{vmid
};
3379 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3380 my $snaphash = $conf->{snapshots
} || {};
3384 foreach my $name (keys %$snaphash) {
3385 my $d = $snaphash->{$name};
3388 snaptime
=> $d->{snaptime
} || 0,
3389 vmstate
=> $d->{vmstate
} ?
1 : 0,
3390 description
=> $d->{description
} || '',
3392 $item->{parent
} = $d->{parent
} if $d->{parent
};
3393 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3397 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3400 digest
=> $conf->{digest
},
3401 running
=> $running,
3402 description
=> "You are here!",
3404 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3406 push @$res, $current;
3411 __PACKAGE__-
>register_method({
3413 path
=> '{vmid}/snapshot',
3417 description
=> "Snapshot a VM.",
3419 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3422 additionalProperties
=> 0,
3424 node
=> get_standard_option
('pve-node'),
3425 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3426 snapname
=> get_standard_option
('pve-snapshot-name'),
3430 description
=> "Save the vmstate",
3435 description
=> "A textual description or comment.",
3441 description
=> "the task ID.",
3446 my $rpcenv = PVE
::RPCEnvironment
::get
();
3448 my $authuser = $rpcenv->get_user();
3450 my $node = extract_param
($param, 'node');
3452 my $vmid = extract_param
($param, 'vmid');
3454 my $snapname = extract_param
($param, 'snapname');
3456 die "unable to use snapshot name 'current' (reserved name)\n"
3457 if $snapname eq 'current';
3460 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3461 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3462 $param->{description
});
3465 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3468 __PACKAGE__-
>register_method({
3469 name
=> 'snapshot_cmd_idx',
3470 path
=> '{vmid}/snapshot/{snapname}',
3477 additionalProperties
=> 0,
3479 vmid
=> get_standard_option
('pve-vmid'),
3480 node
=> get_standard_option
('pve-node'),
3481 snapname
=> get_standard_option
('pve-snapshot-name'),
3490 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3497 push @$res, { cmd
=> 'rollback' };
3498 push @$res, { cmd
=> 'config' };
3503 __PACKAGE__-
>register_method({
3504 name
=> 'update_snapshot_config',
3505 path
=> '{vmid}/snapshot/{snapname}/config',
3509 description
=> "Update snapshot metadata.",
3511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3514 additionalProperties
=> 0,
3516 node
=> get_standard_option
('pve-node'),
3517 vmid
=> get_standard_option
('pve-vmid'),
3518 snapname
=> get_standard_option
('pve-snapshot-name'),
3522 description
=> "A textual description or comment.",
3526 returns
=> { type
=> 'null' },
3530 my $rpcenv = PVE
::RPCEnvironment
::get
();
3532 my $authuser = $rpcenv->get_user();
3534 my $vmid = extract_param
($param, 'vmid');
3536 my $snapname = extract_param
($param, 'snapname');
3538 return undef if !defined($param->{description
});
3540 my $updatefn = sub {
3542 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3544 PVE
::QemuConfig-
>check_lock($conf);
3546 my $snap = $conf->{snapshots
}->{$snapname};
3548 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3550 $snap->{description
} = $param->{description
} if defined($param->{description
});
3552 PVE
::QemuConfig-
>write_config($vmid, $conf);
3555 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3560 __PACKAGE__-
>register_method({
3561 name
=> 'get_snapshot_config',
3562 path
=> '{vmid}/snapshot/{snapname}/config',
3565 description
=> "Get snapshot configuration",
3567 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3570 additionalProperties
=> 0,
3572 node
=> get_standard_option
('pve-node'),
3573 vmid
=> get_standard_option
('pve-vmid'),
3574 snapname
=> get_standard_option
('pve-snapshot-name'),
3577 returns
=> { type
=> "object" },
3581 my $rpcenv = PVE
::RPCEnvironment
::get
();
3583 my $authuser = $rpcenv->get_user();
3585 my $vmid = extract_param
($param, 'vmid');
3587 my $snapname = extract_param
($param, 'snapname');
3589 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3591 my $snap = $conf->{snapshots
}->{$snapname};
3593 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3598 __PACKAGE__-
>register_method({
3600 path
=> '{vmid}/snapshot/{snapname}/rollback',
3604 description
=> "Rollback VM state to specified snapshot.",
3606 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3609 additionalProperties
=> 0,
3611 node
=> get_standard_option
('pve-node'),
3612 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3613 snapname
=> get_standard_option
('pve-snapshot-name'),
3618 description
=> "the task ID.",
3623 my $rpcenv = PVE
::RPCEnvironment
::get
();
3625 my $authuser = $rpcenv->get_user();
3627 my $node = extract_param
($param, 'node');
3629 my $vmid = extract_param
($param, 'vmid');
3631 my $snapname = extract_param
($param, 'snapname');
3634 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3635 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3639 # hold migration lock, this makes sure that nobody create replication snapshots
3640 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3643 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3646 __PACKAGE__-
>register_method({
3647 name
=> 'delsnapshot',
3648 path
=> '{vmid}/snapshot/{snapname}',
3652 description
=> "Delete a VM snapshot.",
3654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3657 additionalProperties
=> 0,
3659 node
=> get_standard_option
('pve-node'),
3660 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3661 snapname
=> get_standard_option
('pve-snapshot-name'),
3665 description
=> "For removal from config file, even if removing disk snapshots fails.",
3671 description
=> "the task ID.",
3676 my $rpcenv = PVE
::RPCEnvironment
::get
();
3678 my $authuser = $rpcenv->get_user();
3680 my $node = extract_param
($param, 'node');
3682 my $vmid = extract_param
($param, 'vmid');
3684 my $snapname = extract_param
($param, 'snapname');
3687 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3688 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3691 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3694 __PACKAGE__-
>register_method({
3696 path
=> '{vmid}/template',
3700 description
=> "Create a Template.",
3702 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3703 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3706 additionalProperties
=> 0,
3708 node
=> get_standard_option
('pve-node'),
3709 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3713 description
=> "If you want to convert only 1 disk to base image.",
3714 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3719 returns
=> { type
=> 'null'},
3723 my $rpcenv = PVE
::RPCEnvironment
::get
();
3725 my $authuser = $rpcenv->get_user();
3727 my $node = extract_param
($param, 'node');
3729 my $vmid = extract_param
($param, 'vmid');
3731 my $disk = extract_param
($param, 'disk');
3733 my $updatefn = sub {
3735 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3737 PVE
::QemuConfig-
>check_lock($conf);
3739 die "unable to create template, because VM contains snapshots\n"
3740 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3742 die "you can't convert a template to a template\n"
3743 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3745 die "you can't convert a VM to template if VM is running\n"
3746 if PVE
::QemuServer
::check_running
($vmid);
3749 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3752 $conf->{template
} = 1;
3753 PVE
::QemuConfig-
>write_config($vmid, $conf);
3755 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3758 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);