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
};
810 # hide cloudinit password
811 if ($conf->{cipassword
}) {
812 $conf->{cipassword
} = '**********';
818 __PACKAGE__-
>register_method({
819 name
=> 'vm_pending',
820 path
=> '{vmid}/pending',
823 description
=> "Get virtual machine configuration, including pending changes.",
825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
828 additionalProperties
=> 0,
830 node
=> get_standard_option
('pve-node'),
831 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
840 description
=> "Configuration option name.",
844 description
=> "Current value.",
849 description
=> "Pending value.",
854 description
=> "Indicates a pending delete request if present and not 0. " .
855 "The value 2 indicates a force-delete request.",
867 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
869 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
873 foreach my $opt (keys %$conf) {
874 next if ref($conf->{$opt});
875 my $item = { key
=> $opt };
876 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
877 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
878 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
880 # hide cloudinit password
881 if ($opt eq 'cipassword') {
882 $item->{value
} = '**********' if defined($item->{value
});
883 # the trailing space so that the pending string is different
884 $item->{pending
} = '********** ' if defined($item->{pending
});
889 foreach my $opt (keys %{$conf->{pending
}}) {
890 next if $opt eq 'delete';
891 next if ref($conf->{pending
}->{$opt}); # just to be sure
892 next if defined($conf->{$opt});
893 my $item = { key
=> $opt };
894 $item->{pending
} = $conf->{pending
}->{$opt};
896 # hide cloudinit password
897 if ($opt eq 'cipassword') {
898 $item->{pending
} = '**********' if defined($item->{pending
});
903 while (my ($opt, $force) = each %$pending_delete_hash) {
904 next if $conf->{pending
}->{$opt}; # just to be sure
905 next if $conf->{$opt};
906 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
913 # POST/PUT {vmid}/config implementation
915 # The original API used PUT (idempotent) an we assumed that all operations
916 # are fast. But it turned out that almost any configuration change can
917 # involve hot-plug actions, or disk alloc/free. Such actions can take long
918 # time to complete and have side effects (not idempotent).
920 # The new implementation uses POST and forks a worker process. We added
921 # a new option 'background_delay'. If specified we wait up to
922 # 'background_delay' second for the worker task to complete. It returns null
923 # if the task is finished within that time, else we return the UPID.
925 my $update_vm_api = sub {
926 my ($param, $sync) = @_;
928 my $rpcenv = PVE
::RPCEnvironment
::get
();
930 my $authuser = $rpcenv->get_user();
932 my $node = extract_param
($param, 'node');
934 my $vmid = extract_param
($param, 'vmid');
936 my $digest = extract_param
($param, 'digest');
938 my $background_delay = extract_param
($param, 'background_delay');
940 if (defined(my $cipassword = $param->{cipassword
})) {
941 # Same logic as in cloud-init (but with the regex fixed...)
942 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
943 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
946 my @paramarr = (); # used for log message
947 foreach my $key (sort keys %$param) {
948 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
949 push @paramarr, "-$key", $value;
952 my $skiplock = extract_param
($param, 'skiplock');
953 raise_param_exc
({ skiplock
=> "Only root may use this option." })
954 if $skiplock && $authuser ne 'root@pam';
956 my $delete_str = extract_param
($param, 'delete');
958 my $revert_str = extract_param
($param, 'revert');
960 my $force = extract_param
($param, 'force');
962 if (defined(my $ssh_keys = $param->{sshkeys
})) {
963 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
964 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
967 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
969 my $storecfg = PVE
::Storage
::config
();
971 my $defaults = PVE
::QemuServer
::load_defaults
();
973 &$resolve_cdrom_alias($param);
975 # now try to verify all parameters
978 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
979 if (!PVE
::QemuServer
::option_exists
($opt)) {
980 raise_param_exc
({ revert
=> "unknown option '$opt'" });
983 raise_param_exc
({ delete => "you can't use '-$opt' and " .
984 "-revert $opt' at the same time" })
985 if defined($param->{$opt});
991 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
992 $opt = 'ide2' if $opt eq 'cdrom';
994 raise_param_exc
({ delete => "you can't use '-$opt' and " .
995 "-delete $opt' at the same time" })
996 if defined($param->{$opt});
998 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
999 "-revert $opt' at the same time" })
1002 if (!PVE
::QemuServer
::option_exists
($opt)) {
1003 raise_param_exc
({ delete => "unknown option '$opt'" });
1009 my $repl_conf = PVE
::ReplicationConfig-
>new();
1010 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1011 my $check_replication = sub {
1013 return if !$is_replicated;
1014 my $volid = $drive->{file
};
1015 return if !$volid || !($drive->{replicate
}//1);
1016 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1017 my ($storeid, $format);
1018 if ($volid =~ $NEW_DISK_RE) {
1020 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1022 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1023 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1025 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1026 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1027 return if $scfg->{shared
};
1028 die "cannot add non-replicatable volume to a replicated VM\n";
1031 foreach my $opt (keys %$param) {
1032 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1033 # cleanup drive path
1034 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1035 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1036 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1037 $check_replication->($drive);
1038 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1039 } elsif ($opt =~ m/^net(\d+)$/) {
1041 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1042 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1046 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1048 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1050 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1052 my $updatefn = sub {
1054 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1056 die "checksum missmatch (file change by other user?)\n"
1057 if $digest && $digest ne $conf->{digest
};
1059 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1061 foreach my $opt (keys %$revert) {
1062 if (defined($conf->{$opt})) {
1063 $param->{$opt} = $conf->{$opt};
1064 } elsif (defined($conf->{pending
}->{$opt})) {
1069 if ($param->{memory
} || defined($param->{balloon
})) {
1070 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1071 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1073 die "balloon value too large (must be smaller than assigned memory)\n"
1074 if $balloon && $balloon > $maxmem;
1077 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1081 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1083 # write updates to pending section
1085 my $modified = {}; # record what $option we modify
1087 foreach my $opt (@delete) {
1088 $modified->{$opt} = 1;
1089 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1090 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1091 warn "cannot delete '$opt' - not set in current configuration!\n";
1092 $modified->{$opt} = 0;
1096 if ($opt =~ m/^unused/) {
1097 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1098 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1099 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1100 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1101 delete $conf->{$opt};
1102 PVE
::QemuConfig-
>write_config($vmid, $conf);
1104 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1105 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1106 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1107 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1108 if defined($conf->{pending
}->{$opt});
1109 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1110 PVE
::QemuConfig-
>write_config($vmid, $conf);
1112 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1113 PVE
::QemuConfig-
>write_config($vmid, $conf);
1117 foreach my $opt (keys %$param) { # add/change
1118 $modified->{$opt} = 1;
1119 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1120 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1122 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1123 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1124 # FIXME: cloudinit: CDROM or Disk?
1125 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1126 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1128 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1130 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1131 if defined($conf->{pending
}->{$opt});
1133 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1135 $conf->{pending
}->{$opt} = $param->{$opt};
1137 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1138 PVE
::QemuConfig-
>write_config($vmid, $conf);
1141 # remove pending changes when nothing changed
1142 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1143 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1144 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1146 return if !scalar(keys %{$conf->{pending
}});
1148 my $running = PVE
::QemuServer
::check_running
($vmid);
1150 # apply pending changes
1152 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1156 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1157 raise_param_exc
($errors) if scalar(keys %$errors);
1159 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1169 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1171 if ($background_delay) {
1173 # Note: It would be better to do that in the Event based HTTPServer
1174 # to avoid blocking call to sleep.
1176 my $end_time = time() + $background_delay;
1178 my $task = PVE
::Tools
::upid_decode
($upid);
1181 while (time() < $end_time) {
1182 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1184 sleep(1); # this gets interrupted when child process ends
1188 my $status = PVE
::Tools
::upid_read_status
($upid);
1189 return undef if $status eq 'OK';
1198 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1201 my $vm_config_perm_list = [
1206 'VM.Config.Network',
1208 'VM.Config.Options',
1211 __PACKAGE__-
>register_method({
1212 name
=> 'update_vm_async',
1213 path
=> '{vmid}/config',
1217 description
=> "Set virtual machine options (asynchrounous API).",
1219 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1222 additionalProperties
=> 0,
1223 properties
=> PVE
::QemuServer
::json_config_properties
(
1225 node
=> get_standard_option
('pve-node'),
1226 vmid
=> get_standard_option
('pve-vmid'),
1227 skiplock
=> get_standard_option
('skiplock'),
1229 type
=> 'string', format
=> 'pve-configid-list',
1230 description
=> "A list of settings you want to delete.",
1234 type
=> 'string', format
=> 'pve-configid-list',
1235 description
=> "Revert a pending change.",
1240 description
=> $opt_force_description,
1242 requires
=> 'delete',
1246 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1250 background_delay
=> {
1252 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1263 code
=> $update_vm_api,
1266 __PACKAGE__-
>register_method({
1267 name
=> 'update_vm',
1268 path
=> '{vmid}/config',
1272 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1274 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1277 additionalProperties
=> 0,
1278 properties
=> PVE
::QemuServer
::json_config_properties
(
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1282 skiplock
=> get_standard_option
('skiplock'),
1284 type
=> 'string', format
=> 'pve-configid-list',
1285 description
=> "A list of settings you want to delete.",
1289 type
=> 'string', format
=> 'pve-configid-list',
1290 description
=> "Revert a pending change.",
1295 description
=> $opt_force_description,
1297 requires
=> 'delete',
1301 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1307 returns
=> { type
=> 'null' },
1310 &$update_vm_api($param, 1);
1316 __PACKAGE__-
>register_method({
1317 name
=> 'destroy_vm',
1322 description
=> "Destroy the vm (also delete all used/owned volumes).",
1324 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1327 additionalProperties
=> 0,
1329 node
=> get_standard_option
('pve-node'),
1330 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1331 skiplock
=> get_standard_option
('skiplock'),
1340 my $rpcenv = PVE
::RPCEnvironment
::get
();
1342 my $authuser = $rpcenv->get_user();
1344 my $vmid = $param->{vmid
};
1346 my $skiplock = $param->{skiplock
};
1347 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1348 if $skiplock && $authuser ne 'root@pam';
1351 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1353 my $storecfg = PVE
::Storage
::config
();
1355 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1357 die "unable to remove VM $vmid - used in HA resources\n"
1358 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1360 # do not allow destroy if there are replication jobs
1361 my $repl_conf = PVE
::ReplicationConfig-
>new();
1362 $repl_conf->check_for_existing_jobs($vmid);
1364 # early tests (repeat after locking)
1365 die "VM $vmid is running - destroy failed\n"
1366 if PVE
::QemuServer
::check_running
($vmid);
1371 syslog
('info', "destroy VM $vmid: $upid\n");
1373 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1375 PVE
::AccessControl
::remove_vm_access
($vmid);
1377 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1380 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1383 __PACKAGE__-
>register_method({
1385 path
=> '{vmid}/unlink',
1389 description
=> "Unlink/delete disk images.",
1391 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1394 additionalProperties
=> 0,
1396 node
=> get_standard_option
('pve-node'),
1397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1399 type
=> 'string', format
=> 'pve-configid-list',
1400 description
=> "A list of disk IDs you want to delete.",
1404 description
=> $opt_force_description,
1409 returns
=> { type
=> 'null'},
1413 $param->{delete} = extract_param
($param, 'idlist');
1415 __PACKAGE__-
>update_vm($param);
1422 __PACKAGE__-
>register_method({
1424 path
=> '{vmid}/vncproxy',
1428 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1430 description
=> "Creates a TCP VNC proxy connections.",
1432 additionalProperties
=> 0,
1434 node
=> get_standard_option
('pve-node'),
1435 vmid
=> get_standard_option
('pve-vmid'),
1439 description
=> "starts websockify instead of vncproxy",
1444 additionalProperties
=> 0,
1446 user
=> { type
=> 'string' },
1447 ticket
=> { type
=> 'string' },
1448 cert
=> { type
=> 'string' },
1449 port
=> { type
=> 'integer' },
1450 upid
=> { type
=> 'string' },
1456 my $rpcenv = PVE
::RPCEnvironment
::get
();
1458 my $authuser = $rpcenv->get_user();
1460 my $vmid = $param->{vmid
};
1461 my $node = $param->{node
};
1462 my $websocket = $param->{websocket
};
1464 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1466 my $authpath = "/vms/$vmid";
1468 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1470 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1473 my ($remip, $family);
1476 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1477 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1478 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1479 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1481 $family = PVE
::Tools
::get_host_address_family
($node);
1484 my $port = PVE
::Tools
::next_vnc_port
($family);
1491 syslog
('info', "starting vnc proxy $upid\n");
1495 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1498 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1500 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1501 '-timeout', $timeout, '-authpath', $authpath,
1502 '-perm', 'Sys.Console'];
1504 if ($param->{websocket
}) {
1505 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1506 push @$cmd, '-notls', '-listen', 'localhost';
1509 push @$cmd, '-c', @$remcmd, @$termcmd;
1511 PVE
::Tools
::run_command
($cmd);
1515 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1517 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1519 my $sock = IO
::Socket
::IP-
>new(
1524 GetAddrInfoFlags
=> 0,
1525 ) or die "failed to create socket: $!\n";
1526 # Inside the worker we shouldn't have any previous alarms
1527 # running anyway...:
1529 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1531 accept(my $cli, $sock) or die "connection failed: $!\n";
1534 if (PVE
::Tools
::run_command
($cmd,
1535 output
=> '>&'.fileno($cli),
1536 input
=> '<&'.fileno($cli),
1539 die "Failed to run vncproxy.\n";
1546 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1548 PVE
::Tools
::wait_for_vnc_port
($port);
1559 __PACKAGE__-
>register_method({
1560 name
=> 'termproxy',
1561 path
=> '{vmid}/termproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Creates a TCP proxy connections.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1576 enum
=> [qw(serial0 serial1 serial2 serial3)],
1577 description
=> "opens a serial terminal (defaults to display)",
1582 additionalProperties
=> 0,
1584 user
=> { type
=> 'string' },
1585 ticket
=> { type
=> 'string' },
1586 port
=> { type
=> 'integer' },
1587 upid
=> { type
=> 'string' },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $vmid = $param->{vmid
};
1598 my $node = $param->{node
};
1599 my $serial = $param->{serial
};
1601 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1603 if (!defined($serial)) {
1604 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1605 $serial = $conf->{vga
};
1609 my $authpath = "/vms/$vmid";
1611 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1613 my ($remip, $family);
1615 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1616 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1618 $family = PVE
::Tools
::get_host_address_family
($node);
1621 my $port = PVE
::Tools
::next_vnc_port
($family);
1623 my $remcmd = $remip ?
1624 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1626 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1627 push @$termcmd, '-iface', $serial if $serial;
1632 syslog
('info', "starting qemu termproxy $upid\n");
1634 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1635 '--perm', 'VM.Console', '--'];
1636 push @$cmd, @$remcmd, @$termcmd;
1638 PVE
::Tools
::run_command
($cmd);
1641 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1643 PVE
::Tools
::wait_for_vnc_port
($port);
1653 __PACKAGE__-
>register_method({
1654 name
=> 'vncwebsocket',
1655 path
=> '{vmid}/vncwebsocket',
1658 description
=> "You also need to pass a valid ticket (vncticket).",
1659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1661 description
=> "Opens a weksocket for VNC traffic.",
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1668 description
=> "Ticket from previous call to vncproxy.",
1673 description
=> "Port number returned by previous vncproxy call.",
1683 port
=> { type
=> 'string' },
1689 my $rpcenv = PVE
::RPCEnvironment
::get
();
1691 my $authuser = $rpcenv->get_user();
1693 my $vmid = $param->{vmid
};
1694 my $node = $param->{node
};
1696 my $authpath = "/vms/$vmid";
1698 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1700 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1702 # Note: VNC ports are acessible from outside, so we do not gain any
1703 # security if we verify that $param->{port} belongs to VM $vmid. This
1704 # check is done by verifying the VNC ticket (inside VNC protocol).
1706 my $port = $param->{port
};
1708 return { port
=> $port };
1711 __PACKAGE__-
>register_method({
1712 name
=> 'spiceproxy',
1713 path
=> '{vmid}/spiceproxy',
1718 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1720 description
=> "Returns a SPICE configuration to connect to the VM.",
1722 additionalProperties
=> 0,
1724 node
=> get_standard_option
('pve-node'),
1725 vmid
=> get_standard_option
('pve-vmid'),
1726 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1729 returns
=> get_standard_option
('remote-viewer-config'),
1733 my $rpcenv = PVE
::RPCEnvironment
::get
();
1735 my $authuser = $rpcenv->get_user();
1737 my $vmid = $param->{vmid
};
1738 my $node = $param->{node
};
1739 my $proxy = $param->{proxy
};
1741 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1742 my $title = "VM $vmid";
1743 $title .= " - ". $conf->{name
} if $conf->{name
};
1745 my $port = PVE
::QemuServer
::spice_port
($vmid);
1747 my ($ticket, undef, $remote_viewer_config) =
1748 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1750 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1751 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1753 return $remote_viewer_config;
1756 __PACKAGE__-
>register_method({
1758 path
=> '{vmid}/status',
1761 description
=> "Directory index",
1766 additionalProperties
=> 0,
1768 node
=> get_standard_option
('pve-node'),
1769 vmid
=> get_standard_option
('pve-vmid'),
1777 subdir
=> { type
=> 'string' },
1780 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1786 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1789 { subdir
=> 'current' },
1790 { subdir
=> 'start' },
1791 { subdir
=> 'stop' },
1797 __PACKAGE__-
>register_method({
1798 name
=> 'vm_status',
1799 path
=> '{vmid}/status/current',
1802 protected
=> 1, # qemu pid files are only readable by root
1803 description
=> "Get virtual machine status.",
1805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1808 additionalProperties
=> 0,
1810 node
=> get_standard_option
('pve-node'),
1811 vmid
=> get_standard_option
('pve-vmid'),
1814 returns
=> { type
=> 'object' },
1819 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1821 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1822 my $status = $vmstatus->{$param->{vmid
}};
1824 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1826 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1828 $status->{agent
} = 1 if $conf->{agent
};
1833 __PACKAGE__-
>register_method({
1835 path
=> '{vmid}/status/start',
1839 description
=> "Start virtual machine.",
1841 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1844 additionalProperties
=> 0,
1846 node
=> get_standard_option
('pve-node'),
1847 vmid
=> get_standard_option
('pve-vmid',
1848 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1849 skiplock
=> get_standard_option
('skiplock'),
1850 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1851 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1854 enum
=> ['secure', 'insecure'],
1855 description
=> "Migration traffic is encrypted using an SSH " .
1856 "tunnel by default. On secure, completely private networks " .
1857 "this can be disabled to increase performance.",
1860 migration_network
=> {
1861 type
=> 'string', format
=> 'CIDR',
1862 description
=> "CIDR of the (sub) network that is used for migration.",
1865 machine
=> get_standard_option
('pve-qm-machine'),
1867 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1879 my $rpcenv = PVE
::RPCEnvironment
::get
();
1881 my $authuser = $rpcenv->get_user();
1883 my $node = extract_param
($param, 'node');
1885 my $vmid = extract_param
($param, 'vmid');
1887 my $machine = extract_param
($param, 'machine');
1889 my $stateuri = extract_param
($param, 'stateuri');
1890 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1891 if $stateuri && $authuser ne 'root@pam';
1893 my $skiplock = extract_param
($param, 'skiplock');
1894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1895 if $skiplock && $authuser ne 'root@pam';
1897 my $migratedfrom = extract_param
($param, 'migratedfrom');
1898 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1899 if $migratedfrom && $authuser ne 'root@pam';
1901 my $migration_type = extract_param
($param, 'migration_type');
1902 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1903 if $migration_type && $authuser ne 'root@pam';
1905 my $migration_network = extract_param
($param, 'migration_network');
1906 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1907 if $migration_network && $authuser ne 'root@pam';
1909 my $targetstorage = extract_param
($param, 'targetstorage');
1910 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1911 if $targetstorage && $authuser ne 'root@pam';
1913 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1914 if $targetstorage && !$migratedfrom;
1916 # read spice ticket from STDIN
1918 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1919 if (defined(my $line = <STDIN
>)) {
1921 $spice_ticket = $line;
1925 PVE
::Cluster
::check_cfs_quorum
();
1927 my $storecfg = PVE
::Storage
::config
();
1929 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1930 $rpcenv->{type
} ne 'ha') {
1935 my $service = "vm:$vmid";
1937 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1939 print "Requesting HA start for VM $vmid\n";
1941 PVE
::Tools
::run_command
($cmd);
1946 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1953 syslog
('info', "start VM $vmid: $upid\n");
1955 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1956 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1961 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1965 __PACKAGE__-
>register_method({
1967 path
=> '{vmid}/status/stop',
1971 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1972 "is akin to pulling the power plug of a running computer and may damage the VM data",
1974 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1977 additionalProperties
=> 0,
1979 node
=> get_standard_option
('pve-node'),
1980 vmid
=> get_standard_option
('pve-vmid',
1981 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1982 skiplock
=> get_standard_option
('skiplock'),
1983 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1985 description
=> "Wait maximal timeout seconds.",
1991 description
=> "Do not deactivate storage volumes.",
2004 my $rpcenv = PVE
::RPCEnvironment
::get
();
2006 my $authuser = $rpcenv->get_user();
2008 my $node = extract_param
($param, 'node');
2010 my $vmid = extract_param
($param, 'vmid');
2012 my $skiplock = extract_param
($param, 'skiplock');
2013 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2014 if $skiplock && $authuser ne 'root@pam';
2016 my $keepActive = extract_param
($param, 'keepActive');
2017 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2018 if $keepActive && $authuser ne 'root@pam';
2020 my $migratedfrom = extract_param
($param, 'migratedfrom');
2021 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2022 if $migratedfrom && $authuser ne 'root@pam';
2025 my $storecfg = PVE
::Storage
::config
();
2027 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2032 my $service = "vm:$vmid";
2034 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2036 print "Requesting HA stop for VM $vmid\n";
2038 PVE
::Tools
::run_command
($cmd);
2043 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2049 syslog
('info', "stop VM $vmid: $upid\n");
2051 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2052 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2057 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2061 __PACKAGE__-
>register_method({
2063 path
=> '{vmid}/status/reset',
2067 description
=> "Reset virtual machine.",
2069 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2072 additionalProperties
=> 0,
2074 node
=> get_standard_option
('pve-node'),
2075 vmid
=> get_standard_option
('pve-vmid',
2076 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2077 skiplock
=> get_standard_option
('skiplock'),
2086 my $rpcenv = PVE
::RPCEnvironment
::get
();
2088 my $authuser = $rpcenv->get_user();
2090 my $node = extract_param
($param, 'node');
2092 my $vmid = extract_param
($param, 'vmid');
2094 my $skiplock = extract_param
($param, 'skiplock');
2095 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2096 if $skiplock && $authuser ne 'root@pam';
2098 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2103 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2108 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2111 __PACKAGE__-
>register_method({
2112 name
=> 'vm_shutdown',
2113 path
=> '{vmid}/status/shutdown',
2117 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2118 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2120 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2123 additionalProperties
=> 0,
2125 node
=> get_standard_option
('pve-node'),
2126 vmid
=> get_standard_option
('pve-vmid',
2127 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2128 skiplock
=> get_standard_option
('skiplock'),
2130 description
=> "Wait maximal timeout seconds.",
2136 description
=> "Make sure the VM stops.",
2142 description
=> "Do not deactivate storage volumes.",
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $skiplock = extract_param
($param, 'skiplock');
2164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2165 if $skiplock && $authuser ne 'root@pam';
2167 my $keepActive = extract_param
($param, 'keepActive');
2168 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2169 if $keepActive && $authuser ne 'root@pam';
2171 my $storecfg = PVE
::Storage
::config
();
2175 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2176 # otherwise, we will infer a shutdown command, but run into the timeout,
2177 # then when the vm is resumed, it will instantly shutdown
2179 # checking the qmp status here to get feedback to the gui/cli/api
2180 # and the status query should not take too long
2183 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2187 if (!$err && $qmpstatus->{status
} eq "paused") {
2188 if ($param->{forceStop
}) {
2189 warn "VM is paused - stop instead of shutdown\n";
2192 die "VM is paused - cannot shutdown\n";
2196 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2197 ($rpcenv->{type
} ne 'ha')) {
2202 my $service = "vm:$vmid";
2204 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2206 print "Requesting HA stop for VM $vmid\n";
2208 PVE
::Tools
::run_command
($cmd);
2213 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2220 syslog
('info', "shutdown VM $vmid: $upid\n");
2222 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2223 $shutdown, $param->{forceStop
}, $keepActive);
2228 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2232 __PACKAGE__-
>register_method({
2233 name
=> 'vm_suspend',
2234 path
=> '{vmid}/status/suspend',
2238 description
=> "Suspend virtual machine.",
2240 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2243 additionalProperties
=> 0,
2245 node
=> get_standard_option
('pve-node'),
2246 vmid
=> get_standard_option
('pve-vmid',
2247 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2248 skiplock
=> get_standard_option
('skiplock'),
2257 my $rpcenv = PVE
::RPCEnvironment
::get
();
2259 my $authuser = $rpcenv->get_user();
2261 my $node = extract_param
($param, 'node');
2263 my $vmid = extract_param
($param, 'vmid');
2265 my $skiplock = extract_param
($param, 'skiplock');
2266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2267 if $skiplock && $authuser ne 'root@pam';
2269 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2274 syslog
('info', "suspend VM $vmid: $upid\n");
2276 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2281 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2284 __PACKAGE__-
>register_method({
2285 name
=> 'vm_resume',
2286 path
=> '{vmid}/status/resume',
2290 description
=> "Resume virtual machine.",
2292 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2295 additionalProperties
=> 0,
2297 node
=> get_standard_option
('pve-node'),
2298 vmid
=> get_standard_option
('pve-vmid',
2299 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2300 skiplock
=> get_standard_option
('skiplock'),
2301 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2311 my $rpcenv = PVE
::RPCEnvironment
::get
();
2313 my $authuser = $rpcenv->get_user();
2315 my $node = extract_param
($param, 'node');
2317 my $vmid = extract_param
($param, 'vmid');
2319 my $skiplock = extract_param
($param, 'skiplock');
2320 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2321 if $skiplock && $authuser ne 'root@pam';
2323 my $nocheck = extract_param
($param, 'nocheck');
2325 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2330 syslog
('info', "resume VM $vmid: $upid\n");
2332 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2337 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2340 __PACKAGE__-
>register_method({
2341 name
=> 'vm_sendkey',
2342 path
=> '{vmid}/sendkey',
2346 description
=> "Send key event to virtual machine.",
2348 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2351 additionalProperties
=> 0,
2353 node
=> get_standard_option
('pve-node'),
2354 vmid
=> get_standard_option
('pve-vmid',
2355 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2356 skiplock
=> get_standard_option
('skiplock'),
2358 description
=> "The key (qemu monitor encoding).",
2363 returns
=> { type
=> 'null'},
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2369 my $authuser = $rpcenv->get_user();
2371 my $node = extract_param
($param, 'node');
2373 my $vmid = extract_param
($param, 'vmid');
2375 my $skiplock = extract_param
($param, 'skiplock');
2376 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2377 if $skiplock && $authuser ne 'root@pam';
2379 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2384 __PACKAGE__-
>register_method({
2385 name
=> 'vm_feature',
2386 path
=> '{vmid}/feature',
2390 description
=> "Check if feature for virtual machine is available.",
2392 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2395 additionalProperties
=> 0,
2397 node
=> get_standard_option
('pve-node'),
2398 vmid
=> get_standard_option
('pve-vmid'),
2400 description
=> "Feature to check.",
2402 enum
=> [ 'snapshot', 'clone', 'copy' ],
2404 snapname
=> get_standard_option
('pve-snapshot-name', {
2412 hasFeature
=> { type
=> 'boolean' },
2415 items
=> { type
=> 'string' },
2422 my $node = extract_param
($param, 'node');
2424 my $vmid = extract_param
($param, 'vmid');
2426 my $snapname = extract_param
($param, 'snapname');
2428 my $feature = extract_param
($param, 'feature');
2430 my $running = PVE
::QemuServer
::check_running
($vmid);
2432 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2435 my $snap = $conf->{snapshots
}->{$snapname};
2436 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2439 my $storecfg = PVE
::Storage
::config
();
2441 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2442 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2445 hasFeature
=> $hasFeature,
2446 nodes
=> [ keys %$nodelist ],
2450 __PACKAGE__-
>register_method({
2452 path
=> '{vmid}/clone',
2456 description
=> "Create a copy of virtual machine/template.",
2458 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2459 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2460 "'Datastore.AllocateSpace' on any used storage.",
2463 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2465 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2466 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2471 additionalProperties
=> 0,
2473 node
=> get_standard_option
('pve-node'),
2474 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2475 newid
=> get_standard_option
('pve-vmid', {
2476 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2477 description
=> 'VMID for the clone.' }),
2480 type
=> 'string', format
=> 'dns-name',
2481 description
=> "Set a name for the new VM.",
2486 description
=> "Description for the new VM.",
2490 type
=> 'string', format
=> 'pve-poolid',
2491 description
=> "Add the new VM to the specified pool.",
2493 snapname
=> get_standard_option
('pve-snapshot-name', {
2496 storage
=> get_standard_option
('pve-storage-id', {
2497 description
=> "Target storage for full clone.",
2501 description
=> "Target format for file storage. Only valid for full clone.",
2504 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2509 description
=> "Create a full copy of all disks. This is always done when " .
2510 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2512 target
=> get_standard_option
('pve-node', {
2513 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2524 my $rpcenv = PVE
::RPCEnvironment
::get
();
2526 my $authuser = $rpcenv->get_user();
2528 my $node = extract_param
($param, 'node');
2530 my $vmid = extract_param
($param, 'vmid');
2532 my $newid = extract_param
($param, 'newid');
2534 my $pool = extract_param
($param, 'pool');
2536 if (defined($pool)) {
2537 $rpcenv->check_pool_exist($pool);
2540 my $snapname = extract_param
($param, 'snapname');
2542 my $storage = extract_param
($param, 'storage');
2544 my $format = extract_param
($param, 'format');
2546 my $target = extract_param
($param, 'target');
2548 my $localnode = PVE
::INotify
::nodename
();
2550 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2552 PVE
::Cluster
::check_node_exists
($target) if $target;
2554 my $storecfg = PVE
::Storage
::config
();
2557 # check if storage is enabled on local node
2558 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2560 # check if storage is available on target node
2561 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2562 # clone only works if target storage is shared
2563 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2564 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2568 PVE
::Cluster
::check_cfs_quorum
();
2570 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2572 # exclusive lock if VM is running - else shared lock is enough;
2573 my $shared_lock = $running ?
0 : 1;
2577 # do all tests after lock
2578 # we also try to do all tests before we fork the worker
2580 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2582 PVE
::QemuConfig-
>check_lock($conf);
2584 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2586 die "unexpected state change\n" if $verify_running != $running;
2588 die "snapshot '$snapname' does not exist\n"
2589 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2591 my $full = extract_param
($param, 'full');
2592 if (!defined($full)) {
2593 $full = !PVE
::QemuConfig-
>is_template($conf);
2596 die "parameter 'storage' not allowed for linked clones\n"
2597 if defined($storage) && !$full;
2599 die "parameter 'format' not allowed for linked clones\n"
2600 if defined($format) && !$full;
2602 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2604 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2606 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2608 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2610 die "unable to create VM $newid: config file already exists\n"
2613 my $newconf = { lock => 'clone' };
2618 foreach my $opt (keys %$oldconf) {
2619 my $value = $oldconf->{$opt};
2621 # do not copy snapshot related info
2622 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2623 $opt eq 'vmstate' || $opt eq 'snapstate';
2625 # no need to copy unused images, because VMID(owner) changes anyways
2626 next if $opt =~ m/^unused\d+$/;
2628 # always change MAC! address
2629 if ($opt =~ m/^net(\d+)$/) {
2630 my $net = PVE
::QemuServer
::parse_net
($value);
2631 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2632 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2633 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2634 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2635 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2636 die "unable to parse drive options for '$opt'\n" if !$drive;
2637 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2638 $newconf->{$opt} = $value; # simply copy configuration
2640 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2641 die "Full clone feature is not supported for drive '$opt'\n"
2642 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2643 $fullclone->{$opt} = 1;
2645 # not full means clone instead of copy
2646 die "Linked clone feature is not supported for drive '$opt'\n"
2647 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2649 $drives->{$opt} = $drive;
2650 push @$vollist, $drive->{file
};
2653 # copy everything else
2654 $newconf->{$opt} = $value;
2658 # auto generate a new uuid
2659 my ($uuid, $uuid_str);
2660 UUID
::generate
($uuid);
2661 UUID
::unparse
($uuid, $uuid_str);
2662 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2663 $smbios1->{uuid
} = $uuid_str;
2664 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2666 delete $newconf->{template
};
2668 if ($param->{name
}) {
2669 $newconf->{name
} = $param->{name
};
2671 if ($oldconf->{name
}) {
2672 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2674 $newconf->{name
} = "Copy-of-VM-$vmid";
2678 if ($param->{description
}) {
2679 $newconf->{description
} = $param->{description
};
2682 # create empty/temp config - this fails if VM already exists on other node
2683 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2688 my $newvollist = [];
2695 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2697 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2699 my $total_jobs = scalar(keys %{$drives});
2702 foreach my $opt (keys %$drives) {
2703 my $drive = $drives->{$opt};
2704 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2706 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2707 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2708 $jobs, $skipcomplete, $oldconf->{agent
});
2710 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2712 PVE
::QemuConfig-
>write_config($newid, $newconf);
2716 delete $newconf->{lock};
2717 PVE
::QemuConfig-
>write_config($newid, $newconf);
2720 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2721 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2722 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2724 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2725 die "Failed to move config to node '$target' - rename failed: $!\n"
2726 if !rename($conffile, $newconffile);
2729 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2734 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2736 sleep 1; # some storage like rbd need to wait before release volume - really?
2738 foreach my $volid (@$newvollist) {
2739 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2742 die "clone failed: $err";
2748 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2750 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2753 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2754 # Aquire exclusive lock lock for $newid
2755 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2760 __PACKAGE__-
>register_method({
2761 name
=> 'move_vm_disk',
2762 path
=> '{vmid}/move_disk',
2766 description
=> "Move volume to different storage.",
2768 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2770 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2771 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2775 additionalProperties
=> 0,
2777 node
=> get_standard_option
('pve-node'),
2778 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2781 description
=> "The disk you want to move.",
2782 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2784 storage
=> get_standard_option
('pve-storage-id', {
2785 description
=> "Target storage.",
2786 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2790 description
=> "Target Format.",
2791 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2796 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2802 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2810 description
=> "the task ID.",
2815 my $rpcenv = PVE
::RPCEnvironment
::get
();
2817 my $authuser = $rpcenv->get_user();
2819 my $node = extract_param
($param, 'node');
2821 my $vmid = extract_param
($param, 'vmid');
2823 my $digest = extract_param
($param, 'digest');
2825 my $disk = extract_param
($param, 'disk');
2827 my $storeid = extract_param
($param, 'storage');
2829 my $format = extract_param
($param, 'format');
2831 my $storecfg = PVE
::Storage
::config
();
2833 my $updatefn = sub {
2835 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2837 PVE
::QemuConfig-
>check_lock($conf);
2839 die "checksum missmatch (file change by other user?)\n"
2840 if $digest && $digest ne $conf->{digest
};
2842 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2844 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2846 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2848 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2851 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2852 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2856 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2857 (!$format || !$oldfmt || $oldfmt eq $format);
2859 # this only checks snapshots because $disk is passed!
2860 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2861 die "you can't move a disk with snapshots and delete the source\n"
2862 if $snapshotted && $param->{delete};
2864 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2866 my $running = PVE
::QemuServer
::check_running
($vmid);
2868 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2872 my $newvollist = [];
2878 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2880 warn "moving disk with snapshots, snapshots will not be moved!\n"
2883 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2884 $vmid, $storeid, $format, 1, $newvollist);
2886 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2888 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2890 # convert moved disk to base if part of template
2891 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2892 if PVE
::QemuConfig-
>is_template($conf);
2894 PVE
::QemuConfig-
>write_config($vmid, $conf);
2897 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2898 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2905 foreach my $volid (@$newvollist) {
2906 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2909 die "storage migration failed: $err";
2912 if ($param->{delete}) {
2914 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2915 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2921 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2924 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2927 __PACKAGE__-
>register_method({
2928 name
=> 'migrate_vm',
2929 path
=> '{vmid}/migrate',
2933 description
=> "Migrate virtual machine. Creates a new migration task.",
2935 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2938 additionalProperties
=> 0,
2940 node
=> get_standard_option
('pve-node'),
2941 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2942 target
=> get_standard_option
('pve-node', {
2943 description
=> "Target node.",
2944 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2948 description
=> "Use online/live migration.",
2953 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2958 enum
=> ['secure', 'insecure'],
2959 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2962 migration_network
=> {
2963 type
=> 'string', format
=> 'CIDR',
2964 description
=> "CIDR of the (sub) network that is used for migration.",
2967 "with-local-disks" => {
2969 description
=> "Enable live storage migration for local disk",
2972 targetstorage
=> get_standard_option
('pve-storage-id', {
2973 description
=> "Default target storage.",
2975 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2981 description
=> "the task ID.",
2986 my $rpcenv = PVE
::RPCEnvironment
::get
();
2988 my $authuser = $rpcenv->get_user();
2990 my $target = extract_param
($param, 'target');
2992 my $localnode = PVE
::INotify
::nodename
();
2993 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2995 PVE
::Cluster
::check_cfs_quorum
();
2997 PVE
::Cluster
::check_node_exists
($target);
2999 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3001 my $vmid = extract_param
($param, 'vmid');
3003 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3004 if !$param->{online
} && $param->{targetstorage
};
3006 raise_param_exc
({ force
=> "Only root may use this option." })
3007 if $param->{force
} && $authuser ne 'root@pam';
3009 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3010 if $param->{migration_type
} && $authuser ne 'root@pam';
3012 # allow root only until better network permissions are available
3013 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3014 if $param->{migration_network
} && $authuser ne 'root@pam';
3017 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3019 # try to detect errors early
3021 PVE
::QemuConfig-
>check_lock($conf);
3023 if (PVE
::QemuServer
::check_running
($vmid)) {
3024 die "cant migrate running VM without --online\n"
3025 if !$param->{online
};
3028 my $storecfg = PVE
::Storage
::config
();
3030 if( $param->{targetstorage
}) {
3031 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3033 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3036 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3041 my $service = "vm:$vmid";
3043 my $cmd = ['ha-manager', 'migrate', $service, $target];
3045 print "Requesting HA migration for VM $vmid to node $target\n";
3047 PVE
::Tools
::run_command
($cmd);
3052 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3057 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3061 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3064 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3069 __PACKAGE__-
>register_method({
3071 path
=> '{vmid}/monitor',
3075 description
=> "Execute Qemu monitor commands.",
3077 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3081 additionalProperties
=> 0,
3083 node
=> get_standard_option
('pve-node'),
3084 vmid
=> get_standard_option
('pve-vmid'),
3087 description
=> "The monitor command.",
3091 returns
=> { type
=> 'string'},
3095 my $rpcenv = PVE
::RPCEnvironment
::get
();
3096 my $authuser = $rpcenv->get_user();
3099 my $command = shift;
3100 return $command =~ m/^\s*info(\s+|$)/
3101 || $command =~ m/^\s*help\s*$/;
3104 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3105 if !&$is_ro($param->{command
});
3107 my $vmid = $param->{vmid
};
3109 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3113 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3115 $res = "ERROR: $@" if $@;
3120 __PACKAGE__-
>register_method({
3121 name
=> 'resize_vm',
3122 path
=> '{vmid}/resize',
3126 description
=> "Extend volume size.",
3128 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3131 additionalProperties
=> 0,
3133 node
=> get_standard_option
('pve-node'),
3134 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3135 skiplock
=> get_standard_option
('skiplock'),
3138 description
=> "The disk you want to resize.",
3139 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3143 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3144 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.",
3148 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3154 returns
=> { type
=> 'null'},
3158 my $rpcenv = PVE
::RPCEnvironment
::get
();
3160 my $authuser = $rpcenv->get_user();
3162 my $node = extract_param
($param, 'node');
3164 my $vmid = extract_param
($param, 'vmid');
3166 my $digest = extract_param
($param, 'digest');
3168 my $disk = extract_param
($param, 'disk');
3170 my $sizestr = extract_param
($param, 'size');
3172 my $skiplock = extract_param
($param, 'skiplock');
3173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3174 if $skiplock && $authuser ne 'root@pam';
3176 my $storecfg = PVE
::Storage
::config
();
3178 my $updatefn = sub {
3180 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3182 die "checksum missmatch (file change by other user?)\n"
3183 if $digest && $digest ne $conf->{digest
};
3184 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3186 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3188 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3190 my (undef, undef, undef, undef, undef, undef, $format) =
3191 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3193 die "can't resize volume: $disk if snapshot exists\n"
3194 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3196 my $volid = $drive->{file
};
3198 die "disk '$disk' has no associated volume\n" if !$volid;
3200 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3202 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3204 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3206 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3209 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3210 my ($ext, $newsize, $unit) = ($1, $2, $4);
3213 $newsize = $newsize * 1024;
3214 } elsif ($unit eq 'M') {
3215 $newsize = $newsize * 1024 * 1024;
3216 } elsif ($unit eq 'G') {
3217 $newsize = $newsize * 1024 * 1024 * 1024;
3218 } elsif ($unit eq 'T') {
3219 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3222 $newsize += $size if $ext;
3223 $newsize = int($newsize);
3225 die "shrinking disks is not supported\n" if $newsize < $size;
3227 return if $size == $newsize;
3229 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3231 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3233 $drive->{size
} = $newsize;
3234 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3236 PVE
::QemuConfig-
>write_config($vmid, $conf);
3239 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3243 __PACKAGE__-
>register_method({
3244 name
=> 'snapshot_list',
3245 path
=> '{vmid}/snapshot',
3247 description
=> "List all snapshots.",
3249 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3252 protected
=> 1, # qemu pid files are only readable by root
3254 additionalProperties
=> 0,
3256 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3257 node
=> get_standard_option
('pve-node'),
3266 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3271 my $vmid = $param->{vmid
};
3273 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3274 my $snaphash = $conf->{snapshots
} || {};
3278 foreach my $name (keys %$snaphash) {
3279 my $d = $snaphash->{$name};
3282 snaptime
=> $d->{snaptime
} || 0,
3283 vmstate
=> $d->{vmstate
} ?
1 : 0,
3284 description
=> $d->{description
} || '',
3286 $item->{parent
} = $d->{parent
} if $d->{parent
};
3287 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3291 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3292 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3293 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3295 push @$res, $current;
3300 __PACKAGE__-
>register_method({
3302 path
=> '{vmid}/snapshot',
3306 description
=> "Snapshot a VM.",
3308 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3311 additionalProperties
=> 0,
3313 node
=> get_standard_option
('pve-node'),
3314 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3315 snapname
=> get_standard_option
('pve-snapshot-name'),
3319 description
=> "Save the vmstate",
3324 description
=> "A textual description or comment.",
3330 description
=> "the task ID.",
3335 my $rpcenv = PVE
::RPCEnvironment
::get
();
3337 my $authuser = $rpcenv->get_user();
3339 my $node = extract_param
($param, 'node');
3341 my $vmid = extract_param
($param, 'vmid');
3343 my $snapname = extract_param
($param, 'snapname');
3345 die "unable to use snapshot name 'current' (reserved name)\n"
3346 if $snapname eq 'current';
3349 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3350 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3351 $param->{description
});
3354 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3357 __PACKAGE__-
>register_method({
3358 name
=> 'snapshot_cmd_idx',
3359 path
=> '{vmid}/snapshot/{snapname}',
3366 additionalProperties
=> 0,
3368 vmid
=> get_standard_option
('pve-vmid'),
3369 node
=> get_standard_option
('pve-node'),
3370 snapname
=> get_standard_option
('pve-snapshot-name'),
3379 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3386 push @$res, { cmd
=> 'rollback' };
3387 push @$res, { cmd
=> 'config' };
3392 __PACKAGE__-
>register_method({
3393 name
=> 'update_snapshot_config',
3394 path
=> '{vmid}/snapshot/{snapname}/config',
3398 description
=> "Update snapshot metadata.",
3400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3403 additionalProperties
=> 0,
3405 node
=> get_standard_option
('pve-node'),
3406 vmid
=> get_standard_option
('pve-vmid'),
3407 snapname
=> get_standard_option
('pve-snapshot-name'),
3411 description
=> "A textual description or comment.",
3415 returns
=> { type
=> 'null' },
3419 my $rpcenv = PVE
::RPCEnvironment
::get
();
3421 my $authuser = $rpcenv->get_user();
3423 my $vmid = extract_param
($param, 'vmid');
3425 my $snapname = extract_param
($param, 'snapname');
3427 return undef if !defined($param->{description
});
3429 my $updatefn = sub {
3431 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3433 PVE
::QemuConfig-
>check_lock($conf);
3435 my $snap = $conf->{snapshots
}->{$snapname};
3437 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3439 $snap->{description
} = $param->{description
} if defined($param->{description
});
3441 PVE
::QemuConfig-
>write_config($vmid, $conf);
3444 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3449 __PACKAGE__-
>register_method({
3450 name
=> 'get_snapshot_config',
3451 path
=> '{vmid}/snapshot/{snapname}/config',
3454 description
=> "Get snapshot configuration",
3456 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3459 additionalProperties
=> 0,
3461 node
=> get_standard_option
('pve-node'),
3462 vmid
=> get_standard_option
('pve-vmid'),
3463 snapname
=> get_standard_option
('pve-snapshot-name'),
3466 returns
=> { type
=> "object" },
3470 my $rpcenv = PVE
::RPCEnvironment
::get
();
3472 my $authuser = $rpcenv->get_user();
3474 my $vmid = extract_param
($param, 'vmid');
3476 my $snapname = extract_param
($param, 'snapname');
3478 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3480 my $snap = $conf->{snapshots
}->{$snapname};
3482 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3487 __PACKAGE__-
>register_method({
3489 path
=> '{vmid}/snapshot/{snapname}/rollback',
3493 description
=> "Rollback VM state to specified snapshot.",
3495 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3498 additionalProperties
=> 0,
3500 node
=> get_standard_option
('pve-node'),
3501 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3502 snapname
=> get_standard_option
('pve-snapshot-name'),
3507 description
=> "the task ID.",
3512 my $rpcenv = PVE
::RPCEnvironment
::get
();
3514 my $authuser = $rpcenv->get_user();
3516 my $node = extract_param
($param, 'node');
3518 my $vmid = extract_param
($param, 'vmid');
3520 my $snapname = extract_param
($param, 'snapname');
3523 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3524 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3528 # hold migration lock, this makes sure that nobody create replication snapshots
3529 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3532 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3535 __PACKAGE__-
>register_method({
3536 name
=> 'delsnapshot',
3537 path
=> '{vmid}/snapshot/{snapname}',
3541 description
=> "Delete a VM snapshot.",
3543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3546 additionalProperties
=> 0,
3548 node
=> get_standard_option
('pve-node'),
3549 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3550 snapname
=> get_standard_option
('pve-snapshot-name'),
3554 description
=> "For removal from config file, even if removing disk snapshots fails.",
3560 description
=> "the task ID.",
3565 my $rpcenv = PVE
::RPCEnvironment
::get
();
3567 my $authuser = $rpcenv->get_user();
3569 my $node = extract_param
($param, 'node');
3571 my $vmid = extract_param
($param, 'vmid');
3573 my $snapname = extract_param
($param, 'snapname');
3576 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3577 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3580 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3583 __PACKAGE__-
>register_method({
3585 path
=> '{vmid}/template',
3589 description
=> "Create a Template.",
3591 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3592 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3595 additionalProperties
=> 0,
3597 node
=> get_standard_option
('pve-node'),
3598 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3602 description
=> "If you want to convert only 1 disk to base image.",
3603 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3608 returns
=> { type
=> 'null'},
3612 my $rpcenv = PVE
::RPCEnvironment
::get
();
3614 my $authuser = $rpcenv->get_user();
3616 my $node = extract_param
($param, 'node');
3618 my $vmid = extract_param
($param, 'vmid');
3620 my $disk = extract_param
($param, 'disk');
3622 my $updatefn = sub {
3624 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3626 PVE
::QemuConfig-
>check_lock($conf);
3628 die "unable to create template, because VM contains snapshots\n"
3629 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3631 die "you can't convert a template to a template\n"
3632 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3634 die "you can't convert a VM to template if VM is running\n"
3635 if PVE
::QemuServer
::check_running
($vmid);
3638 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3641 $conf->{template
} = 1;
3642 PVE
::QemuConfig-
>write_config($vmid, $conf);
3644 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3647 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);