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 # FIXME: cloudinit: CDROM or Disk?
1102 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1103 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1105 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1107 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1108 if defined($conf->{pending
}->{$opt});
1110 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1112 $conf->{pending
}->{$opt} = $param->{$opt};
1114 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1115 PVE
::QemuConfig-
>write_config($vmid, $conf);
1118 # remove pending changes when nothing changed
1119 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1120 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1121 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1123 return if !scalar(keys %{$conf->{pending
}});
1125 my $running = PVE
::QemuServer
::check_running
($vmid);
1127 # apply pending changes
1129 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1133 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1134 raise_param_exc
($errors) if scalar(keys %$errors);
1136 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1146 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1148 if ($background_delay) {
1150 # Note: It would be better to do that in the Event based HTTPServer
1151 # to avoid blocking call to sleep.
1153 my $end_time = time() + $background_delay;
1155 my $task = PVE
::Tools
::upid_decode
($upid);
1158 while (time() < $end_time) {
1159 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1161 sleep(1); # this gets interrupted when child process ends
1165 my $status = PVE
::Tools
::upid_read_status
($upid);
1166 return undef if $status eq 'OK';
1175 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1178 my $vm_config_perm_list = [
1183 'VM.Config.Network',
1185 'VM.Config.Options',
1188 __PACKAGE__-
>register_method({
1189 name
=> 'update_vm_async',
1190 path
=> '{vmid}/config',
1194 description
=> "Set virtual machine options (asynchrounous API).",
1196 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1199 additionalProperties
=> 0,
1200 properties
=> PVE
::QemuServer
::json_config_properties
(
1202 node
=> get_standard_option
('pve-node'),
1203 vmid
=> get_standard_option
('pve-vmid'),
1204 skiplock
=> get_standard_option
('skiplock'),
1206 type
=> 'string', format
=> 'pve-configid-list',
1207 description
=> "A list of settings you want to delete.",
1211 type
=> 'string', format
=> 'pve-configid-list',
1212 description
=> "Revert a pending change.",
1217 description
=> $opt_force_description,
1219 requires
=> 'delete',
1223 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1227 background_delay
=> {
1229 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1240 code
=> $update_vm_api,
1243 __PACKAGE__-
>register_method({
1244 name
=> 'update_vm',
1245 path
=> '{vmid}/config',
1249 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1251 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1254 additionalProperties
=> 0,
1255 properties
=> PVE
::QemuServer
::json_config_properties
(
1257 node
=> get_standard_option
('pve-node'),
1258 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1259 skiplock
=> get_standard_option
('skiplock'),
1261 type
=> 'string', format
=> 'pve-configid-list',
1262 description
=> "A list of settings you want to delete.",
1266 type
=> 'string', format
=> 'pve-configid-list',
1267 description
=> "Revert a pending change.",
1272 description
=> $opt_force_description,
1274 requires
=> 'delete',
1278 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1284 returns
=> { type
=> 'null' },
1287 &$update_vm_api($param, 1);
1293 __PACKAGE__-
>register_method({
1294 name
=> 'destroy_vm',
1299 description
=> "Destroy the vm (also delete all used/owned volumes).",
1301 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1304 additionalProperties
=> 0,
1306 node
=> get_standard_option
('pve-node'),
1307 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1308 skiplock
=> get_standard_option
('skiplock'),
1317 my $rpcenv = PVE
::RPCEnvironment
::get
();
1319 my $authuser = $rpcenv->get_user();
1321 my $vmid = $param->{vmid
};
1323 my $skiplock = $param->{skiplock
};
1324 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1325 if $skiplock && $authuser ne 'root@pam';
1328 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1330 my $storecfg = PVE
::Storage
::config
();
1332 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1334 die "unable to remove VM $vmid - used in HA resources\n"
1335 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1337 # do not allow destroy if there are replication jobs
1338 my $repl_conf = PVE
::ReplicationConfig-
>new();
1339 $repl_conf->check_for_existing_jobs($vmid);
1341 # early tests (repeat after locking)
1342 die "VM $vmid is running - destroy failed\n"
1343 if PVE
::QemuServer
::check_running
($vmid);
1348 syslog
('info', "destroy VM $vmid: $upid\n");
1350 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1352 PVE
::AccessControl
::remove_vm_access
($vmid);
1354 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1357 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1360 __PACKAGE__-
>register_method({
1362 path
=> '{vmid}/unlink',
1366 description
=> "Unlink/delete disk images.",
1368 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1371 additionalProperties
=> 0,
1373 node
=> get_standard_option
('pve-node'),
1374 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1376 type
=> 'string', format
=> 'pve-configid-list',
1377 description
=> "A list of disk IDs you want to delete.",
1381 description
=> $opt_force_description,
1386 returns
=> { type
=> 'null'},
1390 $param->{delete} = extract_param
($param, 'idlist');
1392 __PACKAGE__-
>update_vm($param);
1399 __PACKAGE__-
>register_method({
1401 path
=> '{vmid}/vncproxy',
1405 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1407 description
=> "Creates a TCP VNC proxy connections.",
1409 additionalProperties
=> 0,
1411 node
=> get_standard_option
('pve-node'),
1412 vmid
=> get_standard_option
('pve-vmid'),
1416 description
=> "starts websockify instead of vncproxy",
1421 additionalProperties
=> 0,
1423 user
=> { type
=> 'string' },
1424 ticket
=> { type
=> 'string' },
1425 cert
=> { type
=> 'string' },
1426 port
=> { type
=> 'integer' },
1427 upid
=> { type
=> 'string' },
1433 my $rpcenv = PVE
::RPCEnvironment
::get
();
1435 my $authuser = $rpcenv->get_user();
1437 my $vmid = $param->{vmid
};
1438 my $node = $param->{node
};
1439 my $websocket = $param->{websocket
};
1441 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1443 my $authpath = "/vms/$vmid";
1445 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1447 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1450 my ($remip, $family);
1453 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1454 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1455 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1456 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1458 $family = PVE
::Tools
::get_host_address_family
($node);
1461 my $port = PVE
::Tools
::next_vnc_port
($family);
1468 syslog
('info', "starting vnc proxy $upid\n");
1472 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1475 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1477 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1478 '-timeout', $timeout, '-authpath', $authpath,
1479 '-perm', 'Sys.Console'];
1481 if ($param->{websocket
}) {
1482 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1483 push @$cmd, '-notls', '-listen', 'localhost';
1486 push @$cmd, '-c', @$remcmd, @$termcmd;
1488 PVE
::Tools
::run_command
($cmd);
1492 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1494 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1496 my $sock = IO
::Socket
::IP-
>new(
1501 GetAddrInfoFlags
=> 0,
1502 ) or die "failed to create socket: $!\n";
1503 # Inside the worker we shouldn't have any previous alarms
1504 # running anyway...:
1506 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1508 accept(my $cli, $sock) or die "connection failed: $!\n";
1511 if (PVE
::Tools
::run_command
($cmd,
1512 output
=> '>&'.fileno($cli),
1513 input
=> '<&'.fileno($cli),
1516 die "Failed to run vncproxy.\n";
1523 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1525 PVE
::Tools
::wait_for_vnc_port
($port);
1536 __PACKAGE__-
>register_method({
1537 name
=> 'termproxy',
1538 path
=> '{vmid}/termproxy',
1542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1544 description
=> "Creates a TCP proxy connections.",
1546 additionalProperties
=> 0,
1548 node
=> get_standard_option
('pve-node'),
1549 vmid
=> get_standard_option
('pve-vmid'),
1553 enum
=> [qw(serial0 serial1 serial2 serial3)],
1554 description
=> "opens a serial terminal (defaults to display)",
1559 additionalProperties
=> 0,
1561 user
=> { type
=> 'string' },
1562 ticket
=> { type
=> 'string' },
1563 port
=> { type
=> 'integer' },
1564 upid
=> { type
=> 'string' },
1570 my $rpcenv = PVE
::RPCEnvironment
::get
();
1572 my $authuser = $rpcenv->get_user();
1574 my $vmid = $param->{vmid
};
1575 my $node = $param->{node
};
1576 my $serial = $param->{serial
};
1578 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1580 if (!defined($serial)) {
1581 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1582 $serial = $conf->{vga
};
1586 my $authpath = "/vms/$vmid";
1588 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1590 my ($remip, $family);
1592 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1593 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1595 $family = PVE
::Tools
::get_host_address_family
($node);
1598 my $port = PVE
::Tools
::next_vnc_port
($family);
1600 my $remcmd = $remip ?
1601 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1603 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1604 push @$termcmd, '-iface', $serial if $serial;
1609 syslog
('info', "starting qemu termproxy $upid\n");
1611 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1612 '--perm', 'VM.Console', '--'];
1613 push @$cmd, @$remcmd, @$termcmd;
1615 PVE
::Tools
::run_command
($cmd);
1618 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1620 PVE
::Tools
::wait_for_vnc_port
($port);
1630 __PACKAGE__-
>register_method({
1631 name
=> 'vncwebsocket',
1632 path
=> '{vmid}/vncwebsocket',
1635 description
=> "You also need to pass a valid ticket (vncticket).",
1636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1638 description
=> "Opens a weksocket for VNC traffic.",
1640 additionalProperties
=> 0,
1642 node
=> get_standard_option
('pve-node'),
1643 vmid
=> get_standard_option
('pve-vmid'),
1645 description
=> "Ticket from previous call to vncproxy.",
1650 description
=> "Port number returned by previous vncproxy call.",
1660 port
=> { type
=> 'string' },
1666 my $rpcenv = PVE
::RPCEnvironment
::get
();
1668 my $authuser = $rpcenv->get_user();
1670 my $vmid = $param->{vmid
};
1671 my $node = $param->{node
};
1673 my $authpath = "/vms/$vmid";
1675 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1677 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1679 # Note: VNC ports are acessible from outside, so we do not gain any
1680 # security if we verify that $param->{port} belongs to VM $vmid. This
1681 # check is done by verifying the VNC ticket (inside VNC protocol).
1683 my $port = $param->{port
};
1685 return { port
=> $port };
1688 __PACKAGE__-
>register_method({
1689 name
=> 'spiceproxy',
1690 path
=> '{vmid}/spiceproxy',
1695 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1697 description
=> "Returns a SPICE configuration to connect to the VM.",
1699 additionalProperties
=> 0,
1701 node
=> get_standard_option
('pve-node'),
1702 vmid
=> get_standard_option
('pve-vmid'),
1703 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1706 returns
=> get_standard_option
('remote-viewer-config'),
1710 my $rpcenv = PVE
::RPCEnvironment
::get
();
1712 my $authuser = $rpcenv->get_user();
1714 my $vmid = $param->{vmid
};
1715 my $node = $param->{node
};
1716 my $proxy = $param->{proxy
};
1718 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1719 my $title = "VM $vmid";
1720 $title .= " - ". $conf->{name
} if $conf->{name
};
1722 my $port = PVE
::QemuServer
::spice_port
($vmid);
1724 my ($ticket, undef, $remote_viewer_config) =
1725 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1727 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1728 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1730 return $remote_viewer_config;
1733 __PACKAGE__-
>register_method({
1735 path
=> '{vmid}/status',
1738 description
=> "Directory index",
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1754 subdir
=> { type
=> 'string' },
1757 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1763 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1766 { subdir
=> 'current' },
1767 { subdir
=> 'start' },
1768 { subdir
=> 'stop' },
1774 __PACKAGE__-
>register_method({
1775 name
=> 'vm_status',
1776 path
=> '{vmid}/status/current',
1779 protected
=> 1, # qemu pid files are only readable by root
1780 description
=> "Get virtual machine status.",
1782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1785 additionalProperties
=> 0,
1787 node
=> get_standard_option
('pve-node'),
1788 vmid
=> get_standard_option
('pve-vmid'),
1791 returns
=> { type
=> 'object' },
1796 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1798 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1799 my $status = $vmstatus->{$param->{vmid
}};
1801 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1803 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1805 $status->{agent
} = 1 if $conf->{agent
};
1810 __PACKAGE__-
>register_method({
1812 path
=> '{vmid}/status/start',
1816 description
=> "Start virtual machine.",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid',
1825 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1826 skiplock
=> get_standard_option
('skiplock'),
1827 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1828 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1831 enum
=> ['secure', 'insecure'],
1832 description
=> "Migration traffic is encrypted using an SSH " .
1833 "tunnel by default. On secure, completely private networks " .
1834 "this can be disabled to increase performance.",
1837 migration_network
=> {
1838 type
=> 'string', format
=> 'CIDR',
1839 description
=> "CIDR of the (sub) network that is used for migration.",
1842 machine
=> get_standard_option
('pve-qm-machine'),
1844 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1856 my $rpcenv = PVE
::RPCEnvironment
::get
();
1858 my $authuser = $rpcenv->get_user();
1860 my $node = extract_param
($param, 'node');
1862 my $vmid = extract_param
($param, 'vmid');
1864 my $machine = extract_param
($param, 'machine');
1866 my $stateuri = extract_param
($param, 'stateuri');
1867 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1868 if $stateuri && $authuser ne 'root@pam';
1870 my $skiplock = extract_param
($param, 'skiplock');
1871 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1872 if $skiplock && $authuser ne 'root@pam';
1874 my $migratedfrom = extract_param
($param, 'migratedfrom');
1875 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1876 if $migratedfrom && $authuser ne 'root@pam';
1878 my $migration_type = extract_param
($param, 'migration_type');
1879 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1880 if $migration_type && $authuser ne 'root@pam';
1882 my $migration_network = extract_param
($param, 'migration_network');
1883 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1884 if $migration_network && $authuser ne 'root@pam';
1886 my $targetstorage = extract_param
($param, 'targetstorage');
1887 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1888 if $targetstorage && $authuser ne 'root@pam';
1890 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1891 if $targetstorage && !$migratedfrom;
1893 # read spice ticket from STDIN
1895 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1896 if (defined(my $line = <STDIN
>)) {
1898 $spice_ticket = $line;
1902 PVE
::Cluster
::check_cfs_quorum
();
1904 my $storecfg = PVE
::Storage
::config
();
1906 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1907 $rpcenv->{type
} ne 'ha') {
1912 my $service = "vm:$vmid";
1914 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1916 print "Requesting HA start for VM $vmid\n";
1918 PVE
::Tools
::run_command
($cmd);
1923 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1930 syslog
('info', "start VM $vmid: $upid\n");
1932 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1933 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1938 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1942 __PACKAGE__-
>register_method({
1944 path
=> '{vmid}/status/stop',
1948 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1949 "is akin to pulling the power plug of a running computer and may damage the VM data",
1951 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1954 additionalProperties
=> 0,
1956 node
=> get_standard_option
('pve-node'),
1957 vmid
=> get_standard_option
('pve-vmid',
1958 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1959 skiplock
=> get_standard_option
('skiplock'),
1960 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1962 description
=> "Wait maximal timeout seconds.",
1968 description
=> "Do not deactivate storage volumes.",
1981 my $rpcenv = PVE
::RPCEnvironment
::get
();
1983 my $authuser = $rpcenv->get_user();
1985 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $skiplock = extract_param
($param, 'skiplock');
1990 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1991 if $skiplock && $authuser ne 'root@pam';
1993 my $keepActive = extract_param
($param, 'keepActive');
1994 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1995 if $keepActive && $authuser ne 'root@pam';
1997 my $migratedfrom = extract_param
($param, 'migratedfrom');
1998 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1999 if $migratedfrom && $authuser ne 'root@pam';
2002 my $storecfg = PVE
::Storage
::config
();
2004 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2009 my $service = "vm:$vmid";
2011 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2013 print "Requesting HA stop for VM $vmid\n";
2015 PVE
::Tools
::run_command
($cmd);
2020 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2026 syslog
('info', "stop VM $vmid: $upid\n");
2028 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2029 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2034 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2038 __PACKAGE__-
>register_method({
2040 path
=> '{vmid}/status/reset',
2044 description
=> "Reset virtual machine.",
2046 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2049 additionalProperties
=> 0,
2051 node
=> get_standard_option
('pve-node'),
2052 vmid
=> get_standard_option
('pve-vmid',
2053 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2054 skiplock
=> get_standard_option
('skiplock'),
2063 my $rpcenv = PVE
::RPCEnvironment
::get
();
2065 my $authuser = $rpcenv->get_user();
2067 my $node = extract_param
($param, 'node');
2069 my $vmid = extract_param
($param, 'vmid');
2071 my $skiplock = extract_param
($param, 'skiplock');
2072 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2073 if $skiplock && $authuser ne 'root@pam';
2075 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2080 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2085 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2088 __PACKAGE__-
>register_method({
2089 name
=> 'vm_shutdown',
2090 path
=> '{vmid}/status/shutdown',
2094 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2095 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2097 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2100 additionalProperties
=> 0,
2102 node
=> get_standard_option
('pve-node'),
2103 vmid
=> get_standard_option
('pve-vmid',
2104 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2105 skiplock
=> get_standard_option
('skiplock'),
2107 description
=> "Wait maximal timeout seconds.",
2113 description
=> "Make sure the VM stops.",
2119 description
=> "Do not deactivate storage volumes.",
2132 my $rpcenv = PVE
::RPCEnvironment
::get
();
2134 my $authuser = $rpcenv->get_user();
2136 my $node = extract_param
($param, 'node');
2138 my $vmid = extract_param
($param, 'vmid');
2140 my $skiplock = extract_param
($param, 'skiplock');
2141 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2142 if $skiplock && $authuser ne 'root@pam';
2144 my $keepActive = extract_param
($param, 'keepActive');
2145 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2146 if $keepActive && $authuser ne 'root@pam';
2148 my $storecfg = PVE
::Storage
::config
();
2152 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2153 # otherwise, we will infer a shutdown command, but run into the timeout,
2154 # then when the vm is resumed, it will instantly shutdown
2156 # checking the qmp status here to get feedback to the gui/cli/api
2157 # and the status query should not take too long
2160 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2164 if (!$err && $qmpstatus->{status
} eq "paused") {
2165 if ($param->{forceStop
}) {
2166 warn "VM is paused - stop instead of shutdown\n";
2169 die "VM is paused - cannot shutdown\n";
2173 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2174 ($rpcenv->{type
} ne 'ha')) {
2179 my $service = "vm:$vmid";
2181 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2183 print "Requesting HA stop for VM $vmid\n";
2185 PVE
::Tools
::run_command
($cmd);
2190 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2197 syslog
('info', "shutdown VM $vmid: $upid\n");
2199 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2200 $shutdown, $param->{forceStop
}, $keepActive);
2205 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2209 __PACKAGE__-
>register_method({
2210 name
=> 'vm_suspend',
2211 path
=> '{vmid}/status/suspend',
2215 description
=> "Suspend virtual machine.",
2217 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2220 additionalProperties
=> 0,
2222 node
=> get_standard_option
('pve-node'),
2223 vmid
=> get_standard_option
('pve-vmid',
2224 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2225 skiplock
=> get_standard_option
('skiplock'),
2234 my $rpcenv = PVE
::RPCEnvironment
::get
();
2236 my $authuser = $rpcenv->get_user();
2238 my $node = extract_param
($param, 'node');
2240 my $vmid = extract_param
($param, 'vmid');
2242 my $skiplock = extract_param
($param, 'skiplock');
2243 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2244 if $skiplock && $authuser ne 'root@pam';
2246 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2251 syslog
('info', "suspend VM $vmid: $upid\n");
2253 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2258 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2261 __PACKAGE__-
>register_method({
2262 name
=> 'vm_resume',
2263 path
=> '{vmid}/status/resume',
2267 description
=> "Resume virtual machine.",
2269 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2272 additionalProperties
=> 0,
2274 node
=> get_standard_option
('pve-node'),
2275 vmid
=> get_standard_option
('pve-vmid',
2276 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2277 skiplock
=> get_standard_option
('skiplock'),
2278 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2288 my $rpcenv = PVE
::RPCEnvironment
::get
();
2290 my $authuser = $rpcenv->get_user();
2292 my $node = extract_param
($param, 'node');
2294 my $vmid = extract_param
($param, 'vmid');
2296 my $skiplock = extract_param
($param, 'skiplock');
2297 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2298 if $skiplock && $authuser ne 'root@pam';
2300 my $nocheck = extract_param
($param, 'nocheck');
2302 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2307 syslog
('info', "resume VM $vmid: $upid\n");
2309 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2314 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2317 __PACKAGE__-
>register_method({
2318 name
=> 'vm_sendkey',
2319 path
=> '{vmid}/sendkey',
2323 description
=> "Send key event to virtual machine.",
2325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2328 additionalProperties
=> 0,
2330 node
=> get_standard_option
('pve-node'),
2331 vmid
=> get_standard_option
('pve-vmid',
2332 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2333 skiplock
=> get_standard_option
('skiplock'),
2335 description
=> "The key (qemu monitor encoding).",
2340 returns
=> { type
=> 'null'},
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $skiplock = extract_param
($param, 'skiplock');
2353 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2354 if $skiplock && $authuser ne 'root@pam';
2356 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2361 __PACKAGE__-
>register_method({
2362 name
=> 'vm_feature',
2363 path
=> '{vmid}/feature',
2367 description
=> "Check if feature for virtual machine is available.",
2369 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2372 additionalProperties
=> 0,
2374 node
=> get_standard_option
('pve-node'),
2375 vmid
=> get_standard_option
('pve-vmid'),
2377 description
=> "Feature to check.",
2379 enum
=> [ 'snapshot', 'clone', 'copy' ],
2381 snapname
=> get_standard_option
('pve-snapshot-name', {
2389 hasFeature
=> { type
=> 'boolean' },
2392 items
=> { type
=> 'string' },
2399 my $node = extract_param
($param, 'node');
2401 my $vmid = extract_param
($param, 'vmid');
2403 my $snapname = extract_param
($param, 'snapname');
2405 my $feature = extract_param
($param, 'feature');
2407 my $running = PVE
::QemuServer
::check_running
($vmid);
2409 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2412 my $snap = $conf->{snapshots
}->{$snapname};
2413 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2416 my $storecfg = PVE
::Storage
::config
();
2418 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2419 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2422 hasFeature
=> $hasFeature,
2423 nodes
=> [ keys %$nodelist ],
2427 __PACKAGE__-
>register_method({
2429 path
=> '{vmid}/clone',
2433 description
=> "Create a copy of virtual machine/template.",
2435 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2436 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2437 "'Datastore.AllocateSpace' on any used storage.",
2440 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2442 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2443 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2448 additionalProperties
=> 0,
2450 node
=> get_standard_option
('pve-node'),
2451 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2452 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2455 type
=> 'string', format
=> 'dns-name',
2456 description
=> "Set a name for the new VM.",
2461 description
=> "Description for the new VM.",
2465 type
=> 'string', format
=> 'pve-poolid',
2466 description
=> "Add the new VM to the specified pool.",
2468 snapname
=> get_standard_option
('pve-snapshot-name', {
2471 storage
=> get_standard_option
('pve-storage-id', {
2472 description
=> "Target storage for full clone.",
2477 description
=> "Target format for file storage.",
2481 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2486 description
=> "Create a full copy of all disk. This is always done when " .
2487 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2490 target
=> get_standard_option
('pve-node', {
2491 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $newid = extract_param
($param, 'newid');
2512 my $pool = extract_param
($param, 'pool');
2514 if (defined($pool)) {
2515 $rpcenv->check_pool_exist($pool);
2518 my $snapname = extract_param
($param, 'snapname');
2520 my $storage = extract_param
($param, 'storage');
2522 my $format = extract_param
($param, 'format');
2524 my $target = extract_param
($param, 'target');
2526 my $localnode = PVE
::INotify
::nodename
();
2528 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2530 PVE
::Cluster
::check_node_exists
($target) if $target;
2532 my $storecfg = PVE
::Storage
::config
();
2535 # check if storage is enabled on local node
2536 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2538 # check if storage is available on target node
2539 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2540 # clone only works if target storage is shared
2541 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2542 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2546 PVE
::Cluster
::check_cfs_quorum
();
2548 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2550 # exclusive lock if VM is running - else shared lock is enough;
2551 my $shared_lock = $running ?
0 : 1;
2555 # do all tests after lock
2556 # we also try to do all tests before we fork the worker
2558 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2560 PVE
::QemuConfig-
>check_lock($conf);
2562 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2564 die "unexpected state change\n" if $verify_running != $running;
2566 die "snapshot '$snapname' does not exist\n"
2567 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2569 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2571 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2573 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2575 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2577 die "unable to create VM $newid: config file already exists\n"
2580 my $newconf = { lock => 'clone' };
2585 foreach my $opt (keys %$oldconf) {
2586 my $value = $oldconf->{$opt};
2588 # do not copy snapshot related info
2589 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2590 $opt eq 'vmstate' || $opt eq 'snapstate';
2592 # no need to copy unused images, because VMID(owner) changes anyways
2593 next if $opt =~ m/^unused\d+$/;
2595 # always change MAC! address
2596 if ($opt =~ m/^net(\d+)$/) {
2597 my $net = PVE
::QemuServer
::parse_net
($value);
2598 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2599 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2600 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2601 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2602 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2603 die "unable to parse drive options for '$opt'\n" if !$drive;
2604 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2605 $newconf->{$opt} = $value; # simply copy configuration
2607 if ($param->{full
}) {
2608 die "Full clone feature is not supported for drive '$opt'\n"
2609 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2610 $fullclone->{$opt} = 1;
2612 # not full means clone instead of copy
2613 die "Linked clone feature is not supported for drive '$opt'\n"
2614 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2616 $drives->{$opt} = $drive;
2617 push @$vollist, $drive->{file
};
2620 # copy everything else
2621 $newconf->{$opt} = $value;
2625 # auto generate a new uuid
2626 my ($uuid, $uuid_str);
2627 UUID
::generate
($uuid);
2628 UUID
::unparse
($uuid, $uuid_str);
2629 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2630 $smbios1->{uuid
} = $uuid_str;
2631 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2633 delete $newconf->{template
};
2635 if ($param->{name
}) {
2636 $newconf->{name
} = $param->{name
};
2638 if ($oldconf->{name
}) {
2639 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2641 $newconf->{name
} = "Copy-of-VM-$vmid";
2645 if ($param->{description
}) {
2646 $newconf->{description
} = $param->{description
};
2649 # create empty/temp config - this fails if VM already exists on other node
2650 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2655 my $newvollist = [];
2662 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2664 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2666 my $total_jobs = scalar(keys %{$drives});
2669 foreach my $opt (keys %$drives) {
2670 my $drive = $drives->{$opt};
2671 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2673 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2674 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2675 $jobs, $skipcomplete, $oldconf->{agent
});
2677 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2679 PVE
::QemuConfig-
>write_config($newid, $newconf);
2683 delete $newconf->{lock};
2684 PVE
::QemuConfig-
>write_config($newid, $newconf);
2687 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2688 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2689 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2691 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2692 die "Failed to move config to node '$target' - rename failed: $!\n"
2693 if !rename($conffile, $newconffile);
2696 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2701 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2703 sleep 1; # some storage like rbd need to wait before release volume - really?
2705 foreach my $volid (@$newvollist) {
2706 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2709 die "clone failed: $err";
2715 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2717 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2720 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2721 # Aquire exclusive lock lock for $newid
2722 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2727 __PACKAGE__-
>register_method({
2728 name
=> 'move_vm_disk',
2729 path
=> '{vmid}/move_disk',
2733 description
=> "Move volume to different storage.",
2735 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2737 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2738 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2742 additionalProperties
=> 0,
2744 node
=> get_standard_option
('pve-node'),
2745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2748 description
=> "The disk you want to move.",
2749 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2751 storage
=> get_standard_option
('pve-storage-id', {
2752 description
=> "Target storage.",
2753 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2757 description
=> "Target Format.",
2758 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2763 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2769 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2777 description
=> "the task ID.",
2782 my $rpcenv = PVE
::RPCEnvironment
::get
();
2784 my $authuser = $rpcenv->get_user();
2786 my $node = extract_param
($param, 'node');
2788 my $vmid = extract_param
($param, 'vmid');
2790 my $digest = extract_param
($param, 'digest');
2792 my $disk = extract_param
($param, 'disk');
2794 my $storeid = extract_param
($param, 'storage');
2796 my $format = extract_param
($param, 'format');
2798 my $storecfg = PVE
::Storage
::config
();
2800 my $updatefn = sub {
2802 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2804 PVE
::QemuConfig-
>check_lock($conf);
2806 die "checksum missmatch (file change by other user?)\n"
2807 if $digest && $digest ne $conf->{digest
};
2809 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2811 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2813 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2815 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2818 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2819 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2823 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2824 (!$format || !$oldfmt || $oldfmt eq $format);
2826 # this only checks snapshots because $disk is passed!
2827 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2828 die "you can't move a disk with snapshots and delete the source\n"
2829 if $snapshotted && $param->{delete};
2831 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2833 my $running = PVE
::QemuServer
::check_running
($vmid);
2835 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2839 my $newvollist = [];
2845 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2847 warn "moving disk with snapshots, snapshots will not be moved!\n"
2850 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2851 $vmid, $storeid, $format, 1, $newvollist);
2853 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2855 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2857 # convert moved disk to base if part of template
2858 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2859 if PVE
::QemuConfig-
>is_template($conf);
2861 PVE
::QemuConfig-
>write_config($vmid, $conf);
2864 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2865 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2872 foreach my $volid (@$newvollist) {
2873 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2876 die "storage migration failed: $err";
2879 if ($param->{delete}) {
2881 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2882 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2888 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2891 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2894 __PACKAGE__-
>register_method({
2895 name
=> 'migrate_vm',
2896 path
=> '{vmid}/migrate',
2900 description
=> "Migrate virtual machine. Creates a new migration task.",
2902 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2905 additionalProperties
=> 0,
2907 node
=> get_standard_option
('pve-node'),
2908 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2909 target
=> get_standard_option
('pve-node', {
2910 description
=> "Target node.",
2911 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2915 description
=> "Use online/live migration.",
2920 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2925 enum
=> ['secure', 'insecure'],
2926 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2929 migration_network
=> {
2930 type
=> 'string', format
=> 'CIDR',
2931 description
=> "CIDR of the (sub) network that is used for migration.",
2934 "with-local-disks" => {
2936 description
=> "Enable live storage migration for local disk",
2939 targetstorage
=> get_standard_option
('pve-storage-id', {
2940 description
=> "Default target storage.",
2942 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2948 description
=> "the task ID.",
2953 my $rpcenv = PVE
::RPCEnvironment
::get
();
2955 my $authuser = $rpcenv->get_user();
2957 my $target = extract_param
($param, 'target');
2959 my $localnode = PVE
::INotify
::nodename
();
2960 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2962 PVE
::Cluster
::check_cfs_quorum
();
2964 PVE
::Cluster
::check_node_exists
($target);
2966 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2968 my $vmid = extract_param
($param, 'vmid');
2970 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2971 if !$param->{online
} && $param->{targetstorage
};
2973 raise_param_exc
({ force
=> "Only root may use this option." })
2974 if $param->{force
} && $authuser ne 'root@pam';
2976 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2977 if $param->{migration_type
} && $authuser ne 'root@pam';
2979 # allow root only until better network permissions are available
2980 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2981 if $param->{migration_network
} && $authuser ne 'root@pam';
2984 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2986 # try to detect errors early
2988 PVE
::QemuConfig-
>check_lock($conf);
2990 if (PVE
::QemuServer
::check_running
($vmid)) {
2991 die "cant migrate running VM without --online\n"
2992 if !$param->{online
};
2995 my $storecfg = PVE
::Storage
::config
();
2997 if( $param->{targetstorage
}) {
2998 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3000 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3003 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3008 my $service = "vm:$vmid";
3010 my $cmd = ['ha-manager', 'migrate', $service, $target];
3012 print "Requesting HA migration for VM $vmid to node $target\n";
3014 PVE
::Tools
::run_command
($cmd);
3019 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3024 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3028 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3031 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3036 __PACKAGE__-
>register_method({
3038 path
=> '{vmid}/monitor',
3042 description
=> "Execute Qemu monitor commands.",
3044 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3048 additionalProperties
=> 0,
3050 node
=> get_standard_option
('pve-node'),
3051 vmid
=> get_standard_option
('pve-vmid'),
3054 description
=> "The monitor command.",
3058 returns
=> { type
=> 'string'},
3062 my $rpcenv = PVE
::RPCEnvironment
::get
();
3063 my $authuser = $rpcenv->get_user();
3066 my $command = shift;
3067 return $command =~ m/^\s*info(\s+|$)/
3068 || $command =~ m/^\s*help\s*$/;
3071 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3072 if !&$is_ro($param->{command
});
3074 my $vmid = $param->{vmid
};
3076 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3080 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3082 $res = "ERROR: $@" if $@;
3087 __PACKAGE__-
>register_method({
3088 name
=> 'resize_vm',
3089 path
=> '{vmid}/resize',
3093 description
=> "Extend volume size.",
3095 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3098 additionalProperties
=> 0,
3100 node
=> get_standard_option
('pve-node'),
3101 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3102 skiplock
=> get_standard_option
('skiplock'),
3105 description
=> "The disk you want to resize.",
3106 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3110 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3111 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.",
3115 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3121 returns
=> { type
=> 'null'},
3125 my $rpcenv = PVE
::RPCEnvironment
::get
();
3127 my $authuser = $rpcenv->get_user();
3129 my $node = extract_param
($param, 'node');
3131 my $vmid = extract_param
($param, 'vmid');
3133 my $digest = extract_param
($param, 'digest');
3135 my $disk = extract_param
($param, 'disk');
3137 my $sizestr = extract_param
($param, 'size');
3139 my $skiplock = extract_param
($param, 'skiplock');
3140 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3141 if $skiplock && $authuser ne 'root@pam';
3143 my $storecfg = PVE
::Storage
::config
();
3145 my $updatefn = sub {
3147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3149 die "checksum missmatch (file change by other user?)\n"
3150 if $digest && $digest ne $conf->{digest
};
3151 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3153 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3155 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3157 my (undef, undef, undef, undef, undef, undef, $format) =
3158 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3160 die "can't resize volume: $disk if snapshot exists\n"
3161 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3163 my $volid = $drive->{file
};
3165 die "disk '$disk' has no associated volume\n" if !$volid;
3167 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3169 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3171 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3173 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3174 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3176 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3177 my ($ext, $newsize, $unit) = ($1, $2, $4);
3180 $newsize = $newsize * 1024;
3181 } elsif ($unit eq 'M') {
3182 $newsize = $newsize * 1024 * 1024;
3183 } elsif ($unit eq 'G') {
3184 $newsize = $newsize * 1024 * 1024 * 1024;
3185 } elsif ($unit eq 'T') {
3186 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3189 $newsize += $size if $ext;
3190 $newsize = int($newsize);
3192 die "shrinking disks is not supported\n" if $newsize < $size;
3194 return if $size == $newsize;
3196 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3198 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3200 $drive->{size
} = $newsize;
3201 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3203 PVE
::QemuConfig-
>write_config($vmid, $conf);
3206 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3210 __PACKAGE__-
>register_method({
3211 name
=> 'snapshot_list',
3212 path
=> '{vmid}/snapshot',
3214 description
=> "List all snapshots.",
3216 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3219 protected
=> 1, # qemu pid files are only readable by root
3221 additionalProperties
=> 0,
3223 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3224 node
=> get_standard_option
('pve-node'),
3233 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3238 my $vmid = $param->{vmid
};
3240 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3241 my $snaphash = $conf->{snapshots
} || {};
3245 foreach my $name (keys %$snaphash) {
3246 my $d = $snaphash->{$name};
3249 snaptime
=> $d->{snaptime
} || 0,
3250 vmstate
=> $d->{vmstate
} ?
1 : 0,
3251 description
=> $d->{description
} || '',
3253 $item->{parent
} = $d->{parent
} if $d->{parent
};
3254 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3258 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3259 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3260 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3262 push @$res, $current;
3267 __PACKAGE__-
>register_method({
3269 path
=> '{vmid}/snapshot',
3273 description
=> "Snapshot a VM.",
3275 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3278 additionalProperties
=> 0,
3280 node
=> get_standard_option
('pve-node'),
3281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3282 snapname
=> get_standard_option
('pve-snapshot-name'),
3286 description
=> "Save the vmstate",
3291 description
=> "A textual description or comment.",
3297 description
=> "the task ID.",
3302 my $rpcenv = PVE
::RPCEnvironment
::get
();
3304 my $authuser = $rpcenv->get_user();
3306 my $node = extract_param
($param, 'node');
3308 my $vmid = extract_param
($param, 'vmid');
3310 my $snapname = extract_param
($param, 'snapname');
3312 die "unable to use snapshot name 'current' (reserved name)\n"
3313 if $snapname eq 'current';
3316 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3317 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3318 $param->{description
});
3321 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3324 __PACKAGE__-
>register_method({
3325 name
=> 'snapshot_cmd_idx',
3326 path
=> '{vmid}/snapshot/{snapname}',
3333 additionalProperties
=> 0,
3335 vmid
=> get_standard_option
('pve-vmid'),
3336 node
=> get_standard_option
('pve-node'),
3337 snapname
=> get_standard_option
('pve-snapshot-name'),
3346 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3353 push @$res, { cmd
=> 'rollback' };
3354 push @$res, { cmd
=> 'config' };
3359 __PACKAGE__-
>register_method({
3360 name
=> 'update_snapshot_config',
3361 path
=> '{vmid}/snapshot/{snapname}/config',
3365 description
=> "Update snapshot metadata.",
3367 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3370 additionalProperties
=> 0,
3372 node
=> get_standard_option
('pve-node'),
3373 vmid
=> get_standard_option
('pve-vmid'),
3374 snapname
=> get_standard_option
('pve-snapshot-name'),
3378 description
=> "A textual description or comment.",
3382 returns
=> { type
=> 'null' },
3386 my $rpcenv = PVE
::RPCEnvironment
::get
();
3388 my $authuser = $rpcenv->get_user();
3390 my $vmid = extract_param
($param, 'vmid');
3392 my $snapname = extract_param
($param, 'snapname');
3394 return undef if !defined($param->{description
});
3396 my $updatefn = sub {
3398 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3400 PVE
::QemuConfig-
>check_lock($conf);
3402 my $snap = $conf->{snapshots
}->{$snapname};
3404 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3406 $snap->{description
} = $param->{description
} if defined($param->{description
});
3408 PVE
::QemuConfig-
>write_config($vmid, $conf);
3411 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3416 __PACKAGE__-
>register_method({
3417 name
=> 'get_snapshot_config',
3418 path
=> '{vmid}/snapshot/{snapname}/config',
3421 description
=> "Get snapshot configuration",
3423 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3426 additionalProperties
=> 0,
3428 node
=> get_standard_option
('pve-node'),
3429 vmid
=> get_standard_option
('pve-vmid'),
3430 snapname
=> get_standard_option
('pve-snapshot-name'),
3433 returns
=> { type
=> "object" },
3437 my $rpcenv = PVE
::RPCEnvironment
::get
();
3439 my $authuser = $rpcenv->get_user();
3441 my $vmid = extract_param
($param, 'vmid');
3443 my $snapname = extract_param
($param, 'snapname');
3445 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3447 my $snap = $conf->{snapshots
}->{$snapname};
3449 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3454 __PACKAGE__-
>register_method({
3456 path
=> '{vmid}/snapshot/{snapname}/rollback',
3460 description
=> "Rollback VM state to specified snapshot.",
3462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3465 additionalProperties
=> 0,
3467 node
=> get_standard_option
('pve-node'),
3468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3469 snapname
=> get_standard_option
('pve-snapshot-name'),
3474 description
=> "the task ID.",
3479 my $rpcenv = PVE
::RPCEnvironment
::get
();
3481 my $authuser = $rpcenv->get_user();
3483 my $node = extract_param
($param, 'node');
3485 my $vmid = extract_param
($param, 'vmid');
3487 my $snapname = extract_param
($param, 'snapname');
3490 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3491 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3495 # hold migration lock, this makes sure that nobody create replication snapshots
3496 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3499 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3502 __PACKAGE__-
>register_method({
3503 name
=> 'delsnapshot',
3504 path
=> '{vmid}/snapshot/{snapname}',
3508 description
=> "Delete a VM snapshot.",
3510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3513 additionalProperties
=> 0,
3515 node
=> get_standard_option
('pve-node'),
3516 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3517 snapname
=> get_standard_option
('pve-snapshot-name'),
3521 description
=> "For removal from config file, even if removing disk snapshots fails.",
3527 description
=> "the task ID.",
3532 my $rpcenv = PVE
::RPCEnvironment
::get
();
3534 my $authuser = $rpcenv->get_user();
3536 my $node = extract_param
($param, 'node');
3538 my $vmid = extract_param
($param, 'vmid');
3540 my $snapname = extract_param
($param, 'snapname');
3543 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3544 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3547 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3550 __PACKAGE__-
>register_method({
3552 path
=> '{vmid}/template',
3556 description
=> "Create a Template.",
3558 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3559 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3562 additionalProperties
=> 0,
3564 node
=> get_standard_option
('pve-node'),
3565 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3569 description
=> "If you want to convert only 1 disk to base image.",
3570 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3575 returns
=> { type
=> 'null'},
3579 my $rpcenv = PVE
::RPCEnvironment
::get
();
3581 my $authuser = $rpcenv->get_user();
3583 my $node = extract_param
($param, 'node');
3585 my $vmid = extract_param
($param, 'vmid');
3587 my $disk = extract_param
($param, 'disk');
3589 my $updatefn = sub {
3591 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3593 PVE
::QemuConfig-
>check_lock($conf);
3595 die "unable to create template, because VM contains snapshots\n"
3596 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3598 die "you can't convert a template to a template\n"
3599 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3601 die "you can't convert a VM to template if VM is running\n"
3602 if PVE
::QemuServer
::check_running
($vmid);
3605 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3608 $conf->{template
} = 1;
3609 PVE
::QemuConfig-
>write_config($vmid, $conf);
3611 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3614 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);