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.",
434 description
=> "Override i/o bandwidth limit (in KiB/s).",
447 my $rpcenv = PVE
::RPCEnvironment
::get
();
449 my $authuser = $rpcenv->get_user();
451 my $node = extract_param
($param, 'node');
453 my $vmid = extract_param
($param, 'vmid');
455 my $archive = extract_param
($param, 'archive');
457 my $storage = extract_param
($param, 'storage');
459 my $force = extract_param
($param, 'force');
461 my $unique = extract_param
($param, 'unique');
463 my $pool = extract_param
($param, 'pool');
465 my $bwlimit = extract_param
($param, 'bwlimit');
467 my $filename = PVE
::QemuConfig-
>config_file($vmid);
469 my $storecfg = PVE
::Storage
::config
();
471 if (defined(my $ssh_keys = $param->{sshkeys
})) {
472 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
473 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
476 PVE
::Cluster
::check_cfs_quorum
();
478 if (defined($pool)) {
479 $rpcenv->check_pool_exist($pool);
482 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
483 if defined($storage);
485 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
487 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
489 } elsif ($archive && $force && (-f
$filename) &&
490 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
491 # OK: user has VM.Backup permissions, and want to restore an existing VM
497 &$resolve_cdrom_alias($param);
499 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
501 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
503 foreach my $opt (keys %$param) {
504 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
505 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
506 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
508 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
509 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
513 PVE
::QemuServer
::add_random_macs
($param);
515 my $keystr = join(' ', keys %$param);
516 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
518 if ($archive eq '-') {
519 die "pipe requires cli environment\n"
520 if $rpcenv->{type
} ne 'cli';
522 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
523 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
527 my $restorefn = sub {
528 my $vmlist = PVE
::Cluster
::get_vmlist
();
529 if ($vmlist->{ids
}->{$vmid}) {
530 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
531 if ($current_node eq $node) {
532 my $conf = PVE
::QemuConfig-
>load_config($vmid);
534 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
536 die "unable to restore vm $vmid - config file already exists\n"
539 die "unable to restore vm $vmid - vm is running\n"
540 if PVE
::QemuServer
::check_running
($vmid);
542 die "unable to restore vm $vmid - vm is a template\n"
543 if PVE
::QemuConfig-
>is_template($conf);
546 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
551 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
555 bwlimit
=> $bwlimit, });
557 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
560 # ensure no old replication state are exists
561 PVE
::ReplicationState
::delete_guest_states
($vmid);
563 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
569 PVE
::Cluster
::check_vmid_unused
($vmid);
571 # ensure no old replication state are exists
572 PVE
::ReplicationState
::delete_guest_states
($vmid);
582 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
584 if (!$conf->{bootdisk
}) {
585 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
586 $conf->{bootdisk
} = $firstdisk if $firstdisk;
589 # auto generate uuid if user did not specify smbios1 option
590 if (!$conf->{smbios1
}) {
591 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
594 PVE
::QemuConfig-
>write_config($vmid, $conf);
600 foreach my $volid (@$vollist) {
601 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
604 die "create failed - $err";
607 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
610 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
613 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
616 __PACKAGE__-
>register_method({
621 description
=> "Directory index",
626 additionalProperties
=> 0,
628 node
=> get_standard_option
('pve-node'),
629 vmid
=> get_standard_option
('pve-vmid'),
637 subdir
=> { type
=> 'string' },
640 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
646 { subdir
=> 'config' },
647 { subdir
=> 'pending' },
648 { subdir
=> 'status' },
649 { subdir
=> 'unlink' },
650 { subdir
=> 'vncproxy' },
651 { subdir
=> 'termproxy' },
652 { subdir
=> 'migrate' },
653 { subdir
=> 'resize' },
654 { subdir
=> 'move' },
656 { subdir
=> 'rrddata' },
657 { subdir
=> 'monitor' },
658 { subdir
=> 'agent' },
659 { subdir
=> 'snapshot' },
660 { subdir
=> 'spiceproxy' },
661 { subdir
=> 'sendkey' },
662 { subdir
=> 'firewall' },
668 __PACKAGE__-
>register_method ({
669 subclass
=> "PVE::API2::Firewall::VM",
670 path
=> '{vmid}/firewall',
673 __PACKAGE__-
>register_method ({
674 subclass
=> "PVE::API2::Qemu::Agent",
675 path
=> '{vmid}/agent',
678 __PACKAGE__-
>register_method({
680 path
=> '{vmid}/rrd',
682 protected
=> 1, # fixme: can we avoid that?
684 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
686 description
=> "Read VM RRD statistics (returns PNG)",
688 additionalProperties
=> 0,
690 node
=> get_standard_option
('pve-node'),
691 vmid
=> get_standard_option
('pve-vmid'),
693 description
=> "Specify the time frame you are interested in.",
695 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
698 description
=> "The list of datasources you want to display.",
699 type
=> 'string', format
=> 'pve-configid-list',
702 description
=> "The RRD consolidation function",
704 enum
=> [ 'AVERAGE', 'MAX' ],
712 filename
=> { type
=> 'string' },
718 return PVE
::Cluster
::create_rrd_graph
(
719 "pve2-vm/$param->{vmid}", $param->{timeframe
},
720 $param->{ds
}, $param->{cf
});
724 __PACKAGE__-
>register_method({
726 path
=> '{vmid}/rrddata',
728 protected
=> 1, # fixme: can we avoid that?
730 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
732 description
=> "Read VM RRD statistics",
734 additionalProperties
=> 0,
736 node
=> get_standard_option
('pve-node'),
737 vmid
=> get_standard_option
('pve-vmid'),
739 description
=> "Specify the time frame you are interested in.",
741 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
744 description
=> "The RRD consolidation function",
746 enum
=> [ 'AVERAGE', 'MAX' ],
761 return PVE
::Cluster
::create_rrd_data
(
762 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
766 __PACKAGE__-
>register_method({
768 path
=> '{vmid}/config',
771 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
776 additionalProperties
=> 0,
778 node
=> get_standard_option
('pve-node'),
779 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
781 description
=> "Get current values (instead of pending values).",
793 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
800 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
802 delete $conf->{snapshots
};
804 if (!$param->{current
}) {
805 foreach my $opt (keys %{$conf->{pending
}}) {
806 next if $opt eq 'delete';
807 my $value = $conf->{pending
}->{$opt};
808 next if ref($value); # just to be sure
809 $conf->{$opt} = $value;
811 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
812 foreach my $opt (keys %$pending_delete_hash) {
813 delete $conf->{$opt} if $conf->{$opt};
817 delete $conf->{pending
};
819 # hide cloudinit password
820 if ($conf->{cipassword
}) {
821 $conf->{cipassword
} = '**********';
827 __PACKAGE__-
>register_method({
828 name
=> 'vm_pending',
829 path
=> '{vmid}/pending',
832 description
=> "Get virtual machine configuration, including pending changes.",
834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
837 additionalProperties
=> 0,
839 node
=> get_standard_option
('pve-node'),
840 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
849 description
=> "Configuration option name.",
853 description
=> "Current value.",
858 description
=> "Pending value.",
863 description
=> "Indicates a pending delete request if present and not 0. " .
864 "The value 2 indicates a force-delete request.",
876 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
878 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
882 foreach my $opt (keys %$conf) {
883 next if ref($conf->{$opt});
884 my $item = { key
=> $opt };
885 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
886 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
887 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
889 # hide cloudinit password
890 if ($opt eq 'cipassword') {
891 $item->{value
} = '**********' if defined($item->{value
});
892 # the trailing space so that the pending string is different
893 $item->{pending
} = '********** ' if defined($item->{pending
});
898 foreach my $opt (keys %{$conf->{pending
}}) {
899 next if $opt eq 'delete';
900 next if ref($conf->{pending
}->{$opt}); # just to be sure
901 next if defined($conf->{$opt});
902 my $item = { key
=> $opt };
903 $item->{pending
} = $conf->{pending
}->{$opt};
905 # hide cloudinit password
906 if ($opt eq 'cipassword') {
907 $item->{pending
} = '**********' if defined($item->{pending
});
912 while (my ($opt, $force) = each %$pending_delete_hash) {
913 next if $conf->{pending
}->{$opt}; # just to be sure
914 next if $conf->{$opt};
915 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
922 # POST/PUT {vmid}/config implementation
924 # The original API used PUT (idempotent) an we assumed that all operations
925 # are fast. But it turned out that almost any configuration change can
926 # involve hot-plug actions, or disk alloc/free. Such actions can take long
927 # time to complete and have side effects (not idempotent).
929 # The new implementation uses POST and forks a worker process. We added
930 # a new option 'background_delay'. If specified we wait up to
931 # 'background_delay' second for the worker task to complete. It returns null
932 # if the task is finished within that time, else we return the UPID.
934 my $update_vm_api = sub {
935 my ($param, $sync) = @_;
937 my $rpcenv = PVE
::RPCEnvironment
::get
();
939 my $authuser = $rpcenv->get_user();
941 my $node = extract_param
($param, 'node');
943 my $vmid = extract_param
($param, 'vmid');
945 my $digest = extract_param
($param, 'digest');
947 my $background_delay = extract_param
($param, 'background_delay');
949 if (defined(my $cipassword = $param->{cipassword
})) {
950 # Same logic as in cloud-init (but with the regex fixed...)
951 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
952 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
955 my @paramarr = (); # used for log message
956 foreach my $key (sort keys %$param) {
957 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
958 push @paramarr, "-$key", $value;
961 my $skiplock = extract_param
($param, 'skiplock');
962 raise_param_exc
({ skiplock
=> "Only root may use this option." })
963 if $skiplock && $authuser ne 'root@pam';
965 my $delete_str = extract_param
($param, 'delete');
967 my $revert_str = extract_param
($param, 'revert');
969 my $force = extract_param
($param, 'force');
971 if (defined(my $ssh_keys = $param->{sshkeys
})) {
972 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
973 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
976 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
978 my $storecfg = PVE
::Storage
::config
();
980 my $defaults = PVE
::QemuServer
::load_defaults
();
982 &$resolve_cdrom_alias($param);
984 # now try to verify all parameters
987 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
988 if (!PVE
::QemuServer
::option_exists
($opt)) {
989 raise_param_exc
({ revert
=> "unknown option '$opt'" });
992 raise_param_exc
({ delete => "you can't use '-$opt' and " .
993 "-revert $opt' at the same time" })
994 if defined($param->{$opt});
1000 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1001 $opt = 'ide2' if $opt eq 'cdrom';
1003 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1004 "-delete $opt' at the same time" })
1005 if defined($param->{$opt});
1007 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1008 "-revert $opt' at the same time" })
1011 if (!PVE
::QemuServer
::option_exists
($opt)) {
1012 raise_param_exc
({ delete => "unknown option '$opt'" });
1018 my $repl_conf = PVE
::ReplicationConfig-
>new();
1019 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1020 my $check_replication = sub {
1022 return if !$is_replicated;
1023 my $volid = $drive->{file
};
1024 return if !$volid || !($drive->{replicate
}//1);
1025 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1026 my ($storeid, $format);
1027 if ($volid =~ $NEW_DISK_RE) {
1029 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1031 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1032 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1034 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1035 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1036 return if $scfg->{shared
};
1037 die "cannot add non-replicatable volume to a replicated VM\n";
1040 foreach my $opt (keys %$param) {
1041 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1042 # cleanup drive path
1043 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1044 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1045 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1046 $check_replication->($drive);
1047 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1048 } elsif ($opt =~ m/^net(\d+)$/) {
1050 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1051 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1055 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1057 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1059 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1061 my $updatefn = sub {
1063 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1065 die "checksum missmatch (file change by other user?)\n"
1066 if $digest && $digest ne $conf->{digest
};
1068 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1070 foreach my $opt (keys %$revert) {
1071 if (defined($conf->{$opt})) {
1072 $param->{$opt} = $conf->{$opt};
1073 } elsif (defined($conf->{pending
}->{$opt})) {
1078 if ($param->{memory
} || defined($param->{balloon
})) {
1079 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1080 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1082 die "balloon value too large (must be smaller than assigned memory)\n"
1083 if $balloon && $balloon > $maxmem;
1086 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1090 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1092 # write updates to pending section
1094 my $modified = {}; # record what $option we modify
1096 foreach my $opt (@delete) {
1097 $modified->{$opt} = 1;
1098 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1099 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1100 warn "cannot delete '$opt' - not set in current configuration!\n";
1101 $modified->{$opt} = 0;
1105 if ($opt =~ m/^unused/) {
1106 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1107 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1108 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1109 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1110 delete $conf->{$opt};
1111 PVE
::QemuConfig-
>write_config($vmid, $conf);
1113 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1114 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1115 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1116 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1117 if defined($conf->{pending
}->{$opt});
1118 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1119 PVE
::QemuConfig-
>write_config($vmid, $conf);
1121 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1122 PVE
::QemuConfig-
>write_config($vmid, $conf);
1126 foreach my $opt (keys %$param) { # add/change
1127 $modified->{$opt} = 1;
1128 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1129 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1131 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1132 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1133 # FIXME: cloudinit: CDROM or Disk?
1134 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1135 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1137 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1139 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1140 if defined($conf->{pending
}->{$opt});
1142 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1144 $conf->{pending
}->{$opt} = $param->{$opt};
1146 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1147 PVE
::QemuConfig-
>write_config($vmid, $conf);
1150 # remove pending changes when nothing changed
1151 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1152 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1153 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1155 return if !scalar(keys %{$conf->{pending
}});
1157 my $running = PVE
::QemuServer
::check_running
($vmid);
1159 # apply pending changes
1161 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1165 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1166 raise_param_exc
($errors) if scalar(keys %$errors);
1168 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1178 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1180 if ($background_delay) {
1182 # Note: It would be better to do that in the Event based HTTPServer
1183 # to avoid blocking call to sleep.
1185 my $end_time = time() + $background_delay;
1187 my $task = PVE
::Tools
::upid_decode
($upid);
1190 while (time() < $end_time) {
1191 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1193 sleep(1); # this gets interrupted when child process ends
1197 my $status = PVE
::Tools
::upid_read_status
($upid);
1198 return undef if $status eq 'OK';
1207 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1210 my $vm_config_perm_list = [
1215 'VM.Config.Network',
1217 'VM.Config.Options',
1220 __PACKAGE__-
>register_method({
1221 name
=> 'update_vm_async',
1222 path
=> '{vmid}/config',
1226 description
=> "Set virtual machine options (asynchrounous API).",
1228 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1231 additionalProperties
=> 0,
1232 properties
=> PVE
::QemuServer
::json_config_properties
(
1234 node
=> get_standard_option
('pve-node'),
1235 vmid
=> get_standard_option
('pve-vmid'),
1236 skiplock
=> get_standard_option
('skiplock'),
1238 type
=> 'string', format
=> 'pve-configid-list',
1239 description
=> "A list of settings you want to delete.",
1243 type
=> 'string', format
=> 'pve-configid-list',
1244 description
=> "Revert a pending change.",
1249 description
=> $opt_force_description,
1251 requires
=> 'delete',
1255 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1259 background_delay
=> {
1261 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1272 code
=> $update_vm_api,
1275 __PACKAGE__-
>register_method({
1276 name
=> 'update_vm',
1277 path
=> '{vmid}/config',
1281 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1283 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1286 additionalProperties
=> 0,
1287 properties
=> PVE
::QemuServer
::json_config_properties
(
1289 node
=> get_standard_option
('pve-node'),
1290 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1291 skiplock
=> get_standard_option
('skiplock'),
1293 type
=> 'string', format
=> 'pve-configid-list',
1294 description
=> "A list of settings you want to delete.",
1298 type
=> 'string', format
=> 'pve-configid-list',
1299 description
=> "Revert a pending change.",
1304 description
=> $opt_force_description,
1306 requires
=> 'delete',
1310 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1316 returns
=> { type
=> 'null' },
1319 &$update_vm_api($param, 1);
1325 __PACKAGE__-
>register_method({
1326 name
=> 'destroy_vm',
1331 description
=> "Destroy the vm (also delete all used/owned volumes).",
1333 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1340 skiplock
=> get_standard_option
('skiplock'),
1349 my $rpcenv = PVE
::RPCEnvironment
::get
();
1351 my $authuser = $rpcenv->get_user();
1353 my $vmid = $param->{vmid
};
1355 my $skiplock = $param->{skiplock
};
1356 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1357 if $skiplock && $authuser ne 'root@pam';
1360 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1362 my $storecfg = PVE
::Storage
::config
();
1364 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1366 die "unable to remove VM $vmid - used in HA resources\n"
1367 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1369 # do not allow destroy if there are replication jobs
1370 my $repl_conf = PVE
::ReplicationConfig-
>new();
1371 $repl_conf->check_for_existing_jobs($vmid);
1373 # early tests (repeat after locking)
1374 die "VM $vmid is running - destroy failed\n"
1375 if PVE
::QemuServer
::check_running
($vmid);
1380 syslog
('info', "destroy VM $vmid: $upid\n");
1382 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1384 PVE
::AccessControl
::remove_vm_access
($vmid);
1386 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1389 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1392 __PACKAGE__-
>register_method({
1394 path
=> '{vmid}/unlink',
1398 description
=> "Unlink/delete disk images.",
1400 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1403 additionalProperties
=> 0,
1405 node
=> get_standard_option
('pve-node'),
1406 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "A list of disk IDs you want to delete.",
1413 description
=> $opt_force_description,
1418 returns
=> { type
=> 'null'},
1422 $param->{delete} = extract_param
($param, 'idlist');
1424 __PACKAGE__-
>update_vm($param);
1431 __PACKAGE__-
>register_method({
1433 path
=> '{vmid}/vncproxy',
1437 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1439 description
=> "Creates a TCP VNC proxy connections.",
1441 additionalProperties
=> 0,
1443 node
=> get_standard_option
('pve-node'),
1444 vmid
=> get_standard_option
('pve-vmid'),
1448 description
=> "starts websockify instead of vncproxy",
1453 additionalProperties
=> 0,
1455 user
=> { type
=> 'string' },
1456 ticket
=> { type
=> 'string' },
1457 cert
=> { type
=> 'string' },
1458 port
=> { type
=> 'integer' },
1459 upid
=> { type
=> 'string' },
1465 my $rpcenv = PVE
::RPCEnvironment
::get
();
1467 my $authuser = $rpcenv->get_user();
1469 my $vmid = $param->{vmid
};
1470 my $node = $param->{node
};
1471 my $websocket = $param->{websocket
};
1473 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1475 my $authpath = "/vms/$vmid";
1477 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1479 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1482 my ($remip, $family);
1485 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1486 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1487 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1488 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1490 $family = PVE
::Tools
::get_host_address_family
($node);
1493 my $port = PVE
::Tools
::next_vnc_port
($family);
1500 syslog
('info', "starting vnc proxy $upid\n");
1504 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1507 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1509 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1510 '-timeout', $timeout, '-authpath', $authpath,
1511 '-perm', 'Sys.Console'];
1513 if ($param->{websocket
}) {
1514 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1515 push @$cmd, '-notls', '-listen', 'localhost';
1518 push @$cmd, '-c', @$remcmd, @$termcmd;
1520 PVE
::Tools
::run_command
($cmd);
1524 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1526 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1528 my $sock = IO
::Socket
::IP-
>new(
1533 GetAddrInfoFlags
=> 0,
1534 ) or die "failed to create socket: $!\n";
1535 # Inside the worker we shouldn't have any previous alarms
1536 # running anyway...:
1538 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1540 accept(my $cli, $sock) or die "connection failed: $!\n";
1543 if (PVE
::Tools
::run_command
($cmd,
1544 output
=> '>&'.fileno($cli),
1545 input
=> '<&'.fileno($cli),
1548 die "Failed to run vncproxy.\n";
1555 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1557 PVE
::Tools
::wait_for_vnc_port
($port);
1568 __PACKAGE__-
>register_method({
1569 name
=> 'termproxy',
1570 path
=> '{vmid}/termproxy',
1574 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1576 description
=> "Creates a TCP proxy connections.",
1578 additionalProperties
=> 0,
1580 node
=> get_standard_option
('pve-node'),
1581 vmid
=> get_standard_option
('pve-vmid'),
1585 enum
=> [qw(serial0 serial1 serial2 serial3)],
1586 description
=> "opens a serial terminal (defaults to display)",
1591 additionalProperties
=> 0,
1593 user
=> { type
=> 'string' },
1594 ticket
=> { type
=> 'string' },
1595 port
=> { type
=> 'integer' },
1596 upid
=> { type
=> 'string' },
1602 my $rpcenv = PVE
::RPCEnvironment
::get
();
1604 my $authuser = $rpcenv->get_user();
1606 my $vmid = $param->{vmid
};
1607 my $node = $param->{node
};
1608 my $serial = $param->{serial
};
1610 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1612 if (!defined($serial)) {
1613 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1614 $serial = $conf->{vga
};
1618 my $authpath = "/vms/$vmid";
1620 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1622 my ($remip, $family);
1624 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1625 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1627 $family = PVE
::Tools
::get_host_address_family
($node);
1630 my $port = PVE
::Tools
::next_vnc_port
($family);
1632 my $remcmd = $remip ?
1633 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1635 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1636 push @$termcmd, '-iface', $serial if $serial;
1641 syslog
('info', "starting qemu termproxy $upid\n");
1643 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1644 '--perm', 'VM.Console', '--'];
1645 push @$cmd, @$remcmd, @$termcmd;
1647 PVE
::Tools
::run_command
($cmd);
1650 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1652 PVE
::Tools
::wait_for_vnc_port
($port);
1662 __PACKAGE__-
>register_method({
1663 name
=> 'vncwebsocket',
1664 path
=> '{vmid}/vncwebsocket',
1667 description
=> "You also need to pass a valid ticket (vncticket).",
1668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1670 description
=> "Opens a weksocket for VNC traffic.",
1672 additionalProperties
=> 0,
1674 node
=> get_standard_option
('pve-node'),
1675 vmid
=> get_standard_option
('pve-vmid'),
1677 description
=> "Ticket from previous call to vncproxy.",
1682 description
=> "Port number returned by previous vncproxy call.",
1692 port
=> { type
=> 'string' },
1698 my $rpcenv = PVE
::RPCEnvironment
::get
();
1700 my $authuser = $rpcenv->get_user();
1702 my $vmid = $param->{vmid
};
1703 my $node = $param->{node
};
1705 my $authpath = "/vms/$vmid";
1707 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1709 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1711 # Note: VNC ports are acessible from outside, so we do not gain any
1712 # security if we verify that $param->{port} belongs to VM $vmid. This
1713 # check is done by verifying the VNC ticket (inside VNC protocol).
1715 my $port = $param->{port
};
1717 return { port
=> $port };
1720 __PACKAGE__-
>register_method({
1721 name
=> 'spiceproxy',
1722 path
=> '{vmid}/spiceproxy',
1727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1729 description
=> "Returns a SPICE configuration to connect to the VM.",
1731 additionalProperties
=> 0,
1733 node
=> get_standard_option
('pve-node'),
1734 vmid
=> get_standard_option
('pve-vmid'),
1735 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1738 returns
=> get_standard_option
('remote-viewer-config'),
1742 my $rpcenv = PVE
::RPCEnvironment
::get
();
1744 my $authuser = $rpcenv->get_user();
1746 my $vmid = $param->{vmid
};
1747 my $node = $param->{node
};
1748 my $proxy = $param->{proxy
};
1750 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1751 my $title = "VM $vmid";
1752 $title .= " - ". $conf->{name
} if $conf->{name
};
1754 my $port = PVE
::QemuServer
::spice_port
($vmid);
1756 my ($ticket, undef, $remote_viewer_config) =
1757 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1759 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1760 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1762 return $remote_viewer_config;
1765 __PACKAGE__-
>register_method({
1767 path
=> '{vmid}/status',
1770 description
=> "Directory index",
1775 additionalProperties
=> 0,
1777 node
=> get_standard_option
('pve-node'),
1778 vmid
=> get_standard_option
('pve-vmid'),
1786 subdir
=> { type
=> 'string' },
1789 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1795 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1798 { subdir
=> 'current' },
1799 { subdir
=> 'start' },
1800 { subdir
=> 'stop' },
1806 __PACKAGE__-
>register_method({
1807 name
=> 'vm_status',
1808 path
=> '{vmid}/status/current',
1811 protected
=> 1, # qemu pid files are only readable by root
1812 description
=> "Get virtual machine status.",
1814 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1823 returns
=> { type
=> 'object' },
1828 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1830 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1831 my $status = $vmstatus->{$param->{vmid
}};
1833 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1835 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1837 $status->{agent
} = 1 if $conf->{agent
};
1842 __PACKAGE__-
>register_method({
1844 path
=> '{vmid}/status/start',
1848 description
=> "Start virtual machine.",
1850 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1853 additionalProperties
=> 0,
1855 node
=> get_standard_option
('pve-node'),
1856 vmid
=> get_standard_option
('pve-vmid',
1857 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1858 skiplock
=> get_standard_option
('skiplock'),
1859 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1860 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1863 enum
=> ['secure', 'insecure'],
1864 description
=> "Migration traffic is encrypted using an SSH " .
1865 "tunnel by default. On secure, completely private networks " .
1866 "this can be disabled to increase performance.",
1869 migration_network
=> {
1870 type
=> 'string', format
=> 'CIDR',
1871 description
=> "CIDR of the (sub) network that is used for migration.",
1874 machine
=> get_standard_option
('pve-qm-machine'),
1876 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1888 my $rpcenv = PVE
::RPCEnvironment
::get
();
1890 my $authuser = $rpcenv->get_user();
1892 my $node = extract_param
($param, 'node');
1894 my $vmid = extract_param
($param, 'vmid');
1896 my $machine = extract_param
($param, 'machine');
1898 my $stateuri = extract_param
($param, 'stateuri');
1899 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1900 if $stateuri && $authuser ne 'root@pam';
1902 my $skiplock = extract_param
($param, 'skiplock');
1903 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1904 if $skiplock && $authuser ne 'root@pam';
1906 my $migratedfrom = extract_param
($param, 'migratedfrom');
1907 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1908 if $migratedfrom && $authuser ne 'root@pam';
1910 my $migration_type = extract_param
($param, 'migration_type');
1911 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1912 if $migration_type && $authuser ne 'root@pam';
1914 my $migration_network = extract_param
($param, 'migration_network');
1915 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1916 if $migration_network && $authuser ne 'root@pam';
1918 my $targetstorage = extract_param
($param, 'targetstorage');
1919 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1920 if $targetstorage && $authuser ne 'root@pam';
1922 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1923 if $targetstorage && !$migratedfrom;
1925 # read spice ticket from STDIN
1927 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1928 if (defined(my $line = <STDIN
>)) {
1930 $spice_ticket = $line;
1934 PVE
::Cluster
::check_cfs_quorum
();
1936 my $storecfg = PVE
::Storage
::config
();
1938 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1939 $rpcenv->{type
} ne 'ha') {
1944 my $service = "vm:$vmid";
1946 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1948 print "Requesting HA start for VM $vmid\n";
1950 PVE
::Tools
::run_command
($cmd);
1955 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1962 syslog
('info', "start VM $vmid: $upid\n");
1964 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1965 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1970 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1974 __PACKAGE__-
>register_method({
1976 path
=> '{vmid}/status/stop',
1980 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1981 "is akin to pulling the power plug of a running computer and may damage the VM data",
1983 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1986 additionalProperties
=> 0,
1988 node
=> get_standard_option
('pve-node'),
1989 vmid
=> get_standard_option
('pve-vmid',
1990 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1991 skiplock
=> get_standard_option
('skiplock'),
1992 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1994 description
=> "Wait maximal timeout seconds.",
2000 description
=> "Do not deactivate storage volumes.",
2013 my $rpcenv = PVE
::RPCEnvironment
::get
();
2015 my $authuser = $rpcenv->get_user();
2017 my $node = extract_param
($param, 'node');
2019 my $vmid = extract_param
($param, 'vmid');
2021 my $skiplock = extract_param
($param, 'skiplock');
2022 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2023 if $skiplock && $authuser ne 'root@pam';
2025 my $keepActive = extract_param
($param, 'keepActive');
2026 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2027 if $keepActive && $authuser ne 'root@pam';
2029 my $migratedfrom = extract_param
($param, 'migratedfrom');
2030 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2031 if $migratedfrom && $authuser ne 'root@pam';
2034 my $storecfg = PVE
::Storage
::config
();
2036 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2041 my $service = "vm:$vmid";
2043 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2045 print "Requesting HA stop for VM $vmid\n";
2047 PVE
::Tools
::run_command
($cmd);
2052 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2058 syslog
('info', "stop VM $vmid: $upid\n");
2060 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2061 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2066 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2070 __PACKAGE__-
>register_method({
2072 path
=> '{vmid}/status/reset',
2076 description
=> "Reset virtual machine.",
2078 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid',
2085 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2086 skiplock
=> get_standard_option
('skiplock'),
2095 my $rpcenv = PVE
::RPCEnvironment
::get
();
2097 my $authuser = $rpcenv->get_user();
2099 my $node = extract_param
($param, 'node');
2101 my $vmid = extract_param
($param, 'vmid');
2103 my $skiplock = extract_param
($param, 'skiplock');
2104 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2105 if $skiplock && $authuser ne 'root@pam';
2107 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2112 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2117 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2120 __PACKAGE__-
>register_method({
2121 name
=> 'vm_shutdown',
2122 path
=> '{vmid}/status/shutdown',
2126 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2127 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2129 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2132 additionalProperties
=> 0,
2134 node
=> get_standard_option
('pve-node'),
2135 vmid
=> get_standard_option
('pve-vmid',
2136 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2137 skiplock
=> get_standard_option
('skiplock'),
2139 description
=> "Wait maximal timeout seconds.",
2145 description
=> "Make sure the VM stops.",
2151 description
=> "Do not deactivate storage volumes.",
2164 my $rpcenv = PVE
::RPCEnvironment
::get
();
2166 my $authuser = $rpcenv->get_user();
2168 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $skiplock = extract_param
($param, 'skiplock');
2173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2174 if $skiplock && $authuser ne 'root@pam';
2176 my $keepActive = extract_param
($param, 'keepActive');
2177 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2178 if $keepActive && $authuser ne 'root@pam';
2180 my $storecfg = PVE
::Storage
::config
();
2184 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2185 # otherwise, we will infer a shutdown command, but run into the timeout,
2186 # then when the vm is resumed, it will instantly shutdown
2188 # checking the qmp status here to get feedback to the gui/cli/api
2189 # and the status query should not take too long
2192 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2196 if (!$err && $qmpstatus->{status
} eq "paused") {
2197 if ($param->{forceStop
}) {
2198 warn "VM is paused - stop instead of shutdown\n";
2201 die "VM is paused - cannot shutdown\n";
2205 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2206 ($rpcenv->{type
} ne 'ha')) {
2211 my $service = "vm:$vmid";
2213 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2215 print "Requesting HA stop for VM $vmid\n";
2217 PVE
::Tools
::run_command
($cmd);
2222 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2229 syslog
('info', "shutdown VM $vmid: $upid\n");
2231 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2232 $shutdown, $param->{forceStop
}, $keepActive);
2237 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2241 __PACKAGE__-
>register_method({
2242 name
=> 'vm_suspend',
2243 path
=> '{vmid}/status/suspend',
2247 description
=> "Suspend virtual machine.",
2249 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2252 additionalProperties
=> 0,
2254 node
=> get_standard_option
('pve-node'),
2255 vmid
=> get_standard_option
('pve-vmid',
2256 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2257 skiplock
=> get_standard_option
('skiplock'),
2266 my $rpcenv = PVE
::RPCEnvironment
::get
();
2268 my $authuser = $rpcenv->get_user();
2270 my $node = extract_param
($param, 'node');
2272 my $vmid = extract_param
($param, 'vmid');
2274 my $skiplock = extract_param
($param, 'skiplock');
2275 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2276 if $skiplock && $authuser ne 'root@pam';
2278 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2283 syslog
('info', "suspend VM $vmid: $upid\n");
2285 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2290 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2293 __PACKAGE__-
>register_method({
2294 name
=> 'vm_resume',
2295 path
=> '{vmid}/status/resume',
2299 description
=> "Resume virtual machine.",
2301 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2304 additionalProperties
=> 0,
2306 node
=> get_standard_option
('pve-node'),
2307 vmid
=> get_standard_option
('pve-vmid',
2308 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2309 skiplock
=> get_standard_option
('skiplock'),
2310 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2320 my $rpcenv = PVE
::RPCEnvironment
::get
();
2322 my $authuser = $rpcenv->get_user();
2324 my $node = extract_param
($param, 'node');
2326 my $vmid = extract_param
($param, 'vmid');
2328 my $skiplock = extract_param
($param, 'skiplock');
2329 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2330 if $skiplock && $authuser ne 'root@pam';
2332 my $nocheck = extract_param
($param, 'nocheck');
2334 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2339 syslog
('info', "resume VM $vmid: $upid\n");
2341 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2346 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2349 __PACKAGE__-
>register_method({
2350 name
=> 'vm_sendkey',
2351 path
=> '{vmid}/sendkey',
2355 description
=> "Send key event to virtual machine.",
2357 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2360 additionalProperties
=> 0,
2362 node
=> get_standard_option
('pve-node'),
2363 vmid
=> get_standard_option
('pve-vmid',
2364 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2365 skiplock
=> get_standard_option
('skiplock'),
2367 description
=> "The key (qemu monitor encoding).",
2372 returns
=> { type
=> 'null'},
2376 my $rpcenv = PVE
::RPCEnvironment
::get
();
2378 my $authuser = $rpcenv->get_user();
2380 my $node = extract_param
($param, 'node');
2382 my $vmid = extract_param
($param, 'vmid');
2384 my $skiplock = extract_param
($param, 'skiplock');
2385 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2386 if $skiplock && $authuser ne 'root@pam';
2388 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2393 __PACKAGE__-
>register_method({
2394 name
=> 'vm_feature',
2395 path
=> '{vmid}/feature',
2399 description
=> "Check if feature for virtual machine is available.",
2401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2404 additionalProperties
=> 0,
2406 node
=> get_standard_option
('pve-node'),
2407 vmid
=> get_standard_option
('pve-vmid'),
2409 description
=> "Feature to check.",
2411 enum
=> [ 'snapshot', 'clone', 'copy' ],
2413 snapname
=> get_standard_option
('pve-snapshot-name', {
2421 hasFeature
=> { type
=> 'boolean' },
2424 items
=> { type
=> 'string' },
2431 my $node = extract_param
($param, 'node');
2433 my $vmid = extract_param
($param, 'vmid');
2435 my $snapname = extract_param
($param, 'snapname');
2437 my $feature = extract_param
($param, 'feature');
2439 my $running = PVE
::QemuServer
::check_running
($vmid);
2441 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2444 my $snap = $conf->{snapshots
}->{$snapname};
2445 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2448 my $storecfg = PVE
::Storage
::config
();
2450 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2451 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2454 hasFeature
=> $hasFeature,
2455 nodes
=> [ keys %$nodelist ],
2459 __PACKAGE__-
>register_method({
2461 path
=> '{vmid}/clone',
2465 description
=> "Create a copy of virtual machine/template.",
2467 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2468 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2469 "'Datastore.AllocateSpace' on any used storage.",
2472 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2474 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2475 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2480 additionalProperties
=> 0,
2482 node
=> get_standard_option
('pve-node'),
2483 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2484 newid
=> get_standard_option
('pve-vmid', {
2485 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2486 description
=> 'VMID for the clone.' }),
2489 type
=> 'string', format
=> 'dns-name',
2490 description
=> "Set a name for the new VM.",
2495 description
=> "Description for the new VM.",
2499 type
=> 'string', format
=> 'pve-poolid',
2500 description
=> "Add the new VM to the specified pool.",
2502 snapname
=> get_standard_option
('pve-snapshot-name', {
2505 storage
=> get_standard_option
('pve-storage-id', {
2506 description
=> "Target storage for full clone.",
2510 description
=> "Target format for file storage. Only valid for full clone.",
2513 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2518 description
=> "Create a full copy of all disks. This is always done when " .
2519 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2521 target
=> get_standard_option
('pve-node', {
2522 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2533 my $rpcenv = PVE
::RPCEnvironment
::get
();
2535 my $authuser = $rpcenv->get_user();
2537 my $node = extract_param
($param, 'node');
2539 my $vmid = extract_param
($param, 'vmid');
2541 my $newid = extract_param
($param, 'newid');
2543 my $pool = extract_param
($param, 'pool');
2545 if (defined($pool)) {
2546 $rpcenv->check_pool_exist($pool);
2549 my $snapname = extract_param
($param, 'snapname');
2551 my $storage = extract_param
($param, 'storage');
2553 my $format = extract_param
($param, 'format');
2555 my $target = extract_param
($param, 'target');
2557 my $localnode = PVE
::INotify
::nodename
();
2559 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2561 PVE
::Cluster
::check_node_exists
($target) if $target;
2563 my $storecfg = PVE
::Storage
::config
();
2566 # check if storage is enabled on local node
2567 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2569 # check if storage is available on target node
2570 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2571 # clone only works if target storage is shared
2572 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2573 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2577 PVE
::Cluster
::check_cfs_quorum
();
2579 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2581 # exclusive lock if VM is running - else shared lock is enough;
2582 my $shared_lock = $running ?
0 : 1;
2586 # do all tests after lock
2587 # we also try to do all tests before we fork the worker
2589 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2591 PVE
::QemuConfig-
>check_lock($conf);
2593 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2595 die "unexpected state change\n" if $verify_running != $running;
2597 die "snapshot '$snapname' does not exist\n"
2598 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2600 my $full = extract_param
($param, 'full');
2601 if (!defined($full)) {
2602 $full = !PVE
::QemuConfig-
>is_template($conf);
2605 die "parameter 'storage' not allowed for linked clones\n"
2606 if defined($storage) && !$full;
2608 die "parameter 'format' not allowed for linked clones\n"
2609 if defined($format) && !$full;
2611 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2613 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2615 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2617 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2619 die "unable to create VM $newid: config file already exists\n"
2622 my $newconf = { lock => 'clone' };
2627 foreach my $opt (keys %$oldconf) {
2628 my $value = $oldconf->{$opt};
2630 # do not copy snapshot related info
2631 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2632 $opt eq 'vmstate' || $opt eq 'snapstate';
2634 # no need to copy unused images, because VMID(owner) changes anyways
2635 next if $opt =~ m/^unused\d+$/;
2637 # always change MAC! address
2638 if ($opt =~ m/^net(\d+)$/) {
2639 my $net = PVE
::QemuServer
::parse_net
($value);
2640 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2641 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2642 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2643 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2644 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2645 die "unable to parse drive options for '$opt'\n" if !$drive;
2646 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2647 $newconf->{$opt} = $value; # simply copy configuration
2649 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2650 die "Full clone feature is not supported for drive '$opt'\n"
2651 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2652 $fullclone->{$opt} = 1;
2654 # not full means clone instead of copy
2655 die "Linked clone feature is not supported for drive '$opt'\n"
2656 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2658 $drives->{$opt} = $drive;
2659 push @$vollist, $drive->{file
};
2662 # copy everything else
2663 $newconf->{$opt} = $value;
2667 # auto generate a new uuid
2668 my ($uuid, $uuid_str);
2669 UUID
::generate
($uuid);
2670 UUID
::unparse
($uuid, $uuid_str);
2671 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2672 $smbios1->{uuid
} = $uuid_str;
2673 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2675 delete $newconf->{template
};
2677 if ($param->{name
}) {
2678 $newconf->{name
} = $param->{name
};
2680 if ($oldconf->{name
}) {
2681 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2683 $newconf->{name
} = "Copy-of-VM-$vmid";
2687 if ($param->{description
}) {
2688 $newconf->{description
} = $param->{description
};
2691 # create empty/temp config - this fails if VM already exists on other node
2692 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2697 my $newvollist = [];
2704 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2706 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2708 my $total_jobs = scalar(keys %{$drives});
2711 foreach my $opt (keys %$drives) {
2712 my $drive = $drives->{$opt};
2713 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2715 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2716 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2717 $jobs, $skipcomplete, $oldconf->{agent
});
2719 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2721 PVE
::QemuConfig-
>write_config($newid, $newconf);
2725 delete $newconf->{lock};
2726 PVE
::QemuConfig-
>write_config($newid, $newconf);
2729 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2730 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2731 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2733 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2734 die "Failed to move config to node '$target' - rename failed: $!\n"
2735 if !rename($conffile, $newconffile);
2738 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2743 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2745 sleep 1; # some storage like rbd need to wait before release volume - really?
2747 foreach my $volid (@$newvollist) {
2748 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2751 die "clone failed: $err";
2757 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2759 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2762 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2763 # Aquire exclusive lock lock for $newid
2764 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2769 __PACKAGE__-
>register_method({
2770 name
=> 'move_vm_disk',
2771 path
=> '{vmid}/move_disk',
2775 description
=> "Move volume to different storage.",
2777 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2779 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2780 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2784 additionalProperties
=> 0,
2786 node
=> get_standard_option
('pve-node'),
2787 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2790 description
=> "The disk you want to move.",
2791 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2793 storage
=> get_standard_option
('pve-storage-id', {
2794 description
=> "Target storage.",
2795 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2799 description
=> "Target Format.",
2800 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2805 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2811 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2819 description
=> "the task ID.",
2824 my $rpcenv = PVE
::RPCEnvironment
::get
();
2826 my $authuser = $rpcenv->get_user();
2828 my $node = extract_param
($param, 'node');
2830 my $vmid = extract_param
($param, 'vmid');
2832 my $digest = extract_param
($param, 'digest');
2834 my $disk = extract_param
($param, 'disk');
2836 my $storeid = extract_param
($param, 'storage');
2838 my $format = extract_param
($param, 'format');
2840 my $storecfg = PVE
::Storage
::config
();
2842 my $updatefn = sub {
2844 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2846 PVE
::QemuConfig-
>check_lock($conf);
2848 die "checksum missmatch (file change by other user?)\n"
2849 if $digest && $digest ne $conf->{digest
};
2851 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2853 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2855 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2857 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2860 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2861 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2865 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2866 (!$format || !$oldfmt || $oldfmt eq $format);
2868 # this only checks snapshots because $disk is passed!
2869 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2870 die "you can't move a disk with snapshots and delete the source\n"
2871 if $snapshotted && $param->{delete};
2873 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2875 my $running = PVE
::QemuServer
::check_running
($vmid);
2877 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2881 my $newvollist = [];
2887 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2889 warn "moving disk with snapshots, snapshots will not be moved!\n"
2892 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2893 $vmid, $storeid, $format, 1, $newvollist);
2895 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2897 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2899 # convert moved disk to base if part of template
2900 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2901 if PVE
::QemuConfig-
>is_template($conf);
2903 PVE
::QemuConfig-
>write_config($vmid, $conf);
2906 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2907 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2914 foreach my $volid (@$newvollist) {
2915 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2918 die "storage migration failed: $err";
2921 if ($param->{delete}) {
2923 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2924 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2930 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2933 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2936 __PACKAGE__-
>register_method({
2937 name
=> 'migrate_vm',
2938 path
=> '{vmid}/migrate',
2942 description
=> "Migrate virtual machine. Creates a new migration task.",
2944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2947 additionalProperties
=> 0,
2949 node
=> get_standard_option
('pve-node'),
2950 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2951 target
=> get_standard_option
('pve-node', {
2952 description
=> "Target node.",
2953 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2957 description
=> "Use online/live migration.",
2962 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2967 enum
=> ['secure', 'insecure'],
2968 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2971 migration_network
=> {
2972 type
=> 'string', format
=> 'CIDR',
2973 description
=> "CIDR of the (sub) network that is used for migration.",
2976 "with-local-disks" => {
2978 description
=> "Enable live storage migration for local disk",
2981 targetstorage
=> get_standard_option
('pve-storage-id', {
2982 description
=> "Default target storage.",
2984 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2990 description
=> "the task ID.",
2995 my $rpcenv = PVE
::RPCEnvironment
::get
();
2997 my $authuser = $rpcenv->get_user();
2999 my $target = extract_param
($param, 'target');
3001 my $localnode = PVE
::INotify
::nodename
();
3002 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3004 PVE
::Cluster
::check_cfs_quorum
();
3006 PVE
::Cluster
::check_node_exists
($target);
3008 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3010 my $vmid = extract_param
($param, 'vmid');
3012 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3013 if !$param->{online
} && $param->{targetstorage
};
3015 raise_param_exc
({ force
=> "Only root may use this option." })
3016 if $param->{force
} && $authuser ne 'root@pam';
3018 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3019 if $param->{migration_type
} && $authuser ne 'root@pam';
3021 # allow root only until better network permissions are available
3022 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3023 if $param->{migration_network
} && $authuser ne 'root@pam';
3026 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3028 # try to detect errors early
3030 PVE
::QemuConfig-
>check_lock($conf);
3032 if (PVE
::QemuServer
::check_running
($vmid)) {
3033 die "cant migrate running VM without --online\n"
3034 if !$param->{online
};
3037 my $storecfg = PVE
::Storage
::config
();
3039 if( $param->{targetstorage
}) {
3040 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3042 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3045 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3050 my $service = "vm:$vmid";
3052 my $cmd = ['ha-manager', 'migrate', $service, $target];
3054 print "Requesting HA migration for VM $vmid to node $target\n";
3056 PVE
::Tools
::run_command
($cmd);
3061 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3066 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3070 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3073 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3078 __PACKAGE__-
>register_method({
3080 path
=> '{vmid}/monitor',
3084 description
=> "Execute Qemu monitor commands.",
3086 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3087 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3090 additionalProperties
=> 0,
3092 node
=> get_standard_option
('pve-node'),
3093 vmid
=> get_standard_option
('pve-vmid'),
3096 description
=> "The monitor command.",
3100 returns
=> { type
=> 'string'},
3104 my $rpcenv = PVE
::RPCEnvironment
::get
();
3105 my $authuser = $rpcenv->get_user();
3108 my $command = shift;
3109 return $command =~ m/^\s*info(\s+|$)/
3110 || $command =~ m/^\s*help\s*$/;
3113 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3114 if !&$is_ro($param->{command
});
3116 my $vmid = $param->{vmid
};
3118 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3122 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3124 $res = "ERROR: $@" if $@;
3129 __PACKAGE__-
>register_method({
3130 name
=> 'resize_vm',
3131 path
=> '{vmid}/resize',
3135 description
=> "Extend volume size.",
3137 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3140 additionalProperties
=> 0,
3142 node
=> get_standard_option
('pve-node'),
3143 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3144 skiplock
=> get_standard_option
('skiplock'),
3147 description
=> "The disk you want to resize.",
3148 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3152 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3153 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.",
3157 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3163 returns
=> { type
=> 'null'},
3167 my $rpcenv = PVE
::RPCEnvironment
::get
();
3169 my $authuser = $rpcenv->get_user();
3171 my $node = extract_param
($param, 'node');
3173 my $vmid = extract_param
($param, 'vmid');
3175 my $digest = extract_param
($param, 'digest');
3177 my $disk = extract_param
($param, 'disk');
3179 my $sizestr = extract_param
($param, 'size');
3181 my $skiplock = extract_param
($param, 'skiplock');
3182 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3183 if $skiplock && $authuser ne 'root@pam';
3185 my $storecfg = PVE
::Storage
::config
();
3187 my $updatefn = sub {
3189 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3191 die "checksum missmatch (file change by other user?)\n"
3192 if $digest && $digest ne $conf->{digest
};
3193 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3195 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3197 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3199 my (undef, undef, undef, undef, undef, undef, $format) =
3200 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3202 die "can't resize volume: $disk if snapshot exists\n"
3203 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3205 my $volid = $drive->{file
};
3207 die "disk '$disk' has no associated volume\n" if !$volid;
3209 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3211 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3213 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3215 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3216 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3218 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3219 my ($ext, $newsize, $unit) = ($1, $2, $4);
3222 $newsize = $newsize * 1024;
3223 } elsif ($unit eq 'M') {
3224 $newsize = $newsize * 1024 * 1024;
3225 } elsif ($unit eq 'G') {
3226 $newsize = $newsize * 1024 * 1024 * 1024;
3227 } elsif ($unit eq 'T') {
3228 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3231 $newsize += $size if $ext;
3232 $newsize = int($newsize);
3234 die "shrinking disks is not supported\n" if $newsize < $size;
3236 return if $size == $newsize;
3238 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3240 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3242 $drive->{size
} = $newsize;
3243 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3245 PVE
::QemuConfig-
>write_config($vmid, $conf);
3248 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3252 __PACKAGE__-
>register_method({
3253 name
=> 'snapshot_list',
3254 path
=> '{vmid}/snapshot',
3256 description
=> "List all snapshots.",
3258 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3261 protected
=> 1, # qemu pid files are only readable by root
3263 additionalProperties
=> 0,
3265 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3266 node
=> get_standard_option
('pve-node'),
3275 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3280 my $vmid = $param->{vmid
};
3282 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3283 my $snaphash = $conf->{snapshots
} || {};
3287 foreach my $name (keys %$snaphash) {
3288 my $d = $snaphash->{$name};
3291 snaptime
=> $d->{snaptime
} || 0,
3292 vmstate
=> $d->{vmstate
} ?
1 : 0,
3293 description
=> $d->{description
} || '',
3295 $item->{parent
} = $d->{parent
} if $d->{parent
};
3296 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3300 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3301 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3302 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3304 push @$res, $current;
3309 __PACKAGE__-
>register_method({
3311 path
=> '{vmid}/snapshot',
3315 description
=> "Snapshot a VM.",
3317 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3320 additionalProperties
=> 0,
3322 node
=> get_standard_option
('pve-node'),
3323 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3324 snapname
=> get_standard_option
('pve-snapshot-name'),
3328 description
=> "Save the vmstate",
3333 description
=> "A textual description or comment.",
3339 description
=> "the task ID.",
3344 my $rpcenv = PVE
::RPCEnvironment
::get
();
3346 my $authuser = $rpcenv->get_user();
3348 my $node = extract_param
($param, 'node');
3350 my $vmid = extract_param
($param, 'vmid');
3352 my $snapname = extract_param
($param, 'snapname');
3354 die "unable to use snapshot name 'current' (reserved name)\n"
3355 if $snapname eq 'current';
3358 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3359 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3360 $param->{description
});
3363 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3366 __PACKAGE__-
>register_method({
3367 name
=> 'snapshot_cmd_idx',
3368 path
=> '{vmid}/snapshot/{snapname}',
3375 additionalProperties
=> 0,
3377 vmid
=> get_standard_option
('pve-vmid'),
3378 node
=> get_standard_option
('pve-node'),
3379 snapname
=> get_standard_option
('pve-snapshot-name'),
3388 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3395 push @$res, { cmd
=> 'rollback' };
3396 push @$res, { cmd
=> 'config' };
3401 __PACKAGE__-
>register_method({
3402 name
=> 'update_snapshot_config',
3403 path
=> '{vmid}/snapshot/{snapname}/config',
3407 description
=> "Update snapshot metadata.",
3409 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3412 additionalProperties
=> 0,
3414 node
=> get_standard_option
('pve-node'),
3415 vmid
=> get_standard_option
('pve-vmid'),
3416 snapname
=> get_standard_option
('pve-snapshot-name'),
3420 description
=> "A textual description or comment.",
3424 returns
=> { type
=> 'null' },
3428 my $rpcenv = PVE
::RPCEnvironment
::get
();
3430 my $authuser = $rpcenv->get_user();
3432 my $vmid = extract_param
($param, 'vmid');
3434 my $snapname = extract_param
($param, 'snapname');
3436 return undef if !defined($param->{description
});
3438 my $updatefn = sub {
3440 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3442 PVE
::QemuConfig-
>check_lock($conf);
3444 my $snap = $conf->{snapshots
}->{$snapname};
3446 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3448 $snap->{description
} = $param->{description
} if defined($param->{description
});
3450 PVE
::QemuConfig-
>write_config($vmid, $conf);
3453 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3458 __PACKAGE__-
>register_method({
3459 name
=> 'get_snapshot_config',
3460 path
=> '{vmid}/snapshot/{snapname}/config',
3463 description
=> "Get snapshot configuration",
3465 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3468 additionalProperties
=> 0,
3470 node
=> get_standard_option
('pve-node'),
3471 vmid
=> get_standard_option
('pve-vmid'),
3472 snapname
=> get_standard_option
('pve-snapshot-name'),
3475 returns
=> { type
=> "object" },
3479 my $rpcenv = PVE
::RPCEnvironment
::get
();
3481 my $authuser = $rpcenv->get_user();
3483 my $vmid = extract_param
($param, 'vmid');
3485 my $snapname = extract_param
($param, 'snapname');
3487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3489 my $snap = $conf->{snapshots
}->{$snapname};
3491 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3496 __PACKAGE__-
>register_method({
3498 path
=> '{vmid}/snapshot/{snapname}/rollback',
3502 description
=> "Rollback VM state to specified snapshot.",
3504 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3507 additionalProperties
=> 0,
3509 node
=> get_standard_option
('pve-node'),
3510 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3511 snapname
=> get_standard_option
('pve-snapshot-name'),
3516 description
=> "the task ID.",
3521 my $rpcenv = PVE
::RPCEnvironment
::get
();
3523 my $authuser = $rpcenv->get_user();
3525 my $node = extract_param
($param, 'node');
3527 my $vmid = extract_param
($param, 'vmid');
3529 my $snapname = extract_param
($param, 'snapname');
3532 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3533 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3537 # hold migration lock, this makes sure that nobody create replication snapshots
3538 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3541 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3544 __PACKAGE__-
>register_method({
3545 name
=> 'delsnapshot',
3546 path
=> '{vmid}/snapshot/{snapname}',
3550 description
=> "Delete a VM snapshot.",
3552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3555 additionalProperties
=> 0,
3557 node
=> get_standard_option
('pve-node'),
3558 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3559 snapname
=> get_standard_option
('pve-snapshot-name'),
3563 description
=> "For removal from config file, even if removing disk snapshots fails.",
3569 description
=> "the task ID.",
3574 my $rpcenv = PVE
::RPCEnvironment
::get
();
3576 my $authuser = $rpcenv->get_user();
3578 my $node = extract_param
($param, 'node');
3580 my $vmid = extract_param
($param, 'vmid');
3582 my $snapname = extract_param
($param, 'snapname');
3585 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3586 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3589 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3592 __PACKAGE__-
>register_method({
3594 path
=> '{vmid}/template',
3598 description
=> "Create a Template.",
3600 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3601 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3604 additionalProperties
=> 0,
3606 node
=> get_standard_option
('pve-node'),
3607 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3611 description
=> "If you want to convert only 1 disk to base image.",
3612 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3617 returns
=> { type
=> 'null'},
3621 my $rpcenv = PVE
::RPCEnvironment
::get
();
3623 my $authuser = $rpcenv->get_user();
3625 my $node = extract_param
($param, 'node');
3627 my $vmid = extract_param
($param, 'vmid');
3629 my $disk = extract_param
($param, 'disk');
3631 my $updatefn = sub {
3633 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3635 PVE
::QemuConfig-
>check_lock($conf);
3637 die "unable to create template, because VM contains snapshots\n"
3638 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3640 die "you can't convert a template to a template\n"
3641 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3643 die "you can't convert a VM to template if VM is running\n"
3644 if PVE
::QemuServer
::check_running
($vmid);
3647 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3650 $conf->{template
} = 1;
3651 PVE
::QemuConfig-
>write_config($vmid, $conf);
3653 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3656 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);