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');
923 if (defined(my $cipassword = $param->{cipassword
})) {
924 # Same logic as in cloud-init (but with the regex fixed...)
925 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
926 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
929 my @paramarr = (); # used for log message
930 foreach my $key (sort keys %$param) {
931 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
932 push @paramarr, "-$key", $value;
935 my $skiplock = extract_param
($param, 'skiplock');
936 raise_param_exc
({ skiplock
=> "Only root may use this option." })
937 if $skiplock && $authuser ne 'root@pam';
939 my $delete_str = extract_param
($param, 'delete');
941 my $revert_str = extract_param
($param, 'revert');
943 my $force = extract_param
($param, 'force');
945 if (defined(my $ssh_keys = $param->{sshkeys
})) {
946 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
947 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
950 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
952 my $storecfg = PVE
::Storage
::config
();
954 my $defaults = PVE
::QemuServer
::load_defaults
();
956 &$resolve_cdrom_alias($param);
958 # now try to verify all parameters
961 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
962 if (!PVE
::QemuServer
::option_exists
($opt)) {
963 raise_param_exc
({ revert
=> "unknown option '$opt'" });
966 raise_param_exc
({ delete => "you can't use '-$opt' and " .
967 "-revert $opt' at the same time" })
968 if defined($param->{$opt});
974 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
975 $opt = 'ide2' if $opt eq 'cdrom';
977 raise_param_exc
({ delete => "you can't use '-$opt' and " .
978 "-delete $opt' at the same time" })
979 if defined($param->{$opt});
981 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
982 "-revert $opt' at the same time" })
985 if (!PVE
::QemuServer
::option_exists
($opt)) {
986 raise_param_exc
({ delete => "unknown option '$opt'" });
992 my $repl_conf = PVE
::ReplicationConfig-
>new();
993 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
994 my $check_replication = sub {
996 return if !$is_replicated;
997 my $volid = $drive->{file
};
998 return if !$volid || !($drive->{replicate
}//1);
999 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1000 my ($storeid, $format);
1001 if ($volid =~ $NEW_DISK_RE) {
1003 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1005 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1006 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1008 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1009 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1010 return if $scfg->{shared
};
1011 die "cannot add non-replicatable volume to a replicated VM\n";
1014 foreach my $opt (keys %$param) {
1015 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1016 # cleanup drive path
1017 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1018 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1019 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1020 $check_replication->($drive);
1021 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1022 } elsif ($opt =~ m/^net(\d+)$/) {
1024 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1025 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1029 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1031 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1033 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1035 my $updatefn = sub {
1037 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1039 die "checksum missmatch (file change by other user?)\n"
1040 if $digest && $digest ne $conf->{digest
};
1042 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1044 foreach my $opt (keys %$revert) {
1045 if (defined($conf->{$opt})) {
1046 $param->{$opt} = $conf->{$opt};
1047 } elsif (defined($conf->{pending
}->{$opt})) {
1052 if ($param->{memory
} || defined($param->{balloon
})) {
1053 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1054 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1056 die "balloon value too large (must be smaller than assigned memory)\n"
1057 if $balloon && $balloon > $maxmem;
1060 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1064 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1066 # write updates to pending section
1068 my $modified = {}; # record what $option we modify
1070 foreach my $opt (@delete) {
1071 $modified->{$opt} = 1;
1072 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1073 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1074 warn "cannot delete '$opt' - not set in current configuration!\n";
1075 $modified->{$opt} = 0;
1079 if ($opt =~ m/^unused/) {
1080 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1081 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1082 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1083 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1084 delete $conf->{$opt};
1085 PVE
::QemuConfig-
>write_config($vmid, $conf);
1087 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1088 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1089 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1090 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1091 if defined($conf->{pending
}->{$opt});
1092 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1093 PVE
::QemuConfig-
>write_config($vmid, $conf);
1095 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1096 PVE
::QemuConfig-
>write_config($vmid, $conf);
1100 foreach my $opt (keys %$param) { # add/change
1101 $modified->{$opt} = 1;
1102 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1103 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1105 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1106 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1107 # FIXME: cloudinit: CDROM or Disk?
1108 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1109 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1111 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1113 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1114 if defined($conf->{pending
}->{$opt});
1116 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1118 $conf->{pending
}->{$opt} = $param->{$opt};
1120 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1121 PVE
::QemuConfig-
>write_config($vmid, $conf);
1124 # remove pending changes when nothing changed
1125 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1126 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1127 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1129 return if !scalar(keys %{$conf->{pending
}});
1131 my $running = PVE
::QemuServer
::check_running
($vmid);
1133 # apply pending changes
1135 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1139 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1140 raise_param_exc
($errors) if scalar(keys %$errors);
1142 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1152 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1154 if ($background_delay) {
1156 # Note: It would be better to do that in the Event based HTTPServer
1157 # to avoid blocking call to sleep.
1159 my $end_time = time() + $background_delay;
1161 my $task = PVE
::Tools
::upid_decode
($upid);
1164 while (time() < $end_time) {
1165 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1167 sleep(1); # this gets interrupted when child process ends
1171 my $status = PVE
::Tools
::upid_read_status
($upid);
1172 return undef if $status eq 'OK';
1181 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1184 my $vm_config_perm_list = [
1189 'VM.Config.Network',
1191 'VM.Config.Options',
1194 __PACKAGE__-
>register_method({
1195 name
=> 'update_vm_async',
1196 path
=> '{vmid}/config',
1200 description
=> "Set virtual machine options (asynchrounous API).",
1202 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1205 additionalProperties
=> 0,
1206 properties
=> PVE
::QemuServer
::json_config_properties
(
1208 node
=> get_standard_option
('pve-node'),
1209 vmid
=> get_standard_option
('pve-vmid'),
1210 skiplock
=> get_standard_option
('skiplock'),
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "A list of settings you want to delete.",
1217 type
=> 'string', format
=> 'pve-configid-list',
1218 description
=> "Revert a pending change.",
1223 description
=> $opt_force_description,
1225 requires
=> 'delete',
1229 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1233 background_delay
=> {
1235 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1246 code
=> $update_vm_api,
1249 __PACKAGE__-
>register_method({
1250 name
=> 'update_vm',
1251 path
=> '{vmid}/config',
1255 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1257 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1260 additionalProperties
=> 0,
1261 properties
=> PVE
::QemuServer
::json_config_properties
(
1263 node
=> get_standard_option
('pve-node'),
1264 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1265 skiplock
=> get_standard_option
('skiplock'),
1267 type
=> 'string', format
=> 'pve-configid-list',
1268 description
=> "A list of settings you want to delete.",
1272 type
=> 'string', format
=> 'pve-configid-list',
1273 description
=> "Revert a pending change.",
1278 description
=> $opt_force_description,
1280 requires
=> 'delete',
1284 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1290 returns
=> { type
=> 'null' },
1293 &$update_vm_api($param, 1);
1299 __PACKAGE__-
>register_method({
1300 name
=> 'destroy_vm',
1305 description
=> "Destroy the vm (also delete all used/owned volumes).",
1307 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1310 additionalProperties
=> 0,
1312 node
=> get_standard_option
('pve-node'),
1313 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1314 skiplock
=> get_standard_option
('skiplock'),
1323 my $rpcenv = PVE
::RPCEnvironment
::get
();
1325 my $authuser = $rpcenv->get_user();
1327 my $vmid = $param->{vmid
};
1329 my $skiplock = $param->{skiplock
};
1330 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1331 if $skiplock && $authuser ne 'root@pam';
1334 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1336 my $storecfg = PVE
::Storage
::config
();
1338 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1340 die "unable to remove VM $vmid - used in HA resources\n"
1341 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1343 # do not allow destroy if there are replication jobs
1344 my $repl_conf = PVE
::ReplicationConfig-
>new();
1345 $repl_conf->check_for_existing_jobs($vmid);
1347 # early tests (repeat after locking)
1348 die "VM $vmid is running - destroy failed\n"
1349 if PVE
::QemuServer
::check_running
($vmid);
1354 syslog
('info', "destroy VM $vmid: $upid\n");
1356 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1358 PVE
::AccessControl
::remove_vm_access
($vmid);
1360 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1363 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1366 __PACKAGE__-
>register_method({
1368 path
=> '{vmid}/unlink',
1372 description
=> "Unlink/delete disk images.",
1374 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1377 additionalProperties
=> 0,
1379 node
=> get_standard_option
('pve-node'),
1380 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1382 type
=> 'string', format
=> 'pve-configid-list',
1383 description
=> "A list of disk IDs you want to delete.",
1387 description
=> $opt_force_description,
1392 returns
=> { type
=> 'null'},
1396 $param->{delete} = extract_param
($param, 'idlist');
1398 __PACKAGE__-
>update_vm($param);
1405 __PACKAGE__-
>register_method({
1407 path
=> '{vmid}/vncproxy',
1411 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1413 description
=> "Creates a TCP VNC proxy connections.",
1415 additionalProperties
=> 0,
1417 node
=> get_standard_option
('pve-node'),
1418 vmid
=> get_standard_option
('pve-vmid'),
1422 description
=> "starts websockify instead of vncproxy",
1427 additionalProperties
=> 0,
1429 user
=> { type
=> 'string' },
1430 ticket
=> { type
=> 'string' },
1431 cert
=> { type
=> 'string' },
1432 port
=> { type
=> 'integer' },
1433 upid
=> { type
=> 'string' },
1439 my $rpcenv = PVE
::RPCEnvironment
::get
();
1441 my $authuser = $rpcenv->get_user();
1443 my $vmid = $param->{vmid
};
1444 my $node = $param->{node
};
1445 my $websocket = $param->{websocket
};
1447 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1449 my $authpath = "/vms/$vmid";
1451 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1453 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1456 my ($remip, $family);
1459 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1460 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1461 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1462 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1464 $family = PVE
::Tools
::get_host_address_family
($node);
1467 my $port = PVE
::Tools
::next_vnc_port
($family);
1474 syslog
('info', "starting vnc proxy $upid\n");
1478 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1481 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1483 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1484 '-timeout', $timeout, '-authpath', $authpath,
1485 '-perm', 'Sys.Console'];
1487 if ($param->{websocket
}) {
1488 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1489 push @$cmd, '-notls', '-listen', 'localhost';
1492 push @$cmd, '-c', @$remcmd, @$termcmd;
1494 PVE
::Tools
::run_command
($cmd);
1498 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1500 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1502 my $sock = IO
::Socket
::IP-
>new(
1507 GetAddrInfoFlags
=> 0,
1508 ) or die "failed to create socket: $!\n";
1509 # Inside the worker we shouldn't have any previous alarms
1510 # running anyway...:
1512 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1514 accept(my $cli, $sock) or die "connection failed: $!\n";
1517 if (PVE
::Tools
::run_command
($cmd,
1518 output
=> '>&'.fileno($cli),
1519 input
=> '<&'.fileno($cli),
1522 die "Failed to run vncproxy.\n";
1529 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1531 PVE
::Tools
::wait_for_vnc_port
($port);
1542 __PACKAGE__-
>register_method({
1543 name
=> 'termproxy',
1544 path
=> '{vmid}/termproxy',
1548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1550 description
=> "Creates a TCP proxy connections.",
1552 additionalProperties
=> 0,
1554 node
=> get_standard_option
('pve-node'),
1555 vmid
=> get_standard_option
('pve-vmid'),
1559 enum
=> [qw(serial0 serial1 serial2 serial3)],
1560 description
=> "opens a serial terminal (defaults to display)",
1565 additionalProperties
=> 0,
1567 user
=> { type
=> 'string' },
1568 ticket
=> { type
=> 'string' },
1569 port
=> { type
=> 'integer' },
1570 upid
=> { type
=> 'string' },
1576 my $rpcenv = PVE
::RPCEnvironment
::get
();
1578 my $authuser = $rpcenv->get_user();
1580 my $vmid = $param->{vmid
};
1581 my $node = $param->{node
};
1582 my $serial = $param->{serial
};
1584 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1586 if (!defined($serial)) {
1587 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1588 $serial = $conf->{vga
};
1592 my $authpath = "/vms/$vmid";
1594 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1596 my ($remip, $family);
1598 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1599 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1601 $family = PVE
::Tools
::get_host_address_family
($node);
1604 my $port = PVE
::Tools
::next_vnc_port
($family);
1606 my $remcmd = $remip ?
1607 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1609 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1610 push @$termcmd, '-iface', $serial if $serial;
1615 syslog
('info', "starting qemu termproxy $upid\n");
1617 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1618 '--perm', 'VM.Console', '--'];
1619 push @$cmd, @$remcmd, @$termcmd;
1621 PVE
::Tools
::run_command
($cmd);
1624 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1626 PVE
::Tools
::wait_for_vnc_port
($port);
1636 __PACKAGE__-
>register_method({
1637 name
=> 'vncwebsocket',
1638 path
=> '{vmid}/vncwebsocket',
1641 description
=> "You also need to pass a valid ticket (vncticket).",
1642 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1644 description
=> "Opens a weksocket for VNC traffic.",
1646 additionalProperties
=> 0,
1648 node
=> get_standard_option
('pve-node'),
1649 vmid
=> get_standard_option
('pve-vmid'),
1651 description
=> "Ticket from previous call to vncproxy.",
1656 description
=> "Port number returned by previous vncproxy call.",
1666 port
=> { type
=> 'string' },
1672 my $rpcenv = PVE
::RPCEnvironment
::get
();
1674 my $authuser = $rpcenv->get_user();
1676 my $vmid = $param->{vmid
};
1677 my $node = $param->{node
};
1679 my $authpath = "/vms/$vmid";
1681 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1683 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1685 # Note: VNC ports are acessible from outside, so we do not gain any
1686 # security if we verify that $param->{port} belongs to VM $vmid. This
1687 # check is done by verifying the VNC ticket (inside VNC protocol).
1689 my $port = $param->{port
};
1691 return { port
=> $port };
1694 __PACKAGE__-
>register_method({
1695 name
=> 'spiceproxy',
1696 path
=> '{vmid}/spiceproxy',
1701 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1703 description
=> "Returns a SPICE configuration to connect to the VM.",
1705 additionalProperties
=> 0,
1707 node
=> get_standard_option
('pve-node'),
1708 vmid
=> get_standard_option
('pve-vmid'),
1709 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1712 returns
=> get_standard_option
('remote-viewer-config'),
1716 my $rpcenv = PVE
::RPCEnvironment
::get
();
1718 my $authuser = $rpcenv->get_user();
1720 my $vmid = $param->{vmid
};
1721 my $node = $param->{node
};
1722 my $proxy = $param->{proxy
};
1724 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1725 my $title = "VM $vmid";
1726 $title .= " - ". $conf->{name
} if $conf->{name
};
1728 my $port = PVE
::QemuServer
::spice_port
($vmid);
1730 my ($ticket, undef, $remote_viewer_config) =
1731 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1733 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1734 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1736 return $remote_viewer_config;
1739 __PACKAGE__-
>register_method({
1741 path
=> '{vmid}/status',
1744 description
=> "Directory index",
1749 additionalProperties
=> 0,
1751 node
=> get_standard_option
('pve-node'),
1752 vmid
=> get_standard_option
('pve-vmid'),
1760 subdir
=> { type
=> 'string' },
1763 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1769 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1772 { subdir
=> 'current' },
1773 { subdir
=> 'start' },
1774 { subdir
=> 'stop' },
1780 __PACKAGE__-
>register_method({
1781 name
=> 'vm_status',
1782 path
=> '{vmid}/status/current',
1785 protected
=> 1, # qemu pid files are only readable by root
1786 description
=> "Get virtual machine status.",
1788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1791 additionalProperties
=> 0,
1793 node
=> get_standard_option
('pve-node'),
1794 vmid
=> get_standard_option
('pve-vmid'),
1797 returns
=> { type
=> 'object' },
1802 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1804 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1805 my $status = $vmstatus->{$param->{vmid
}};
1807 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1809 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1811 $status->{agent
} = 1 if $conf->{agent
};
1816 __PACKAGE__-
>register_method({
1818 path
=> '{vmid}/status/start',
1822 description
=> "Start virtual machine.",
1824 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1827 additionalProperties
=> 0,
1829 node
=> get_standard_option
('pve-node'),
1830 vmid
=> get_standard_option
('pve-vmid',
1831 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1832 skiplock
=> get_standard_option
('skiplock'),
1833 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1834 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1837 enum
=> ['secure', 'insecure'],
1838 description
=> "Migration traffic is encrypted using an SSH " .
1839 "tunnel by default. On secure, completely private networks " .
1840 "this can be disabled to increase performance.",
1843 migration_network
=> {
1844 type
=> 'string', format
=> 'CIDR',
1845 description
=> "CIDR of the (sub) network that is used for migration.",
1848 machine
=> get_standard_option
('pve-qm-machine'),
1850 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1862 my $rpcenv = PVE
::RPCEnvironment
::get
();
1864 my $authuser = $rpcenv->get_user();
1866 my $node = extract_param
($param, 'node');
1868 my $vmid = extract_param
($param, 'vmid');
1870 my $machine = extract_param
($param, 'machine');
1872 my $stateuri = extract_param
($param, 'stateuri');
1873 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1874 if $stateuri && $authuser ne 'root@pam';
1876 my $skiplock = extract_param
($param, 'skiplock');
1877 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1878 if $skiplock && $authuser ne 'root@pam';
1880 my $migratedfrom = extract_param
($param, 'migratedfrom');
1881 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1882 if $migratedfrom && $authuser ne 'root@pam';
1884 my $migration_type = extract_param
($param, 'migration_type');
1885 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1886 if $migration_type && $authuser ne 'root@pam';
1888 my $migration_network = extract_param
($param, 'migration_network');
1889 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1890 if $migration_network && $authuser ne 'root@pam';
1892 my $targetstorage = extract_param
($param, 'targetstorage');
1893 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1894 if $targetstorage && $authuser ne 'root@pam';
1896 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1897 if $targetstorage && !$migratedfrom;
1899 # read spice ticket from STDIN
1901 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1902 if (defined(my $line = <STDIN
>)) {
1904 $spice_ticket = $line;
1908 PVE
::Cluster
::check_cfs_quorum
();
1910 my $storecfg = PVE
::Storage
::config
();
1912 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1913 $rpcenv->{type
} ne 'ha') {
1918 my $service = "vm:$vmid";
1920 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1922 print "Requesting HA start for VM $vmid\n";
1924 PVE
::Tools
::run_command
($cmd);
1929 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1936 syslog
('info', "start VM $vmid: $upid\n");
1938 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1939 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1944 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1948 __PACKAGE__-
>register_method({
1950 path
=> '{vmid}/status/stop',
1954 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1955 "is akin to pulling the power plug of a running computer and may damage the VM data",
1957 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1960 additionalProperties
=> 0,
1962 node
=> get_standard_option
('pve-node'),
1963 vmid
=> get_standard_option
('pve-vmid',
1964 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1965 skiplock
=> get_standard_option
('skiplock'),
1966 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1968 description
=> "Wait maximal timeout seconds.",
1974 description
=> "Do not deactivate storage volumes.",
1987 my $rpcenv = PVE
::RPCEnvironment
::get
();
1989 my $authuser = $rpcenv->get_user();
1991 my $node = extract_param
($param, 'node');
1993 my $vmid = extract_param
($param, 'vmid');
1995 my $skiplock = extract_param
($param, 'skiplock');
1996 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1997 if $skiplock && $authuser ne 'root@pam';
1999 my $keepActive = extract_param
($param, 'keepActive');
2000 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2001 if $keepActive && $authuser ne 'root@pam';
2003 my $migratedfrom = extract_param
($param, 'migratedfrom');
2004 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2005 if $migratedfrom && $authuser ne 'root@pam';
2008 my $storecfg = PVE
::Storage
::config
();
2010 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2015 my $service = "vm:$vmid";
2017 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2019 print "Requesting HA stop for VM $vmid\n";
2021 PVE
::Tools
::run_command
($cmd);
2026 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2032 syslog
('info', "stop VM $vmid: $upid\n");
2034 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2035 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2040 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2044 __PACKAGE__-
>register_method({
2046 path
=> '{vmid}/status/reset',
2050 description
=> "Reset virtual machine.",
2052 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2055 additionalProperties
=> 0,
2057 node
=> get_standard_option
('pve-node'),
2058 vmid
=> get_standard_option
('pve-vmid',
2059 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2060 skiplock
=> get_standard_option
('skiplock'),
2069 my $rpcenv = PVE
::RPCEnvironment
::get
();
2071 my $authuser = $rpcenv->get_user();
2073 my $node = extract_param
($param, 'node');
2075 my $vmid = extract_param
($param, 'vmid');
2077 my $skiplock = extract_param
($param, 'skiplock');
2078 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2079 if $skiplock && $authuser ne 'root@pam';
2081 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2086 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2091 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2094 __PACKAGE__-
>register_method({
2095 name
=> 'vm_shutdown',
2096 path
=> '{vmid}/status/shutdown',
2100 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2101 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2103 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2106 additionalProperties
=> 0,
2108 node
=> get_standard_option
('pve-node'),
2109 vmid
=> get_standard_option
('pve-vmid',
2110 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2111 skiplock
=> get_standard_option
('skiplock'),
2113 description
=> "Wait maximal timeout seconds.",
2119 description
=> "Make sure the VM stops.",
2125 description
=> "Do not deactivate storage volumes.",
2138 my $rpcenv = PVE
::RPCEnvironment
::get
();
2140 my $authuser = $rpcenv->get_user();
2142 my $node = extract_param
($param, 'node');
2144 my $vmid = extract_param
($param, 'vmid');
2146 my $skiplock = extract_param
($param, 'skiplock');
2147 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2148 if $skiplock && $authuser ne 'root@pam';
2150 my $keepActive = extract_param
($param, 'keepActive');
2151 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2152 if $keepActive && $authuser ne 'root@pam';
2154 my $storecfg = PVE
::Storage
::config
();
2158 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2159 # otherwise, we will infer a shutdown command, but run into the timeout,
2160 # then when the vm is resumed, it will instantly shutdown
2162 # checking the qmp status here to get feedback to the gui/cli/api
2163 # and the status query should not take too long
2166 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2170 if (!$err && $qmpstatus->{status
} eq "paused") {
2171 if ($param->{forceStop
}) {
2172 warn "VM is paused - stop instead of shutdown\n";
2175 die "VM is paused - cannot shutdown\n";
2179 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2180 ($rpcenv->{type
} ne 'ha')) {
2185 my $service = "vm:$vmid";
2187 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2189 print "Requesting HA stop for VM $vmid\n";
2191 PVE
::Tools
::run_command
($cmd);
2196 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2203 syslog
('info', "shutdown VM $vmid: $upid\n");
2205 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2206 $shutdown, $param->{forceStop
}, $keepActive);
2211 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2215 __PACKAGE__-
>register_method({
2216 name
=> 'vm_suspend',
2217 path
=> '{vmid}/status/suspend',
2221 description
=> "Suspend virtual machine.",
2223 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2226 additionalProperties
=> 0,
2228 node
=> get_standard_option
('pve-node'),
2229 vmid
=> get_standard_option
('pve-vmid',
2230 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2231 skiplock
=> get_standard_option
('skiplock'),
2240 my $rpcenv = PVE
::RPCEnvironment
::get
();
2242 my $authuser = $rpcenv->get_user();
2244 my $node = extract_param
($param, 'node');
2246 my $vmid = extract_param
($param, 'vmid');
2248 my $skiplock = extract_param
($param, 'skiplock');
2249 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2250 if $skiplock && $authuser ne 'root@pam';
2252 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2257 syslog
('info', "suspend VM $vmid: $upid\n");
2259 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2264 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2267 __PACKAGE__-
>register_method({
2268 name
=> 'vm_resume',
2269 path
=> '{vmid}/status/resume',
2273 description
=> "Resume virtual machine.",
2275 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2278 additionalProperties
=> 0,
2280 node
=> get_standard_option
('pve-node'),
2281 vmid
=> get_standard_option
('pve-vmid',
2282 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2283 skiplock
=> get_standard_option
('skiplock'),
2284 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2294 my $rpcenv = PVE
::RPCEnvironment
::get
();
2296 my $authuser = $rpcenv->get_user();
2298 my $node = extract_param
($param, 'node');
2300 my $vmid = extract_param
($param, 'vmid');
2302 my $skiplock = extract_param
($param, 'skiplock');
2303 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2304 if $skiplock && $authuser ne 'root@pam';
2306 my $nocheck = extract_param
($param, 'nocheck');
2308 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2313 syslog
('info', "resume VM $vmid: $upid\n");
2315 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2320 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2323 __PACKAGE__-
>register_method({
2324 name
=> 'vm_sendkey',
2325 path
=> '{vmid}/sendkey',
2329 description
=> "Send key event to virtual machine.",
2331 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2334 additionalProperties
=> 0,
2336 node
=> get_standard_option
('pve-node'),
2337 vmid
=> get_standard_option
('pve-vmid',
2338 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2339 skiplock
=> get_standard_option
('skiplock'),
2341 description
=> "The key (qemu monitor encoding).",
2346 returns
=> { type
=> 'null'},
2350 my $rpcenv = PVE
::RPCEnvironment
::get
();
2352 my $authuser = $rpcenv->get_user();
2354 my $node = extract_param
($param, 'node');
2356 my $vmid = extract_param
($param, 'vmid');
2358 my $skiplock = extract_param
($param, 'skiplock');
2359 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2360 if $skiplock && $authuser ne 'root@pam';
2362 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2367 __PACKAGE__-
>register_method({
2368 name
=> 'vm_feature',
2369 path
=> '{vmid}/feature',
2373 description
=> "Check if feature for virtual machine is available.",
2375 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2378 additionalProperties
=> 0,
2380 node
=> get_standard_option
('pve-node'),
2381 vmid
=> get_standard_option
('pve-vmid'),
2383 description
=> "Feature to check.",
2385 enum
=> [ 'snapshot', 'clone', 'copy' ],
2387 snapname
=> get_standard_option
('pve-snapshot-name', {
2395 hasFeature
=> { type
=> 'boolean' },
2398 items
=> { type
=> 'string' },
2405 my $node = extract_param
($param, 'node');
2407 my $vmid = extract_param
($param, 'vmid');
2409 my $snapname = extract_param
($param, 'snapname');
2411 my $feature = extract_param
($param, 'feature');
2413 my $running = PVE
::QemuServer
::check_running
($vmid);
2415 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2418 my $snap = $conf->{snapshots
}->{$snapname};
2419 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2422 my $storecfg = PVE
::Storage
::config
();
2424 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2425 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2428 hasFeature
=> $hasFeature,
2429 nodes
=> [ keys %$nodelist ],
2433 __PACKAGE__-
>register_method({
2435 path
=> '{vmid}/clone',
2439 description
=> "Create a copy of virtual machine/template.",
2441 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2442 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2443 "'Datastore.AllocateSpace' on any used storage.",
2446 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2448 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2449 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2454 additionalProperties
=> 0,
2456 node
=> get_standard_option
('pve-node'),
2457 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2458 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2461 type
=> 'string', format
=> 'dns-name',
2462 description
=> "Set a name for the new VM.",
2467 description
=> "Description for the new VM.",
2471 type
=> 'string', format
=> 'pve-poolid',
2472 description
=> "Add the new VM to the specified pool.",
2474 snapname
=> get_standard_option
('pve-snapshot-name', {
2477 storage
=> get_standard_option
('pve-storage-id', {
2478 description
=> "Target storage for full clone.",
2483 description
=> "Target format for file storage.",
2487 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2492 description
=> "Create a full copy of all disk. This is always done when " .
2493 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2496 target
=> get_standard_option
('pve-node', {
2497 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2508 my $rpcenv = PVE
::RPCEnvironment
::get
();
2510 my $authuser = $rpcenv->get_user();
2512 my $node = extract_param
($param, 'node');
2514 my $vmid = extract_param
($param, 'vmid');
2516 my $newid = extract_param
($param, 'newid');
2518 my $pool = extract_param
($param, 'pool');
2520 if (defined($pool)) {
2521 $rpcenv->check_pool_exist($pool);
2524 my $snapname = extract_param
($param, 'snapname');
2526 my $storage = extract_param
($param, 'storage');
2528 my $format = extract_param
($param, 'format');
2530 my $target = extract_param
($param, 'target');
2532 my $localnode = PVE
::INotify
::nodename
();
2534 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2536 PVE
::Cluster
::check_node_exists
($target) if $target;
2538 my $storecfg = PVE
::Storage
::config
();
2541 # check if storage is enabled on local node
2542 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2544 # check if storage is available on target node
2545 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2546 # clone only works if target storage is shared
2547 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2548 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2552 PVE
::Cluster
::check_cfs_quorum
();
2554 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2556 # exclusive lock if VM is running - else shared lock is enough;
2557 my $shared_lock = $running ?
0 : 1;
2561 # do all tests after lock
2562 # we also try to do all tests before we fork the worker
2564 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2566 PVE
::QemuConfig-
>check_lock($conf);
2568 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2570 die "unexpected state change\n" if $verify_running != $running;
2572 die "snapshot '$snapname' does not exist\n"
2573 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2575 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2577 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2579 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2581 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2583 die "unable to create VM $newid: config file already exists\n"
2586 my $newconf = { lock => 'clone' };
2591 foreach my $opt (keys %$oldconf) {
2592 my $value = $oldconf->{$opt};
2594 # do not copy snapshot related info
2595 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2596 $opt eq 'vmstate' || $opt eq 'snapstate';
2598 # no need to copy unused images, because VMID(owner) changes anyways
2599 next if $opt =~ m/^unused\d+$/;
2601 # always change MAC! address
2602 if ($opt =~ m/^net(\d+)$/) {
2603 my $net = PVE
::QemuServer
::parse_net
($value);
2604 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2605 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2606 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2607 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2608 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2609 die "unable to parse drive options for '$opt'\n" if !$drive;
2610 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2611 $newconf->{$opt} = $value; # simply copy configuration
2613 if ($param->{full
} || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2614 die "Full clone feature is not supported for drive '$opt'\n"
2615 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2616 $fullclone->{$opt} = 1;
2618 # not full means clone instead of copy
2619 die "Linked clone feature is not supported for drive '$opt'\n"
2620 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2622 $drives->{$opt} = $drive;
2623 push @$vollist, $drive->{file
};
2626 # copy everything else
2627 $newconf->{$opt} = $value;
2631 # auto generate a new uuid
2632 my ($uuid, $uuid_str);
2633 UUID
::generate
($uuid);
2634 UUID
::unparse
($uuid, $uuid_str);
2635 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2636 $smbios1->{uuid
} = $uuid_str;
2637 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2639 delete $newconf->{template
};
2641 if ($param->{name
}) {
2642 $newconf->{name
} = $param->{name
};
2644 if ($oldconf->{name
}) {
2645 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2647 $newconf->{name
} = "Copy-of-VM-$vmid";
2651 if ($param->{description
}) {
2652 $newconf->{description
} = $param->{description
};
2655 # create empty/temp config - this fails if VM already exists on other node
2656 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2661 my $newvollist = [];
2668 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2670 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2672 my $total_jobs = scalar(keys %{$drives});
2675 foreach my $opt (keys %$drives) {
2676 my $drive = $drives->{$opt};
2677 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2679 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2680 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2681 $jobs, $skipcomplete, $oldconf->{agent
});
2683 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2685 PVE
::QemuConfig-
>write_config($newid, $newconf);
2689 delete $newconf->{lock};
2690 PVE
::QemuConfig-
>write_config($newid, $newconf);
2693 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2694 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2695 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2697 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2698 die "Failed to move config to node '$target' - rename failed: $!\n"
2699 if !rename($conffile, $newconffile);
2702 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2707 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2709 sleep 1; # some storage like rbd need to wait before release volume - really?
2711 foreach my $volid (@$newvollist) {
2712 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2715 die "clone failed: $err";
2721 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2723 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2726 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2727 # Aquire exclusive lock lock for $newid
2728 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2733 __PACKAGE__-
>register_method({
2734 name
=> 'move_vm_disk',
2735 path
=> '{vmid}/move_disk',
2739 description
=> "Move volume to different storage.",
2741 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2743 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2744 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2748 additionalProperties
=> 0,
2750 node
=> get_standard_option
('pve-node'),
2751 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2754 description
=> "The disk you want to move.",
2755 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2757 storage
=> get_standard_option
('pve-storage-id', {
2758 description
=> "Target storage.",
2759 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2763 description
=> "Target Format.",
2764 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2769 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2775 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2783 description
=> "the task ID.",
2788 my $rpcenv = PVE
::RPCEnvironment
::get
();
2790 my $authuser = $rpcenv->get_user();
2792 my $node = extract_param
($param, 'node');
2794 my $vmid = extract_param
($param, 'vmid');
2796 my $digest = extract_param
($param, 'digest');
2798 my $disk = extract_param
($param, 'disk');
2800 my $storeid = extract_param
($param, 'storage');
2802 my $format = extract_param
($param, 'format');
2804 my $storecfg = PVE
::Storage
::config
();
2806 my $updatefn = sub {
2808 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2810 PVE
::QemuConfig-
>check_lock($conf);
2812 die "checksum missmatch (file change by other user?)\n"
2813 if $digest && $digest ne $conf->{digest
};
2815 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2817 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2819 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2821 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2824 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2825 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2829 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2830 (!$format || !$oldfmt || $oldfmt eq $format);
2832 # this only checks snapshots because $disk is passed!
2833 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2834 die "you can't move a disk with snapshots and delete the source\n"
2835 if $snapshotted && $param->{delete};
2837 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2839 my $running = PVE
::QemuServer
::check_running
($vmid);
2841 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2845 my $newvollist = [];
2851 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2853 warn "moving disk with snapshots, snapshots will not be moved!\n"
2856 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2857 $vmid, $storeid, $format, 1, $newvollist);
2859 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2861 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2863 # convert moved disk to base if part of template
2864 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2865 if PVE
::QemuConfig-
>is_template($conf);
2867 PVE
::QemuConfig-
>write_config($vmid, $conf);
2870 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2871 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2878 foreach my $volid (@$newvollist) {
2879 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2882 die "storage migration failed: $err";
2885 if ($param->{delete}) {
2887 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2888 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2894 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2897 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2900 __PACKAGE__-
>register_method({
2901 name
=> 'migrate_vm',
2902 path
=> '{vmid}/migrate',
2906 description
=> "Migrate virtual machine. Creates a new migration task.",
2908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2911 additionalProperties
=> 0,
2913 node
=> get_standard_option
('pve-node'),
2914 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2915 target
=> get_standard_option
('pve-node', {
2916 description
=> "Target node.",
2917 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2921 description
=> "Use online/live migration.",
2926 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2931 enum
=> ['secure', 'insecure'],
2932 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2935 migration_network
=> {
2936 type
=> 'string', format
=> 'CIDR',
2937 description
=> "CIDR of the (sub) network that is used for migration.",
2940 "with-local-disks" => {
2942 description
=> "Enable live storage migration for local disk",
2945 targetstorage
=> get_standard_option
('pve-storage-id', {
2946 description
=> "Default target storage.",
2948 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2954 description
=> "the task ID.",
2959 my $rpcenv = PVE
::RPCEnvironment
::get
();
2961 my $authuser = $rpcenv->get_user();
2963 my $target = extract_param
($param, 'target');
2965 my $localnode = PVE
::INotify
::nodename
();
2966 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2968 PVE
::Cluster
::check_cfs_quorum
();
2970 PVE
::Cluster
::check_node_exists
($target);
2972 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2974 my $vmid = extract_param
($param, 'vmid');
2976 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2977 if !$param->{online
} && $param->{targetstorage
};
2979 raise_param_exc
({ force
=> "Only root may use this option." })
2980 if $param->{force
} && $authuser ne 'root@pam';
2982 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2983 if $param->{migration_type
} && $authuser ne 'root@pam';
2985 # allow root only until better network permissions are available
2986 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2987 if $param->{migration_network
} && $authuser ne 'root@pam';
2990 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2992 # try to detect errors early
2994 PVE
::QemuConfig-
>check_lock($conf);
2996 if (PVE
::QemuServer
::check_running
($vmid)) {
2997 die "cant migrate running VM without --online\n"
2998 if !$param->{online
};
3001 my $storecfg = PVE
::Storage
::config
();
3003 if( $param->{targetstorage
}) {
3004 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3006 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3009 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3014 my $service = "vm:$vmid";
3016 my $cmd = ['ha-manager', 'migrate', $service, $target];
3018 print "Requesting HA migration for VM $vmid to node $target\n";
3020 PVE
::Tools
::run_command
($cmd);
3025 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3030 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3034 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3037 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3042 __PACKAGE__-
>register_method({
3044 path
=> '{vmid}/monitor',
3048 description
=> "Execute Qemu monitor commands.",
3050 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3051 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3054 additionalProperties
=> 0,
3056 node
=> get_standard_option
('pve-node'),
3057 vmid
=> get_standard_option
('pve-vmid'),
3060 description
=> "The monitor command.",
3064 returns
=> { type
=> 'string'},
3068 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3072 my $command = shift;
3073 return $command =~ m/^\s*info(\s+|$)/
3074 || $command =~ m/^\s*help\s*$/;
3077 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3078 if !&$is_ro($param->{command
});
3080 my $vmid = $param->{vmid
};
3082 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3086 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3088 $res = "ERROR: $@" if $@;
3093 __PACKAGE__-
>register_method({
3094 name
=> 'resize_vm',
3095 path
=> '{vmid}/resize',
3099 description
=> "Extend volume size.",
3101 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3104 additionalProperties
=> 0,
3106 node
=> get_standard_option
('pve-node'),
3107 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3108 skiplock
=> get_standard_option
('skiplock'),
3111 description
=> "The disk you want to resize.",
3112 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3116 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3117 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.",
3121 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3127 returns
=> { type
=> 'null'},
3131 my $rpcenv = PVE
::RPCEnvironment
::get
();
3133 my $authuser = $rpcenv->get_user();
3135 my $node = extract_param
($param, 'node');
3137 my $vmid = extract_param
($param, 'vmid');
3139 my $digest = extract_param
($param, 'digest');
3141 my $disk = extract_param
($param, 'disk');
3143 my $sizestr = extract_param
($param, 'size');
3145 my $skiplock = extract_param
($param, 'skiplock');
3146 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3147 if $skiplock && $authuser ne 'root@pam';
3149 my $storecfg = PVE
::Storage
::config
();
3151 my $updatefn = sub {
3153 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3155 die "checksum missmatch (file change by other user?)\n"
3156 if $digest && $digest ne $conf->{digest
};
3157 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3159 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3161 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3163 my (undef, undef, undef, undef, undef, undef, $format) =
3164 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3166 die "can't resize volume: $disk if snapshot exists\n"
3167 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3169 my $volid = $drive->{file
};
3171 die "disk '$disk' has no associated volume\n" if !$volid;
3173 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3175 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3177 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3179 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3180 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3182 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3183 my ($ext, $newsize, $unit) = ($1, $2, $4);
3186 $newsize = $newsize * 1024;
3187 } elsif ($unit eq 'M') {
3188 $newsize = $newsize * 1024 * 1024;
3189 } elsif ($unit eq 'G') {
3190 $newsize = $newsize * 1024 * 1024 * 1024;
3191 } elsif ($unit eq 'T') {
3192 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3195 $newsize += $size if $ext;
3196 $newsize = int($newsize);
3198 die "shrinking disks is not supported\n" if $newsize < $size;
3200 return if $size == $newsize;
3202 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3204 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3206 $drive->{size
} = $newsize;
3207 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3209 PVE
::QemuConfig-
>write_config($vmid, $conf);
3212 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3216 __PACKAGE__-
>register_method({
3217 name
=> 'snapshot_list',
3218 path
=> '{vmid}/snapshot',
3220 description
=> "List all snapshots.",
3222 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3225 protected
=> 1, # qemu pid files are only readable by root
3227 additionalProperties
=> 0,
3229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3230 node
=> get_standard_option
('pve-node'),
3239 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3244 my $vmid = $param->{vmid
};
3246 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3247 my $snaphash = $conf->{snapshots
} || {};
3251 foreach my $name (keys %$snaphash) {
3252 my $d = $snaphash->{$name};
3255 snaptime
=> $d->{snaptime
} || 0,
3256 vmstate
=> $d->{vmstate
} ?
1 : 0,
3257 description
=> $d->{description
} || '',
3259 $item->{parent
} = $d->{parent
} if $d->{parent
};
3260 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3264 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3265 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3266 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3268 push @$res, $current;
3273 __PACKAGE__-
>register_method({
3275 path
=> '{vmid}/snapshot',
3279 description
=> "Snapshot a VM.",
3281 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3284 additionalProperties
=> 0,
3286 node
=> get_standard_option
('pve-node'),
3287 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3288 snapname
=> get_standard_option
('pve-snapshot-name'),
3292 description
=> "Save the vmstate",
3297 description
=> "A textual description or comment.",
3303 description
=> "the task ID.",
3308 my $rpcenv = PVE
::RPCEnvironment
::get
();
3310 my $authuser = $rpcenv->get_user();
3312 my $node = extract_param
($param, 'node');
3314 my $vmid = extract_param
($param, 'vmid');
3316 my $snapname = extract_param
($param, 'snapname');
3318 die "unable to use snapshot name 'current' (reserved name)\n"
3319 if $snapname eq 'current';
3322 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3323 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3324 $param->{description
});
3327 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3330 __PACKAGE__-
>register_method({
3331 name
=> 'snapshot_cmd_idx',
3332 path
=> '{vmid}/snapshot/{snapname}',
3339 additionalProperties
=> 0,
3341 vmid
=> get_standard_option
('pve-vmid'),
3342 node
=> get_standard_option
('pve-node'),
3343 snapname
=> get_standard_option
('pve-snapshot-name'),
3352 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3359 push @$res, { cmd
=> 'rollback' };
3360 push @$res, { cmd
=> 'config' };
3365 __PACKAGE__-
>register_method({
3366 name
=> 'update_snapshot_config',
3367 path
=> '{vmid}/snapshot/{snapname}/config',
3371 description
=> "Update snapshot metadata.",
3373 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3376 additionalProperties
=> 0,
3378 node
=> get_standard_option
('pve-node'),
3379 vmid
=> get_standard_option
('pve-vmid'),
3380 snapname
=> get_standard_option
('pve-snapshot-name'),
3384 description
=> "A textual description or comment.",
3388 returns
=> { type
=> 'null' },
3392 my $rpcenv = PVE
::RPCEnvironment
::get
();
3394 my $authuser = $rpcenv->get_user();
3396 my $vmid = extract_param
($param, 'vmid');
3398 my $snapname = extract_param
($param, 'snapname');
3400 return undef if !defined($param->{description
});
3402 my $updatefn = sub {
3404 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3406 PVE
::QemuConfig-
>check_lock($conf);
3408 my $snap = $conf->{snapshots
}->{$snapname};
3410 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3412 $snap->{description
} = $param->{description
} if defined($param->{description
});
3414 PVE
::QemuConfig-
>write_config($vmid, $conf);
3417 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3422 __PACKAGE__-
>register_method({
3423 name
=> 'get_snapshot_config',
3424 path
=> '{vmid}/snapshot/{snapname}/config',
3427 description
=> "Get snapshot configuration",
3429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3432 additionalProperties
=> 0,
3434 node
=> get_standard_option
('pve-node'),
3435 vmid
=> get_standard_option
('pve-vmid'),
3436 snapname
=> get_standard_option
('pve-snapshot-name'),
3439 returns
=> { type
=> "object" },
3443 my $rpcenv = PVE
::RPCEnvironment
::get
();
3445 my $authuser = $rpcenv->get_user();
3447 my $vmid = extract_param
($param, 'vmid');
3449 my $snapname = extract_param
($param, 'snapname');
3451 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3453 my $snap = $conf->{snapshots
}->{$snapname};
3455 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3460 __PACKAGE__-
>register_method({
3462 path
=> '{vmid}/snapshot/{snapname}/rollback',
3466 description
=> "Rollback VM state to specified snapshot.",
3468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3471 additionalProperties
=> 0,
3473 node
=> get_standard_option
('pve-node'),
3474 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3475 snapname
=> get_standard_option
('pve-snapshot-name'),
3480 description
=> "the task ID.",
3485 my $rpcenv = PVE
::RPCEnvironment
::get
();
3487 my $authuser = $rpcenv->get_user();
3489 my $node = extract_param
($param, 'node');
3491 my $vmid = extract_param
($param, 'vmid');
3493 my $snapname = extract_param
($param, 'snapname');
3496 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3497 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3501 # hold migration lock, this makes sure that nobody create replication snapshots
3502 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3505 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3508 __PACKAGE__-
>register_method({
3509 name
=> 'delsnapshot',
3510 path
=> '{vmid}/snapshot/{snapname}',
3514 description
=> "Delete a VM snapshot.",
3516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3519 additionalProperties
=> 0,
3521 node
=> get_standard_option
('pve-node'),
3522 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3523 snapname
=> get_standard_option
('pve-snapshot-name'),
3527 description
=> "For removal from config file, even if removing disk snapshots fails.",
3533 description
=> "the task ID.",
3538 my $rpcenv = PVE
::RPCEnvironment
::get
();
3540 my $authuser = $rpcenv->get_user();
3542 my $node = extract_param
($param, 'node');
3544 my $vmid = extract_param
($param, 'vmid');
3546 my $snapname = extract_param
($param, 'snapname');
3549 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3550 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3553 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3556 __PACKAGE__-
>register_method({
3558 path
=> '{vmid}/template',
3562 description
=> "Create a Template.",
3564 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3565 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3568 additionalProperties
=> 0,
3570 node
=> get_standard_option
('pve-node'),
3571 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3575 description
=> "If you want to convert only 1 disk to base image.",
3576 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3581 returns
=> { type
=> 'null'},
3585 my $rpcenv = PVE
::RPCEnvironment
::get
();
3587 my $authuser = $rpcenv->get_user();
3589 my $node = extract_param
($param, 'node');
3591 my $vmid = extract_param
($param, 'vmid');
3593 my $disk = extract_param
($param, 'disk');
3595 my $updatefn = sub {
3597 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3599 PVE
::QemuConfig-
>check_lock($conf);
3601 die "unable to create template, because VM contains snapshots\n"
3602 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3604 die "you can't convert a template to a template\n"
3605 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3607 die "you can't convert a VM to template if VM is running\n"
3608 if PVE
::QemuServer
::check_running
($vmid);
3611 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3614 $conf->{template
} = 1;
3615 PVE
::QemuConfig-
>write_config($vmid, $conf);
3617 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3620 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);