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 $check_vm_modify_config_perm = sub {
295 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
297 return 1 if $authuser eq 'root@pam';
299 foreach my $opt (@$key_list) {
300 # disk checks need to be done somewhere else
301 next if PVE
::QemuServer
::is_valid_drivename
($opt);
302 next if $opt eq 'cdrom';
303 next if $opt =~ m/^unused\d+$/;
305 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
306 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
307 } elsif ($memoryoptions->{$opt}) {
308 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
309 } elsif ($hwtypeoptions->{$opt}) {
310 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
311 } elsif ($generaloptions->{$opt}) {
312 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
313 # special case for startup since it changes host behaviour
314 if ($opt eq 'startup') {
315 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
317 } elsif ($vmpoweroptions->{$opt}) {
318 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
319 } elsif ($diskoptions->{$opt}) {
320 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
321 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
322 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
324 # catches usb\d+, hostpci\d+, args, lock, etc.
325 # new options will be checked here
326 die "only root can set '$opt' config\n";
333 __PACKAGE__-
>register_method({
337 description
=> "Virtual machine index (per node).",
339 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
343 protected
=> 1, # qemu pid files are only readable by root
345 additionalProperties
=> 0,
347 node
=> get_standard_option
('pve-node'),
351 description
=> "Determine the full status of active VMs.",
361 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
366 my $rpcenv = PVE
::RPCEnvironment
::get
();
367 my $authuser = $rpcenv->get_user();
369 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
372 foreach my $vmid (keys %$vmstatus) {
373 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
375 my $data = $vmstatus->{$vmid};
376 $data->{vmid
} = int($vmid);
385 __PACKAGE__-
>register_method({
389 description
=> "Create or restore a virtual machine.",
391 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
392 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
393 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
394 user
=> 'all', # check inside
399 additionalProperties
=> 0,
400 properties
=> PVE
::QemuServer
::json_config_properties
(
402 node
=> get_standard_option
('pve-node'),
403 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
405 description
=> "The backup file.",
409 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
411 storage
=> get_standard_option
('pve-storage-id', {
412 description
=> "Default storage.",
414 completion
=> \
&PVE
::QemuServer
::complete_storage
,
419 description
=> "Allow to overwrite existing VM.",
420 requires
=> 'archive',
425 description
=> "Assign a unique random ethernet address.",
426 requires
=> 'archive',
430 type
=> 'string', format
=> 'pve-poolid',
431 description
=> "Add the VM to the specified pool.",
441 my $rpcenv = PVE
::RPCEnvironment
::get
();
443 my $authuser = $rpcenv->get_user();
445 my $node = extract_param
($param, 'node');
447 my $vmid = extract_param
($param, 'vmid');
449 my $archive = extract_param
($param, 'archive');
451 my $storage = extract_param
($param, 'storage');
453 my $force = extract_param
($param, 'force');
455 my $unique = extract_param
($param, 'unique');
457 my $pool = extract_param
($param, 'pool');
459 my $filename = PVE
::QemuConfig-
>config_file($vmid);
461 my $storecfg = PVE
::Storage
::config
();
463 if (defined(my $ssh_keys = $param->{sshkeys
})) {
464 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
465 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
468 PVE
::Cluster
::check_cfs_quorum
();
470 if (defined($pool)) {
471 $rpcenv->check_pool_exist($pool);
474 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
475 if defined($storage);
477 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
479 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
481 } elsif ($archive && $force && (-f
$filename) &&
482 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
483 # OK: user has VM.Backup permissions, and want to restore an existing VM
489 &$resolve_cdrom_alias($param);
491 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
493 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
495 foreach my $opt (keys %$param) {
496 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
497 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
498 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
500 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
501 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
505 PVE
::QemuServer
::add_random_macs
($param);
507 my $keystr = join(' ', keys %$param);
508 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
510 if ($archive eq '-') {
511 die "pipe requires cli environment\n"
512 if $rpcenv->{type
} ne 'cli';
514 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
515 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
519 my $restorefn = sub {
520 my $vmlist = PVE
::Cluster
::get_vmlist
();
521 if ($vmlist->{ids
}->{$vmid}) {
522 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
523 if ($current_node eq $node) {
524 my $conf = PVE
::QemuConfig-
>load_config($vmid);
526 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
528 die "unable to restore vm $vmid - config file already exists\n"
531 die "unable to restore vm $vmid - vm is running\n"
532 if PVE
::QemuServer
::check_running
($vmid);
534 die "unable to restore vm $vmid - vm is a template\n"
535 if PVE
::QemuConfig-
>is_template($conf);
538 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
543 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
546 unique
=> $unique });
548 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
551 # ensure no old replication state are exists
552 PVE
::ReplicationState
::delete_guest_states
($vmid);
554 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
560 PVE
::Cluster
::check_vmid_unused
($vmid);
562 # ensure no old replication state are exists
563 PVE
::ReplicationState
::delete_guest_states
($vmid);
573 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
575 if (!$conf->{bootdisk
}) {
576 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
577 $conf->{bootdisk
} = $firstdisk if $firstdisk;
580 # auto generate uuid if user did not specify smbios1 option
581 if (!$conf->{smbios1
}) {
582 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
585 PVE
::QemuConfig-
>write_config($vmid, $conf);
591 foreach my $volid (@$vollist) {
592 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
595 die "create failed - $err";
598 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
601 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
604 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
607 __PACKAGE__-
>register_method({
612 description
=> "Directory index",
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
620 vmid
=> get_standard_option
('pve-vmid'),
628 subdir
=> { type
=> 'string' },
631 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
637 { subdir
=> 'config' },
638 { subdir
=> 'pending' },
639 { subdir
=> 'status' },
640 { subdir
=> 'unlink' },
641 { subdir
=> 'vncproxy' },
642 { subdir
=> 'termproxy' },
643 { subdir
=> 'migrate' },
644 { subdir
=> 'resize' },
645 { subdir
=> 'move' },
647 { subdir
=> 'rrddata' },
648 { subdir
=> 'monitor' },
649 { subdir
=> 'agent' },
650 { subdir
=> 'snapshot' },
651 { subdir
=> 'spiceproxy' },
652 { subdir
=> 'sendkey' },
653 { subdir
=> 'firewall' },
659 __PACKAGE__-
>register_method ({
660 subclass
=> "PVE::API2::Firewall::VM",
661 path
=> '{vmid}/firewall',
664 __PACKAGE__-
>register_method ({
665 subclass
=> "PVE::API2::Qemu::Agent",
666 path
=> '{vmid}/agent',
669 __PACKAGE__-
>register_method({
671 path
=> '{vmid}/rrd',
673 protected
=> 1, # fixme: can we avoid that?
675 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
677 description
=> "Read VM RRD statistics (returns PNG)",
679 additionalProperties
=> 0,
681 node
=> get_standard_option
('pve-node'),
682 vmid
=> get_standard_option
('pve-vmid'),
684 description
=> "Specify the time frame you are interested in.",
686 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
689 description
=> "The list of datasources you want to display.",
690 type
=> 'string', format
=> 'pve-configid-list',
693 description
=> "The RRD consolidation function",
695 enum
=> [ 'AVERAGE', 'MAX' ],
703 filename
=> { type
=> 'string' },
709 return PVE
::Cluster
::create_rrd_graph
(
710 "pve2-vm/$param->{vmid}", $param->{timeframe
},
711 $param->{ds
}, $param->{cf
});
715 __PACKAGE__-
>register_method({
717 path
=> '{vmid}/rrddata',
719 protected
=> 1, # fixme: can we avoid that?
721 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
723 description
=> "Read VM RRD statistics",
725 additionalProperties
=> 0,
727 node
=> get_standard_option
('pve-node'),
728 vmid
=> get_standard_option
('pve-vmid'),
730 description
=> "Specify the time frame you are interested in.",
732 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
735 description
=> "The RRD consolidation function",
737 enum
=> [ 'AVERAGE', 'MAX' ],
752 return PVE
::Cluster
::create_rrd_data
(
753 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
757 __PACKAGE__-
>register_method({
759 path
=> '{vmid}/config',
762 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
767 additionalProperties
=> 0,
769 node
=> get_standard_option
('pve-node'),
770 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
772 description
=> "Get current values (instead of pending values).",
784 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
791 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
793 delete $conf->{snapshots
};
795 if (!$param->{current
}) {
796 foreach my $opt (keys %{$conf->{pending
}}) {
797 next if $opt eq 'delete';
798 my $value = $conf->{pending
}->{$opt};
799 next if ref($value); # just to be sure
800 $conf->{$opt} = $value;
802 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
803 foreach my $opt (keys %$pending_delete_hash) {
804 delete $conf->{$opt} if $conf->{$opt};
808 delete $conf->{pending
};
813 __PACKAGE__-
>register_method({
814 name
=> 'vm_pending',
815 path
=> '{vmid}/pending',
818 description
=> "Get virtual machine configuration, including pending changes.",
820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
823 additionalProperties
=> 0,
825 node
=> get_standard_option
('pve-node'),
826 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
835 description
=> "Configuration option name.",
839 description
=> "Current value.",
844 description
=> "Pending value.",
849 description
=> "Indicates a pending delete request if present and not 0. " .
850 "The value 2 indicates a force-delete request.",
862 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
864 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
868 foreach my $opt (keys %$conf) {
869 next if ref($conf->{$opt});
870 my $item = { key
=> $opt };
871 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
872 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
873 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
877 foreach my $opt (keys %{$conf->{pending
}}) {
878 next if $opt eq 'delete';
879 next if ref($conf->{pending
}->{$opt}); # just to be sure
880 next if defined($conf->{$opt});
881 my $item = { key
=> $opt };
882 $item->{pending
} = $conf->{pending
}->{$opt};
886 while (my ($opt, $force) = each %$pending_delete_hash) {
887 next if $conf->{pending
}->{$opt}; # just to be sure
888 next if $conf->{$opt};
889 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
896 # POST/PUT {vmid}/config implementation
898 # The original API used PUT (idempotent) an we assumed that all operations
899 # are fast. But it turned out that almost any configuration change can
900 # involve hot-plug actions, or disk alloc/free. Such actions can take long
901 # time to complete and have side effects (not idempotent).
903 # The new implementation uses POST and forks a worker process. We added
904 # a new option 'background_delay'. If specified we wait up to
905 # 'background_delay' second for the worker task to complete. It returns null
906 # if the task is finished within that time, else we return the UPID.
908 my $update_vm_api = sub {
909 my ($param, $sync) = @_;
911 my $rpcenv = PVE
::RPCEnvironment
::get
();
913 my $authuser = $rpcenv->get_user();
915 my $node = extract_param
($param, 'node');
917 my $vmid = extract_param
($param, 'vmid');
919 my $digest = extract_param
($param, 'digest');
921 my $background_delay = extract_param
($param, 'background_delay');
924 my @paramarr = (); # used for log message
925 foreach my $key (sort keys %$param) {
926 push @paramarr, "-$key", $param->{$key};
929 my $skiplock = extract_param
($param, 'skiplock');
930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
931 if $skiplock && $authuser ne 'root@pam';
933 my $delete_str = extract_param
($param, 'delete');
935 my $revert_str = extract_param
($param, 'revert');
937 my $force = extract_param
($param, 'force');
939 if (defined(my $ssh_keys = $param->{sshkeys
})) {
940 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
941 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
944 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
946 my $storecfg = PVE
::Storage
::config
();
948 my $defaults = PVE
::QemuServer
::load_defaults
();
950 &$resolve_cdrom_alias($param);
952 # now try to verify all parameters
955 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
956 if (!PVE
::QemuServer
::option_exists
($opt)) {
957 raise_param_exc
({ revert
=> "unknown option '$opt'" });
960 raise_param_exc
({ delete => "you can't use '-$opt' and " .
961 "-revert $opt' at the same time" })
962 if defined($param->{$opt});
968 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
969 $opt = 'ide2' if $opt eq 'cdrom';
971 raise_param_exc
({ delete => "you can't use '-$opt' and " .
972 "-delete $opt' at the same time" })
973 if defined($param->{$opt});
975 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
976 "-revert $opt' at the same time" })
979 if (!PVE
::QemuServer
::option_exists
($opt)) {
980 raise_param_exc
({ delete => "unknown option '$opt'" });
986 my $repl_conf = PVE
::ReplicationConfig-
>new();
987 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
988 my $check_replication = sub {
990 return if !$is_replicated;
991 my $volid = $drive->{file
};
992 return if !$volid || !($drive->{replicate
}//1);
993 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
994 my ($storeid, $format);
995 if ($volid =~ $NEW_DISK_RE) {
997 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
999 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1000 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1002 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1003 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1004 return if $scfg->{shared
};
1005 die "cannot add non-replicatable volume to a replicated VM\n";
1008 foreach my $opt (keys %$param) {
1009 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1010 # cleanup drive path
1011 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1012 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1013 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1014 $check_replication->($drive);
1015 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1016 } elsif ($opt =~ m/^net(\d+)$/) {
1018 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1019 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1023 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1025 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1027 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1029 my $updatefn = sub {
1031 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1033 die "checksum missmatch (file change by other user?)\n"
1034 if $digest && $digest ne $conf->{digest
};
1036 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1038 foreach my $opt (keys %$revert) {
1039 if (defined($conf->{$opt})) {
1040 $param->{$opt} = $conf->{$opt};
1041 } elsif (defined($conf->{pending
}->{$opt})) {
1046 if ($param->{memory
} || defined($param->{balloon
})) {
1047 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1048 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1050 die "balloon value too large (must be smaller than assigned memory)\n"
1051 if $balloon && $balloon > $maxmem;
1054 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1058 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1060 # write updates to pending section
1062 my $modified = {}; # record what $option we modify
1064 foreach my $opt (@delete) {
1065 $modified->{$opt} = 1;
1066 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1067 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1068 warn "cannot delete '$opt' - not set in current configuration!\n";
1069 $modified->{$opt} = 0;
1073 if ($opt =~ m/^unused/) {
1074 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1075 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1076 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1077 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1078 delete $conf->{$opt};
1079 PVE
::QemuConfig-
>write_config($vmid, $conf);
1081 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1082 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1083 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1084 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1085 if defined($conf->{pending
}->{$opt});
1086 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1087 PVE
::QemuConfig-
>write_config($vmid, $conf);
1089 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1090 PVE
::QemuConfig-
>write_config($vmid, $conf);
1094 foreach my $opt (keys %$param) { # add/change
1095 $modified->{$opt} = 1;
1096 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1097 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1099 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1100 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1101 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1102 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1104 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1106 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1107 if defined($conf->{pending
}->{$opt});
1109 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1111 $conf->{pending
}->{$opt} = $param->{$opt};
1113 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1114 PVE
::QemuConfig-
>write_config($vmid, $conf);
1117 # remove pending changes when nothing changed
1118 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1119 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1120 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1122 return if !scalar(keys %{$conf->{pending
}});
1124 my $running = PVE
::QemuServer
::check_running
($vmid);
1126 # apply pending changes
1128 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1132 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1133 raise_param_exc
($errors) if scalar(keys %$errors);
1135 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1145 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1147 if ($background_delay) {
1149 # Note: It would be better to do that in the Event based HTTPServer
1150 # to avoid blocking call to sleep.
1152 my $end_time = time() + $background_delay;
1154 my $task = PVE
::Tools
::upid_decode
($upid);
1157 while (time() < $end_time) {
1158 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1160 sleep(1); # this gets interrupted when child process ends
1164 my $status = PVE
::Tools
::upid_read_status
($upid);
1165 return undef if $status eq 'OK';
1174 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1177 my $vm_config_perm_list = [
1182 'VM.Config.Network',
1184 'VM.Config.Options',
1187 __PACKAGE__-
>register_method({
1188 name
=> 'update_vm_async',
1189 path
=> '{vmid}/config',
1193 description
=> "Set virtual machine options (asynchrounous API).",
1195 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1198 additionalProperties
=> 0,
1199 properties
=> PVE
::QemuServer
::json_config_properties
(
1201 node
=> get_standard_option
('pve-node'),
1202 vmid
=> get_standard_option
('pve-vmid'),
1203 skiplock
=> get_standard_option
('skiplock'),
1205 type
=> 'string', format
=> 'pve-configid-list',
1206 description
=> "A list of settings you want to delete.",
1210 type
=> 'string', format
=> 'pve-configid-list',
1211 description
=> "Revert a pending change.",
1216 description
=> $opt_force_description,
1218 requires
=> 'delete',
1222 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1226 background_delay
=> {
1228 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1239 code
=> $update_vm_api,
1242 __PACKAGE__-
>register_method({
1243 name
=> 'update_vm',
1244 path
=> '{vmid}/config',
1248 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1250 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1253 additionalProperties
=> 0,
1254 properties
=> PVE
::QemuServer
::json_config_properties
(
1256 node
=> get_standard_option
('pve-node'),
1257 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1258 skiplock
=> get_standard_option
('skiplock'),
1260 type
=> 'string', format
=> 'pve-configid-list',
1261 description
=> "A list of settings you want to delete.",
1265 type
=> 'string', format
=> 'pve-configid-list',
1266 description
=> "Revert a pending change.",
1271 description
=> $opt_force_description,
1273 requires
=> 'delete',
1277 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1283 returns
=> { type
=> 'null' },
1286 &$update_vm_api($param, 1);
1292 __PACKAGE__-
>register_method({
1293 name
=> 'destroy_vm',
1298 description
=> "Destroy the vm (also delete all used/owned volumes).",
1300 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1303 additionalProperties
=> 0,
1305 node
=> get_standard_option
('pve-node'),
1306 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1307 skiplock
=> get_standard_option
('skiplock'),
1316 my $rpcenv = PVE
::RPCEnvironment
::get
();
1318 my $authuser = $rpcenv->get_user();
1320 my $vmid = $param->{vmid
};
1322 my $skiplock = $param->{skiplock
};
1323 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1324 if $skiplock && $authuser ne 'root@pam';
1327 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1329 my $storecfg = PVE
::Storage
::config
();
1331 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1333 die "unable to remove VM $vmid - used in HA resources\n"
1334 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1336 # do not allow destroy if there are replication jobs
1337 my $repl_conf = PVE
::ReplicationConfig-
>new();
1338 $repl_conf->check_for_existing_jobs($vmid);
1340 # early tests (repeat after locking)
1341 die "VM $vmid is running - destroy failed\n"
1342 if PVE
::QemuServer
::check_running
($vmid);
1347 syslog
('info', "destroy VM $vmid: $upid\n");
1349 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1351 PVE
::AccessControl
::remove_vm_access
($vmid);
1353 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1356 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1359 __PACKAGE__-
>register_method({
1361 path
=> '{vmid}/unlink',
1365 description
=> "Unlink/delete disk images.",
1367 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1370 additionalProperties
=> 0,
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1375 type
=> 'string', format
=> 'pve-configid-list',
1376 description
=> "A list of disk IDs you want to delete.",
1380 description
=> $opt_force_description,
1385 returns
=> { type
=> 'null'},
1389 $param->{delete} = extract_param
($param, 'idlist');
1391 __PACKAGE__-
>update_vm($param);
1398 __PACKAGE__-
>register_method({
1400 path
=> '{vmid}/vncproxy',
1404 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1406 description
=> "Creates a TCP VNC proxy connections.",
1408 additionalProperties
=> 0,
1410 node
=> get_standard_option
('pve-node'),
1411 vmid
=> get_standard_option
('pve-vmid'),
1415 description
=> "starts websockify instead of vncproxy",
1420 additionalProperties
=> 0,
1422 user
=> { type
=> 'string' },
1423 ticket
=> { type
=> 'string' },
1424 cert
=> { type
=> 'string' },
1425 port
=> { type
=> 'integer' },
1426 upid
=> { type
=> 'string' },
1432 my $rpcenv = PVE
::RPCEnvironment
::get
();
1434 my $authuser = $rpcenv->get_user();
1436 my $vmid = $param->{vmid
};
1437 my $node = $param->{node
};
1438 my $websocket = $param->{websocket
};
1440 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1442 my $authpath = "/vms/$vmid";
1444 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1446 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1449 my ($remip, $family);
1452 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1453 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1454 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1455 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1457 $family = PVE
::Tools
::get_host_address_family
($node);
1460 my $port = PVE
::Tools
::next_vnc_port
($family);
1467 syslog
('info', "starting vnc proxy $upid\n");
1471 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1474 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1476 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1477 '-timeout', $timeout, '-authpath', $authpath,
1478 '-perm', 'Sys.Console'];
1480 if ($param->{websocket
}) {
1481 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1482 push @$cmd, '-notls', '-listen', 'localhost';
1485 push @$cmd, '-c', @$remcmd, @$termcmd;
1487 PVE
::Tools
::run_command
($cmd);
1491 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1493 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1495 my $sock = IO
::Socket
::IP-
>new(
1500 GetAddrInfoFlags
=> 0,
1501 ) or die "failed to create socket: $!\n";
1502 # Inside the worker we shouldn't have any previous alarms
1503 # running anyway...:
1505 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1507 accept(my $cli, $sock) or die "connection failed: $!\n";
1510 if (PVE
::Tools
::run_command
($cmd,
1511 output
=> '>&'.fileno($cli),
1512 input
=> '<&'.fileno($cli),
1515 die "Failed to run vncproxy.\n";
1522 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1524 PVE
::Tools
::wait_for_vnc_port
($port);
1535 __PACKAGE__-
>register_method({
1536 name
=> 'termproxy',
1537 path
=> '{vmid}/termproxy',
1541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1543 description
=> "Creates a TCP proxy connections.",
1545 additionalProperties
=> 0,
1547 node
=> get_standard_option
('pve-node'),
1548 vmid
=> get_standard_option
('pve-vmid'),
1552 enum
=> [qw(serial0 serial1 serial2 serial3)],
1553 description
=> "opens a serial terminal (defaults to display)",
1558 additionalProperties
=> 0,
1560 user
=> { type
=> 'string' },
1561 ticket
=> { type
=> 'string' },
1562 port
=> { type
=> 'integer' },
1563 upid
=> { type
=> 'string' },
1569 my $rpcenv = PVE
::RPCEnvironment
::get
();
1571 my $authuser = $rpcenv->get_user();
1573 my $vmid = $param->{vmid
};
1574 my $node = $param->{node
};
1575 my $serial = $param->{serial
};
1577 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1579 if (!defined($serial)) {
1580 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1581 $serial = $conf->{vga
};
1585 my $authpath = "/vms/$vmid";
1587 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1589 my ($remip, $family);
1591 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1592 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1594 $family = PVE
::Tools
::get_host_address_family
($node);
1597 my $port = PVE
::Tools
::next_vnc_port
($family);
1599 my $remcmd = $remip ?
1600 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1602 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1603 push @$termcmd, '-iface', $serial if $serial;
1608 syslog
('info', "starting qemu termproxy $upid\n");
1610 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1611 '--perm', 'VM.Console', '--'];
1612 push @$cmd, @$remcmd, @$termcmd;
1614 PVE
::Tools
::run_command
($cmd);
1617 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1619 PVE
::Tools
::wait_for_vnc_port
($port);
1629 __PACKAGE__-
>register_method({
1630 name
=> 'vncwebsocket',
1631 path
=> '{vmid}/vncwebsocket',
1634 description
=> "You also need to pass a valid ticket (vncticket).",
1635 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1637 description
=> "Opens a weksocket for VNC traffic.",
1639 additionalProperties
=> 0,
1641 node
=> get_standard_option
('pve-node'),
1642 vmid
=> get_standard_option
('pve-vmid'),
1644 description
=> "Ticket from previous call to vncproxy.",
1649 description
=> "Port number returned by previous vncproxy call.",
1659 port
=> { type
=> 'string' },
1665 my $rpcenv = PVE
::RPCEnvironment
::get
();
1667 my $authuser = $rpcenv->get_user();
1669 my $vmid = $param->{vmid
};
1670 my $node = $param->{node
};
1672 my $authpath = "/vms/$vmid";
1674 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1676 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1678 # Note: VNC ports are acessible from outside, so we do not gain any
1679 # security if we verify that $param->{port} belongs to VM $vmid. This
1680 # check is done by verifying the VNC ticket (inside VNC protocol).
1682 my $port = $param->{port
};
1684 return { port
=> $port };
1687 __PACKAGE__-
>register_method({
1688 name
=> 'spiceproxy',
1689 path
=> '{vmid}/spiceproxy',
1694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1696 description
=> "Returns a SPICE configuration to connect to the VM.",
1698 additionalProperties
=> 0,
1700 node
=> get_standard_option
('pve-node'),
1701 vmid
=> get_standard_option
('pve-vmid'),
1702 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1705 returns
=> get_standard_option
('remote-viewer-config'),
1709 my $rpcenv = PVE
::RPCEnvironment
::get
();
1711 my $authuser = $rpcenv->get_user();
1713 my $vmid = $param->{vmid
};
1714 my $node = $param->{node
};
1715 my $proxy = $param->{proxy
};
1717 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1718 my $title = "VM $vmid";
1719 $title .= " - ". $conf->{name
} if $conf->{name
};
1721 my $port = PVE
::QemuServer
::spice_port
($vmid);
1723 my ($ticket, undef, $remote_viewer_config) =
1724 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1726 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1727 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1729 return $remote_viewer_config;
1732 __PACKAGE__-
>register_method({
1734 path
=> '{vmid}/status',
1737 description
=> "Directory index",
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1753 subdir
=> { type
=> 'string' },
1756 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1762 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1765 { subdir
=> 'current' },
1766 { subdir
=> 'start' },
1767 { subdir
=> 'stop' },
1773 __PACKAGE__-
>register_method({
1774 name
=> 'vm_status',
1775 path
=> '{vmid}/status/current',
1778 protected
=> 1, # qemu pid files are only readable by root
1779 description
=> "Get virtual machine status.",
1781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1784 additionalProperties
=> 0,
1786 node
=> get_standard_option
('pve-node'),
1787 vmid
=> get_standard_option
('pve-vmid'),
1790 returns
=> { type
=> 'object' },
1795 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1797 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1798 my $status = $vmstatus->{$param->{vmid
}};
1800 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1802 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1804 $status->{agent
} = 1 if $conf->{agent
};
1809 __PACKAGE__-
>register_method({
1811 path
=> '{vmid}/status/start',
1815 description
=> "Start virtual machine.",
1817 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1820 additionalProperties
=> 0,
1822 node
=> get_standard_option
('pve-node'),
1823 vmid
=> get_standard_option
('pve-vmid',
1824 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1825 skiplock
=> get_standard_option
('skiplock'),
1826 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1827 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1830 enum
=> ['secure', 'insecure'],
1831 description
=> "Migration traffic is encrypted using an SSH " .
1832 "tunnel by default. On secure, completely private networks " .
1833 "this can be disabled to increase performance.",
1836 migration_network
=> {
1837 type
=> 'string', format
=> 'CIDR',
1838 description
=> "CIDR of the (sub) network that is used for migration.",
1841 machine
=> get_standard_option
('pve-qm-machine'),
1843 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1855 my $rpcenv = PVE
::RPCEnvironment
::get
();
1857 my $authuser = $rpcenv->get_user();
1859 my $node = extract_param
($param, 'node');
1861 my $vmid = extract_param
($param, 'vmid');
1863 my $machine = extract_param
($param, 'machine');
1865 my $stateuri = extract_param
($param, 'stateuri');
1866 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1867 if $stateuri && $authuser ne 'root@pam';
1869 my $skiplock = extract_param
($param, 'skiplock');
1870 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1871 if $skiplock && $authuser ne 'root@pam';
1873 my $migratedfrom = extract_param
($param, 'migratedfrom');
1874 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1875 if $migratedfrom && $authuser ne 'root@pam';
1877 my $migration_type = extract_param
($param, 'migration_type');
1878 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1879 if $migration_type && $authuser ne 'root@pam';
1881 my $migration_network = extract_param
($param, 'migration_network');
1882 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1883 if $migration_network && $authuser ne 'root@pam';
1885 my $targetstorage = extract_param
($param, 'targetstorage');
1886 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1887 if $targetstorage && $authuser ne 'root@pam';
1889 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1890 if $targetstorage && !$migratedfrom;
1892 # read spice ticket from STDIN
1894 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1895 if (defined(my $line = <STDIN
>)) {
1897 $spice_ticket = $line;
1901 PVE
::Cluster
::check_cfs_quorum
();
1903 my $storecfg = PVE
::Storage
::config
();
1905 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1906 $rpcenv->{type
} ne 'ha') {
1911 my $service = "vm:$vmid";
1913 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1915 print "Requesting HA start for VM $vmid\n";
1917 PVE
::Tools
::run_command
($cmd);
1922 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1929 syslog
('info', "start VM $vmid: $upid\n");
1931 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1932 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1937 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1941 __PACKAGE__-
>register_method({
1943 path
=> '{vmid}/status/stop',
1947 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1948 "is akin to pulling the power plug of a running computer and may damage the VM data",
1950 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1953 additionalProperties
=> 0,
1955 node
=> get_standard_option
('pve-node'),
1956 vmid
=> get_standard_option
('pve-vmid',
1957 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1958 skiplock
=> get_standard_option
('skiplock'),
1959 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1961 description
=> "Wait maximal timeout seconds.",
1967 description
=> "Do not deactivate storage volumes.",
1980 my $rpcenv = PVE
::RPCEnvironment
::get
();
1982 my $authuser = $rpcenv->get_user();
1984 my $node = extract_param
($param, 'node');
1986 my $vmid = extract_param
($param, 'vmid');
1988 my $skiplock = extract_param
($param, 'skiplock');
1989 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1990 if $skiplock && $authuser ne 'root@pam';
1992 my $keepActive = extract_param
($param, 'keepActive');
1993 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1994 if $keepActive && $authuser ne 'root@pam';
1996 my $migratedfrom = extract_param
($param, 'migratedfrom');
1997 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1998 if $migratedfrom && $authuser ne 'root@pam';
2001 my $storecfg = PVE
::Storage
::config
();
2003 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2008 my $service = "vm:$vmid";
2010 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2012 print "Requesting HA stop for VM $vmid\n";
2014 PVE
::Tools
::run_command
($cmd);
2019 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2025 syslog
('info', "stop VM $vmid: $upid\n");
2027 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2028 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2033 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2037 __PACKAGE__-
>register_method({
2039 path
=> '{vmid}/status/reset',
2043 description
=> "Reset virtual machine.",
2045 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2048 additionalProperties
=> 0,
2050 node
=> get_standard_option
('pve-node'),
2051 vmid
=> get_standard_option
('pve-vmid',
2052 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2053 skiplock
=> get_standard_option
('skiplock'),
2062 my $rpcenv = PVE
::RPCEnvironment
::get
();
2064 my $authuser = $rpcenv->get_user();
2066 my $node = extract_param
($param, 'node');
2068 my $vmid = extract_param
($param, 'vmid');
2070 my $skiplock = extract_param
($param, 'skiplock');
2071 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2072 if $skiplock && $authuser ne 'root@pam';
2074 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2079 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2084 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2087 __PACKAGE__-
>register_method({
2088 name
=> 'vm_shutdown',
2089 path
=> '{vmid}/status/shutdown',
2093 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2094 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2096 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2099 additionalProperties
=> 0,
2101 node
=> get_standard_option
('pve-node'),
2102 vmid
=> get_standard_option
('pve-vmid',
2103 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2104 skiplock
=> get_standard_option
('skiplock'),
2106 description
=> "Wait maximal timeout seconds.",
2112 description
=> "Make sure the VM stops.",
2118 description
=> "Do not deactivate storage volumes.",
2131 my $rpcenv = PVE
::RPCEnvironment
::get
();
2133 my $authuser = $rpcenv->get_user();
2135 my $node = extract_param
($param, 'node');
2137 my $vmid = extract_param
($param, 'vmid');
2139 my $skiplock = extract_param
($param, 'skiplock');
2140 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2141 if $skiplock && $authuser ne 'root@pam';
2143 my $keepActive = extract_param
($param, 'keepActive');
2144 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2145 if $keepActive && $authuser ne 'root@pam';
2147 my $storecfg = PVE
::Storage
::config
();
2151 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2152 # otherwise, we will infer a shutdown command, but run into the timeout,
2153 # then when the vm is resumed, it will instantly shutdown
2155 # checking the qmp status here to get feedback to the gui/cli/api
2156 # and the status query should not take too long
2159 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2163 if (!$err && $qmpstatus->{status
} eq "paused") {
2164 if ($param->{forceStop
}) {
2165 warn "VM is paused - stop instead of shutdown\n";
2168 die "VM is paused - cannot shutdown\n";
2172 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2173 ($rpcenv->{type
} ne 'ha')) {
2178 my $service = "vm:$vmid";
2180 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2182 print "Requesting HA stop for VM $vmid\n";
2184 PVE
::Tools
::run_command
($cmd);
2189 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2196 syslog
('info', "shutdown VM $vmid: $upid\n");
2198 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2199 $shutdown, $param->{forceStop
}, $keepActive);
2204 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2208 __PACKAGE__-
>register_method({
2209 name
=> 'vm_suspend',
2210 path
=> '{vmid}/status/suspend',
2214 description
=> "Suspend virtual machine.",
2216 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2219 additionalProperties
=> 0,
2221 node
=> get_standard_option
('pve-node'),
2222 vmid
=> get_standard_option
('pve-vmid',
2223 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2224 skiplock
=> get_standard_option
('skiplock'),
2233 my $rpcenv = PVE
::RPCEnvironment
::get
();
2235 my $authuser = $rpcenv->get_user();
2237 my $node = extract_param
($param, 'node');
2239 my $vmid = extract_param
($param, 'vmid');
2241 my $skiplock = extract_param
($param, 'skiplock');
2242 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2243 if $skiplock && $authuser ne 'root@pam';
2245 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2250 syslog
('info', "suspend VM $vmid: $upid\n");
2252 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2257 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2260 __PACKAGE__-
>register_method({
2261 name
=> 'vm_resume',
2262 path
=> '{vmid}/status/resume',
2266 description
=> "Resume virtual machine.",
2268 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2271 additionalProperties
=> 0,
2273 node
=> get_standard_option
('pve-node'),
2274 vmid
=> get_standard_option
('pve-vmid',
2275 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2276 skiplock
=> get_standard_option
('skiplock'),
2277 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2287 my $rpcenv = PVE
::RPCEnvironment
::get
();
2289 my $authuser = $rpcenv->get_user();
2291 my $node = extract_param
($param, 'node');
2293 my $vmid = extract_param
($param, 'vmid');
2295 my $skiplock = extract_param
($param, 'skiplock');
2296 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2297 if $skiplock && $authuser ne 'root@pam';
2299 my $nocheck = extract_param
($param, 'nocheck');
2301 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2306 syslog
('info', "resume VM $vmid: $upid\n");
2308 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2313 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2316 __PACKAGE__-
>register_method({
2317 name
=> 'vm_sendkey',
2318 path
=> '{vmid}/sendkey',
2322 description
=> "Send key event to virtual machine.",
2324 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2327 additionalProperties
=> 0,
2329 node
=> get_standard_option
('pve-node'),
2330 vmid
=> get_standard_option
('pve-vmid',
2331 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2332 skiplock
=> get_standard_option
('skiplock'),
2334 description
=> "The key (qemu monitor encoding).",
2339 returns
=> { type
=> 'null'},
2343 my $rpcenv = PVE
::RPCEnvironment
::get
();
2345 my $authuser = $rpcenv->get_user();
2347 my $node = extract_param
($param, 'node');
2349 my $vmid = extract_param
($param, 'vmid');
2351 my $skiplock = extract_param
($param, 'skiplock');
2352 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2353 if $skiplock && $authuser ne 'root@pam';
2355 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2360 __PACKAGE__-
>register_method({
2361 name
=> 'vm_feature',
2362 path
=> '{vmid}/feature',
2366 description
=> "Check if feature for virtual machine is available.",
2368 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2371 additionalProperties
=> 0,
2373 node
=> get_standard_option
('pve-node'),
2374 vmid
=> get_standard_option
('pve-vmid'),
2376 description
=> "Feature to check.",
2378 enum
=> [ 'snapshot', 'clone', 'copy' ],
2380 snapname
=> get_standard_option
('pve-snapshot-name', {
2388 hasFeature
=> { type
=> 'boolean' },
2391 items
=> { type
=> 'string' },
2398 my $node = extract_param
($param, 'node');
2400 my $vmid = extract_param
($param, 'vmid');
2402 my $snapname = extract_param
($param, 'snapname');
2404 my $feature = extract_param
($param, 'feature');
2406 my $running = PVE
::QemuServer
::check_running
($vmid);
2408 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2411 my $snap = $conf->{snapshots
}->{$snapname};
2412 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2415 my $storecfg = PVE
::Storage
::config
();
2417 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2418 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2421 hasFeature
=> $hasFeature,
2422 nodes
=> [ keys %$nodelist ],
2426 __PACKAGE__-
>register_method({
2428 path
=> '{vmid}/clone',
2432 description
=> "Create a copy of virtual machine/template.",
2434 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2435 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2436 "'Datastore.AllocateSpace' on any used storage.",
2439 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2441 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2442 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2447 additionalProperties
=> 0,
2449 node
=> get_standard_option
('pve-node'),
2450 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2451 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2454 type
=> 'string', format
=> 'dns-name',
2455 description
=> "Set a name for the new VM.",
2460 description
=> "Description for the new VM.",
2464 type
=> 'string', format
=> 'pve-poolid',
2465 description
=> "Add the new VM to the specified pool.",
2467 snapname
=> get_standard_option
('pve-snapshot-name', {
2470 storage
=> get_standard_option
('pve-storage-id', {
2471 description
=> "Target storage for full clone.",
2476 description
=> "Target format for file storage.",
2480 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2485 description
=> "Create a full copy of all disk. This is always done when " .
2486 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2489 target
=> get_standard_option
('pve-node', {
2490 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2501 my $rpcenv = PVE
::RPCEnvironment
::get
();
2503 my $authuser = $rpcenv->get_user();
2505 my $node = extract_param
($param, 'node');
2507 my $vmid = extract_param
($param, 'vmid');
2509 my $newid = extract_param
($param, 'newid');
2511 my $pool = extract_param
($param, 'pool');
2513 if (defined($pool)) {
2514 $rpcenv->check_pool_exist($pool);
2517 my $snapname = extract_param
($param, 'snapname');
2519 my $storage = extract_param
($param, 'storage');
2521 my $format = extract_param
($param, 'format');
2523 my $target = extract_param
($param, 'target');
2525 my $localnode = PVE
::INotify
::nodename
();
2527 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2529 PVE
::Cluster
::check_node_exists
($target) if $target;
2531 my $storecfg = PVE
::Storage
::config
();
2534 # check if storage is enabled on local node
2535 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2537 # check if storage is available on target node
2538 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2539 # clone only works if target storage is shared
2540 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2541 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2545 PVE
::Cluster
::check_cfs_quorum
();
2547 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2549 # exclusive lock if VM is running - else shared lock is enough;
2550 my $shared_lock = $running ?
0 : 1;
2554 # do all tests after lock
2555 # we also try to do all tests before we fork the worker
2557 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2559 PVE
::QemuConfig-
>check_lock($conf);
2561 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2563 die "unexpected state change\n" if $verify_running != $running;
2565 die "snapshot '$snapname' does not exist\n"
2566 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2568 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2570 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2572 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2574 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2576 die "unable to create VM $newid: config file already exists\n"
2579 my $newconf = { lock => 'clone' };
2584 foreach my $opt (keys %$oldconf) {
2585 my $value = $oldconf->{$opt};
2587 # do not copy snapshot related info
2588 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2589 $opt eq 'vmstate' || $opt eq 'snapstate';
2591 # no need to copy unused images, because VMID(owner) changes anyways
2592 next if $opt =~ m/^unused\d+$/;
2594 # always change MAC! address
2595 if ($opt =~ m/^net(\d+)$/) {
2596 my $net = PVE
::QemuServer
::parse_net
($value);
2597 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2598 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2599 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2600 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2601 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2602 die "unable to parse drive options for '$opt'\n" if !$drive;
2603 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2604 $newconf->{$opt} = $value; # simply copy configuration
2606 if ($param->{full
}) {
2607 die "Full clone feature is not supported for drive '$opt'\n"
2608 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2609 $fullclone->{$opt} = 1;
2611 # not full means clone instead of copy
2612 die "Linked clone feature is not supported for drive '$opt'\n"
2613 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2615 $drives->{$opt} = $drive;
2616 push @$vollist, $drive->{file
};
2619 # copy everything else
2620 $newconf->{$opt} = $value;
2624 # auto generate a new uuid
2625 my ($uuid, $uuid_str);
2626 UUID
::generate
($uuid);
2627 UUID
::unparse
($uuid, $uuid_str);
2628 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2629 $smbios1->{uuid
} = $uuid_str;
2630 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2632 delete $newconf->{template
};
2634 if ($param->{name
}) {
2635 $newconf->{name
} = $param->{name
};
2637 if ($oldconf->{name
}) {
2638 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2640 $newconf->{name
} = "Copy-of-VM-$vmid";
2644 if ($param->{description
}) {
2645 $newconf->{description
} = $param->{description
};
2648 # create empty/temp config - this fails if VM already exists on other node
2649 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2654 my $newvollist = [];
2661 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2663 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2665 my $total_jobs = scalar(keys %{$drives});
2668 foreach my $opt (keys %$drives) {
2669 my $drive = $drives->{$opt};
2670 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2672 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2673 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2674 $jobs, $skipcomplete, $oldconf->{agent
});
2676 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2678 PVE
::QemuConfig-
>write_config($newid, $newconf);
2682 delete $newconf->{lock};
2683 PVE
::QemuConfig-
>write_config($newid, $newconf);
2686 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2687 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2688 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2690 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2691 die "Failed to move config to node '$target' - rename failed: $!\n"
2692 if !rename($conffile, $newconffile);
2695 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2700 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2702 sleep 1; # some storage like rbd need to wait before release volume - really?
2704 foreach my $volid (@$newvollist) {
2705 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2708 die "clone failed: $err";
2714 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2716 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2719 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2720 # Aquire exclusive lock lock for $newid
2721 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2726 __PACKAGE__-
>register_method({
2727 name
=> 'move_vm_disk',
2728 path
=> '{vmid}/move_disk',
2732 description
=> "Move volume to different storage.",
2734 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2736 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2737 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2741 additionalProperties
=> 0,
2743 node
=> get_standard_option
('pve-node'),
2744 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2747 description
=> "The disk you want to move.",
2748 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2750 storage
=> get_standard_option
('pve-storage-id', {
2751 description
=> "Target storage.",
2752 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2756 description
=> "Target Format.",
2757 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2762 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2768 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2776 description
=> "the task ID.",
2781 my $rpcenv = PVE
::RPCEnvironment
::get
();
2783 my $authuser = $rpcenv->get_user();
2785 my $node = extract_param
($param, 'node');
2787 my $vmid = extract_param
($param, 'vmid');
2789 my $digest = extract_param
($param, 'digest');
2791 my $disk = extract_param
($param, 'disk');
2793 my $storeid = extract_param
($param, 'storage');
2795 my $format = extract_param
($param, 'format');
2797 my $storecfg = PVE
::Storage
::config
();
2799 my $updatefn = sub {
2801 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2803 PVE
::QemuConfig-
>check_lock($conf);
2805 die "checksum missmatch (file change by other user?)\n"
2806 if $digest && $digest ne $conf->{digest
};
2808 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2810 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2812 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2814 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2817 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2818 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2822 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2823 (!$format || !$oldfmt || $oldfmt eq $format);
2825 # this only checks snapshots because $disk is passed!
2826 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2827 die "you can't move a disk with snapshots and delete the source\n"
2828 if $snapshotted && $param->{delete};
2830 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2832 my $running = PVE
::QemuServer
::check_running
($vmid);
2834 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2838 my $newvollist = [];
2844 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2846 warn "moving disk with snapshots, snapshots will not be moved!\n"
2849 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2850 $vmid, $storeid, $format, 1, $newvollist);
2852 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2854 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2856 # convert moved disk to base if part of template
2857 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2858 if PVE
::QemuConfig-
>is_template($conf);
2860 PVE
::QemuConfig-
>write_config($vmid, $conf);
2863 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2864 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2871 foreach my $volid (@$newvollist) {
2872 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2875 die "storage migration failed: $err";
2878 if ($param->{delete}) {
2880 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2881 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2887 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2890 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2893 __PACKAGE__-
>register_method({
2894 name
=> 'migrate_vm',
2895 path
=> '{vmid}/migrate',
2899 description
=> "Migrate virtual machine. Creates a new migration task.",
2901 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2904 additionalProperties
=> 0,
2906 node
=> get_standard_option
('pve-node'),
2907 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2908 target
=> get_standard_option
('pve-node', {
2909 description
=> "Target node.",
2910 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2914 description
=> "Use online/live migration.",
2919 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2924 enum
=> ['secure', 'insecure'],
2925 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2928 migration_network
=> {
2929 type
=> 'string', format
=> 'CIDR',
2930 description
=> "CIDR of the (sub) network that is used for migration.",
2933 "with-local-disks" => {
2935 description
=> "Enable live storage migration for local disk",
2938 targetstorage
=> get_standard_option
('pve-storage-id', {
2939 description
=> "Default target storage.",
2941 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2947 description
=> "the task ID.",
2952 my $rpcenv = PVE
::RPCEnvironment
::get
();
2954 my $authuser = $rpcenv->get_user();
2956 my $target = extract_param
($param, 'target');
2958 my $localnode = PVE
::INotify
::nodename
();
2959 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2961 PVE
::Cluster
::check_cfs_quorum
();
2963 PVE
::Cluster
::check_node_exists
($target);
2965 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2967 my $vmid = extract_param
($param, 'vmid');
2969 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2970 if !$param->{online
} && $param->{targetstorage
};
2972 raise_param_exc
({ force
=> "Only root may use this option." })
2973 if $param->{force
} && $authuser ne 'root@pam';
2975 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2976 if $param->{migration_type
} && $authuser ne 'root@pam';
2978 # allow root only until better network permissions are available
2979 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2980 if $param->{migration_network
} && $authuser ne 'root@pam';
2983 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2985 # try to detect errors early
2987 PVE
::QemuConfig-
>check_lock($conf);
2989 if (PVE
::QemuServer
::check_running
($vmid)) {
2990 die "cant migrate running VM without --online\n"
2991 if !$param->{online
};
2994 my $storecfg = PVE
::Storage
::config
();
2996 if( $param->{targetstorage
}) {
2997 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2999 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3002 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3007 my $service = "vm:$vmid";
3009 my $cmd = ['ha-manager', 'migrate', $service, $target];
3011 print "Requesting HA migration for VM $vmid to node $target\n";
3013 PVE
::Tools
::run_command
($cmd);
3018 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3023 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3027 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3030 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3035 __PACKAGE__-
>register_method({
3037 path
=> '{vmid}/monitor',
3041 description
=> "Execute Qemu monitor commands.",
3043 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3044 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3047 additionalProperties
=> 0,
3049 node
=> get_standard_option
('pve-node'),
3050 vmid
=> get_standard_option
('pve-vmid'),
3053 description
=> "The monitor command.",
3057 returns
=> { type
=> 'string'},
3061 my $rpcenv = PVE
::RPCEnvironment
::get
();
3062 my $authuser = $rpcenv->get_user();
3065 my $command = shift;
3066 return $command =~ m/^\s*info(\s+|$)/
3067 || $command =~ m/^\s*help\s*$/;
3070 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3071 if !&$is_ro($param->{command
});
3073 my $vmid = $param->{vmid
};
3075 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3079 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3081 $res = "ERROR: $@" if $@;
3086 __PACKAGE__-
>register_method({
3087 name
=> 'resize_vm',
3088 path
=> '{vmid}/resize',
3092 description
=> "Extend volume size.",
3094 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3097 additionalProperties
=> 0,
3099 node
=> get_standard_option
('pve-node'),
3100 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3101 skiplock
=> get_standard_option
('skiplock'),
3104 description
=> "The disk you want to resize.",
3105 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3109 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3110 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.",
3114 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3120 returns
=> { type
=> 'null'},
3124 my $rpcenv = PVE
::RPCEnvironment
::get
();
3126 my $authuser = $rpcenv->get_user();
3128 my $node = extract_param
($param, 'node');
3130 my $vmid = extract_param
($param, 'vmid');
3132 my $digest = extract_param
($param, 'digest');
3134 my $disk = extract_param
($param, 'disk');
3136 my $sizestr = extract_param
($param, 'size');
3138 my $skiplock = extract_param
($param, 'skiplock');
3139 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3140 if $skiplock && $authuser ne 'root@pam';
3142 my $storecfg = PVE
::Storage
::config
();
3144 my $updatefn = sub {
3146 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3148 die "checksum missmatch (file change by other user?)\n"
3149 if $digest && $digest ne $conf->{digest
};
3150 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3152 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3154 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3156 my (undef, undef, undef, undef, undef, undef, $format) =
3157 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3159 die "can't resize volume: $disk if snapshot exists\n"
3160 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3162 my $volid = $drive->{file
};
3164 die "disk '$disk' has no associated volume\n" if !$volid;
3166 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3168 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3170 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3172 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3173 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3175 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3176 my ($ext, $newsize, $unit) = ($1, $2, $4);
3179 $newsize = $newsize * 1024;
3180 } elsif ($unit eq 'M') {
3181 $newsize = $newsize * 1024 * 1024;
3182 } elsif ($unit eq 'G') {
3183 $newsize = $newsize * 1024 * 1024 * 1024;
3184 } elsif ($unit eq 'T') {
3185 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3188 $newsize += $size if $ext;
3189 $newsize = int($newsize);
3191 die "shrinking disks is not supported\n" if $newsize < $size;
3193 return if $size == $newsize;
3195 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3197 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3199 $drive->{size
} = $newsize;
3200 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3202 PVE
::QemuConfig-
>write_config($vmid, $conf);
3205 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3209 __PACKAGE__-
>register_method({
3210 name
=> 'snapshot_list',
3211 path
=> '{vmid}/snapshot',
3213 description
=> "List all snapshots.",
3215 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3218 protected
=> 1, # qemu pid files are only readable by root
3220 additionalProperties
=> 0,
3222 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3223 node
=> get_standard_option
('pve-node'),
3232 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3237 my $vmid = $param->{vmid
};
3239 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3240 my $snaphash = $conf->{snapshots
} || {};
3244 foreach my $name (keys %$snaphash) {
3245 my $d = $snaphash->{$name};
3248 snaptime
=> $d->{snaptime
} || 0,
3249 vmstate
=> $d->{vmstate
} ?
1 : 0,
3250 description
=> $d->{description
} || '',
3252 $item->{parent
} = $d->{parent
} if $d->{parent
};
3253 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3257 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3258 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3259 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3261 push @$res, $current;
3266 __PACKAGE__-
>register_method({
3268 path
=> '{vmid}/snapshot',
3272 description
=> "Snapshot a VM.",
3274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3277 additionalProperties
=> 0,
3279 node
=> get_standard_option
('pve-node'),
3280 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3281 snapname
=> get_standard_option
('pve-snapshot-name'),
3285 description
=> "Save the vmstate",
3290 description
=> "A textual description or comment.",
3296 description
=> "the task ID.",
3301 my $rpcenv = PVE
::RPCEnvironment
::get
();
3303 my $authuser = $rpcenv->get_user();
3305 my $node = extract_param
($param, 'node');
3307 my $vmid = extract_param
($param, 'vmid');
3309 my $snapname = extract_param
($param, 'snapname');
3311 die "unable to use snapshot name 'current' (reserved name)\n"
3312 if $snapname eq 'current';
3315 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3316 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3317 $param->{description
});
3320 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3323 __PACKAGE__-
>register_method({
3324 name
=> 'snapshot_cmd_idx',
3325 path
=> '{vmid}/snapshot/{snapname}',
3332 additionalProperties
=> 0,
3334 vmid
=> get_standard_option
('pve-vmid'),
3335 node
=> get_standard_option
('pve-node'),
3336 snapname
=> get_standard_option
('pve-snapshot-name'),
3345 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3352 push @$res, { cmd
=> 'rollback' };
3353 push @$res, { cmd
=> 'config' };
3358 __PACKAGE__-
>register_method({
3359 name
=> 'update_snapshot_config',
3360 path
=> '{vmid}/snapshot/{snapname}/config',
3364 description
=> "Update snapshot metadata.",
3366 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3369 additionalProperties
=> 0,
3371 node
=> get_standard_option
('pve-node'),
3372 vmid
=> get_standard_option
('pve-vmid'),
3373 snapname
=> get_standard_option
('pve-snapshot-name'),
3377 description
=> "A textual description or comment.",
3381 returns
=> { type
=> 'null' },
3385 my $rpcenv = PVE
::RPCEnvironment
::get
();
3387 my $authuser = $rpcenv->get_user();
3389 my $vmid = extract_param
($param, 'vmid');
3391 my $snapname = extract_param
($param, 'snapname');
3393 return undef if !defined($param->{description
});
3395 my $updatefn = sub {
3397 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3399 PVE
::QemuConfig-
>check_lock($conf);
3401 my $snap = $conf->{snapshots
}->{$snapname};
3403 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3405 $snap->{description
} = $param->{description
} if defined($param->{description
});
3407 PVE
::QemuConfig-
>write_config($vmid, $conf);
3410 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3415 __PACKAGE__-
>register_method({
3416 name
=> 'get_snapshot_config',
3417 path
=> '{vmid}/snapshot/{snapname}/config',
3420 description
=> "Get snapshot configuration",
3422 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3425 additionalProperties
=> 0,
3427 node
=> get_standard_option
('pve-node'),
3428 vmid
=> get_standard_option
('pve-vmid'),
3429 snapname
=> get_standard_option
('pve-snapshot-name'),
3432 returns
=> { type
=> "object" },
3436 my $rpcenv = PVE
::RPCEnvironment
::get
();
3438 my $authuser = $rpcenv->get_user();
3440 my $vmid = extract_param
($param, 'vmid');
3442 my $snapname = extract_param
($param, 'snapname');
3444 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3446 my $snap = $conf->{snapshots
}->{$snapname};
3448 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3453 __PACKAGE__-
>register_method({
3455 path
=> '{vmid}/snapshot/{snapname}/rollback',
3459 description
=> "Rollback VM state to specified snapshot.",
3461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3464 additionalProperties
=> 0,
3466 node
=> get_standard_option
('pve-node'),
3467 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3468 snapname
=> get_standard_option
('pve-snapshot-name'),
3473 description
=> "the task ID.",
3478 my $rpcenv = PVE
::RPCEnvironment
::get
();
3480 my $authuser = $rpcenv->get_user();
3482 my $node = extract_param
($param, 'node');
3484 my $vmid = extract_param
($param, 'vmid');
3486 my $snapname = extract_param
($param, 'snapname');
3489 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3490 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3494 # hold migration lock, this makes sure that nobody create replication snapshots
3495 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3498 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3501 __PACKAGE__-
>register_method({
3502 name
=> 'delsnapshot',
3503 path
=> '{vmid}/snapshot/{snapname}',
3507 description
=> "Delete a VM snapshot.",
3509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3512 additionalProperties
=> 0,
3514 node
=> get_standard_option
('pve-node'),
3515 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3516 snapname
=> get_standard_option
('pve-snapshot-name'),
3520 description
=> "For removal from config file, even if removing disk snapshots fails.",
3526 description
=> "the task ID.",
3531 my $rpcenv = PVE
::RPCEnvironment
::get
();
3533 my $authuser = $rpcenv->get_user();
3535 my $node = extract_param
($param, 'node');
3537 my $vmid = extract_param
($param, 'vmid');
3539 my $snapname = extract_param
($param, 'snapname');
3542 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3543 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3546 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3549 __PACKAGE__-
>register_method({
3551 path
=> '{vmid}/template',
3555 description
=> "Create a Template.",
3557 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3558 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3561 additionalProperties
=> 0,
3563 node
=> get_standard_option
('pve-node'),
3564 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3568 description
=> "If you want to convert only 1 disk to base image.",
3569 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3574 returns
=> { type
=> 'null'},
3578 my $rpcenv = PVE
::RPCEnvironment
::get
();
3580 my $authuser = $rpcenv->get_user();
3582 my $node = extract_param
($param, 'node');
3584 my $vmid = extract_param
($param, 'vmid');
3586 my $disk = extract_param
($param, 'disk');
3588 my $updatefn = sub {
3590 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3592 PVE
::QemuConfig-
>check_lock($conf);
3594 die "unable to create template, because VM contains snapshots\n"
3595 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3597 die "you can't convert a template to a template\n"
3598 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3600 die "you can't convert a VM to template if VM is running\n"
3601 if PVE
::QemuServer
::check_running
($vmid);
3604 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3607 $conf->{template
} = 1;
3608 PVE
::QemuConfig-
>write_config($vmid, $conf);
3610 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3613 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);