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 $cloudinitoptions = {
303 my $check_vm_modify_config_perm = sub {
304 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
306 return 1 if $authuser eq 'root@pam';
308 foreach my $opt (@$key_list) {
309 # disk checks need to be done somewhere else
310 next if PVE
::QemuServer
::is_valid_drivename
($opt);
311 next if $opt eq 'cdrom';
312 next if $opt =~ m/^unused\d+$/;
314 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
315 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
316 } elsif ($memoryoptions->{$opt}) {
317 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
318 } elsif ($hwtypeoptions->{$opt}) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
320 } elsif ($generaloptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
322 # special case for startup since it changes host behaviour
323 if ($opt eq 'startup') {
324 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
326 } elsif ($vmpoweroptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
328 } elsif ($diskoptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
330 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 # catches usb\d+, hostpci\d+, args, lock, etc.
334 # new options will be checked here
335 die "only root can set '$opt' config\n";
342 __PACKAGE__-
>register_method({
346 description
=> "Virtual machine index (per node).",
348 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
352 protected
=> 1, # qemu pid files are only readable by root
354 additionalProperties
=> 0,
356 node
=> get_standard_option
('pve-node'),
360 description
=> "Determine the full status of active VMs.",
370 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
375 my $rpcenv = PVE
::RPCEnvironment
::get
();
376 my $authuser = $rpcenv->get_user();
378 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
381 foreach my $vmid (keys %$vmstatus) {
382 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
384 my $data = $vmstatus->{$vmid};
385 $data->{vmid
} = int($vmid);
394 __PACKAGE__-
>register_method({
398 description
=> "Create or restore a virtual machine.",
400 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
401 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
402 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
403 user
=> 'all', # check inside
408 additionalProperties
=> 0,
409 properties
=> PVE
::QemuServer
::json_config_properties
(
411 node
=> get_standard_option
('pve-node'),
412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
414 description
=> "The backup file.",
418 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
420 storage
=> get_standard_option
('pve-storage-id', {
421 description
=> "Default storage.",
423 completion
=> \
&PVE
::QemuServer
::complete_storage
,
428 description
=> "Allow to overwrite existing VM.",
429 requires
=> 'archive',
434 description
=> "Assign a unique random ethernet address.",
435 requires
=> 'archive',
439 type
=> 'string', format
=> 'pve-poolid',
440 description
=> "Add the VM to the specified pool.",
443 description
=> "Override i/o bandwidth limit (in KiB/s).",
456 my $rpcenv = PVE
::RPCEnvironment
::get
();
458 my $authuser = $rpcenv->get_user();
460 my $node = extract_param
($param, 'node');
462 my $vmid = extract_param
($param, 'vmid');
464 my $archive = extract_param
($param, 'archive');
465 my $is_restore = !!$archive;
467 my $storage = extract_param
($param, 'storage');
469 my $force = extract_param
($param, 'force');
471 my $unique = extract_param
($param, 'unique');
473 my $pool = extract_param
($param, 'pool');
475 my $bwlimit = extract_param
($param, 'bwlimit');
477 my $filename = PVE
::QemuConfig-
>config_file($vmid);
479 my $storecfg = PVE
::Storage
::config
();
481 if (defined(my $ssh_keys = $param->{sshkeys
})) {
482 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
483 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
486 PVE
::Cluster
::check_cfs_quorum
();
488 if (defined($pool)) {
489 $rpcenv->check_pool_exist($pool);
492 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
493 if defined($storage);
495 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
497 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
499 } elsif ($archive && $force && (-f
$filename) &&
500 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
501 # OK: user has VM.Backup permissions, and want to restore an existing VM
507 &$resolve_cdrom_alias($param);
509 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
511 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
513 foreach my $opt (keys %$param) {
514 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
515 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
516 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
518 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
519 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
523 PVE
::QemuServer
::add_random_macs
($param);
525 my $keystr = join(' ', keys %$param);
526 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
528 if ($archive eq '-') {
529 die "pipe requires cli environment\n"
530 if $rpcenv->{type
} ne 'cli';
532 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
533 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
537 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
539 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
540 die "$emsg $@" if $@;
542 my $restorefn = sub {
543 my $conf = PVE
::QemuConfig-
>load_config($vmid);
545 PVE
::QemuConfig-
>check_protection($conf, $emsg);
547 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
548 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
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 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
567 # ensure no old replication state are exists
568 PVE
::ReplicationState
::delete_guest_states
($vmid);
578 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
580 if (!$conf->{bootdisk
}) {
581 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
582 $conf->{bootdisk
} = $firstdisk if $firstdisk;
585 # auto generate uuid if user did not specify smbios1 option
586 if (!$conf->{smbios1
}) {
587 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
590 PVE
::QemuConfig-
>write_config($vmid, $conf);
596 foreach my $volid (@$vollist) {
597 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
603 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
606 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
609 my $worker_name = $is_restore ?
'qmrestore' : 'qmcreate';
610 my $code = $is_restore ?
$restorefn : $createfn;
612 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
615 __PACKAGE__-
>register_method({
620 description
=> "Directory index",
625 additionalProperties
=> 0,
627 node
=> get_standard_option
('pve-node'),
628 vmid
=> get_standard_option
('pve-vmid'),
636 subdir
=> { type
=> 'string' },
639 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
645 { subdir
=> 'config' },
646 { subdir
=> 'pending' },
647 { subdir
=> 'status' },
648 { subdir
=> 'unlink' },
649 { subdir
=> 'vncproxy' },
650 { subdir
=> 'termproxy' },
651 { subdir
=> 'migrate' },
652 { subdir
=> 'resize' },
653 { subdir
=> 'move' },
655 { subdir
=> 'rrddata' },
656 { subdir
=> 'monitor' },
657 { subdir
=> 'agent' },
658 { subdir
=> 'snapshot' },
659 { subdir
=> 'spiceproxy' },
660 { subdir
=> 'sendkey' },
661 { subdir
=> 'firewall' },
667 __PACKAGE__-
>register_method ({
668 subclass
=> "PVE::API2::Firewall::VM",
669 path
=> '{vmid}/firewall',
672 __PACKAGE__-
>register_method ({
673 subclass
=> "PVE::API2::Qemu::Agent",
674 path
=> '{vmid}/agent',
677 __PACKAGE__-
>register_method({
679 path
=> '{vmid}/rrd',
681 protected
=> 1, # fixme: can we avoid that?
683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
685 description
=> "Read VM RRD statistics (returns PNG)",
687 additionalProperties
=> 0,
689 node
=> get_standard_option
('pve-node'),
690 vmid
=> get_standard_option
('pve-vmid'),
692 description
=> "Specify the time frame you are interested in.",
694 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
697 description
=> "The list of datasources you want to display.",
698 type
=> 'string', format
=> 'pve-configid-list',
701 description
=> "The RRD consolidation function",
703 enum
=> [ 'AVERAGE', 'MAX' ],
711 filename
=> { type
=> 'string' },
717 return PVE
::Cluster
::create_rrd_graph
(
718 "pve2-vm/$param->{vmid}", $param->{timeframe
},
719 $param->{ds
}, $param->{cf
});
723 __PACKAGE__-
>register_method({
725 path
=> '{vmid}/rrddata',
727 protected
=> 1, # fixme: can we avoid that?
729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
731 description
=> "Read VM RRD statistics",
733 additionalProperties
=> 0,
735 node
=> get_standard_option
('pve-node'),
736 vmid
=> get_standard_option
('pve-vmid'),
738 description
=> "Specify the time frame you are interested in.",
740 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
743 description
=> "The RRD consolidation function",
745 enum
=> [ 'AVERAGE', 'MAX' ],
760 return PVE
::Cluster
::create_rrd_data
(
761 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
765 __PACKAGE__-
>register_method({
767 path
=> '{vmid}/config',
770 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
775 additionalProperties
=> 0,
777 node
=> get_standard_option
('pve-node'),
778 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
780 description
=> "Get current values (instead of pending values).",
792 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
799 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
801 delete $conf->{snapshots
};
803 if (!$param->{current
}) {
804 foreach my $opt (keys %{$conf->{pending
}}) {
805 next if $opt eq 'delete';
806 my $value = $conf->{pending
}->{$opt};
807 next if ref($value); # just to be sure
808 $conf->{$opt} = $value;
810 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
811 foreach my $opt (keys %$pending_delete_hash) {
812 delete $conf->{$opt} if $conf->{$opt};
816 delete $conf->{pending
};
818 # hide cloudinit password
819 if ($conf->{cipassword
}) {
820 $conf->{cipassword
} = '**********';
826 __PACKAGE__-
>register_method({
827 name
=> 'vm_pending',
828 path
=> '{vmid}/pending',
831 description
=> "Get virtual machine configuration, including pending changes.",
833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
836 additionalProperties
=> 0,
838 node
=> get_standard_option
('pve-node'),
839 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
848 description
=> "Configuration option name.",
852 description
=> "Current value.",
857 description
=> "Pending value.",
862 description
=> "Indicates a pending delete request if present and not 0. " .
863 "The value 2 indicates a force-delete request.",
875 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
877 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
881 foreach my $opt (keys %$conf) {
882 next if ref($conf->{$opt});
883 my $item = { key
=> $opt };
884 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
885 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
886 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
888 # hide cloudinit password
889 if ($opt eq 'cipassword') {
890 $item->{value
} = '**********' if defined($item->{value
});
891 # the trailing space so that the pending string is different
892 $item->{pending
} = '********** ' if defined($item->{pending
});
897 foreach my $opt (keys %{$conf->{pending
}}) {
898 next if $opt eq 'delete';
899 next if ref($conf->{pending
}->{$opt}); # just to be sure
900 next if defined($conf->{$opt});
901 my $item = { key
=> $opt };
902 $item->{pending
} = $conf->{pending
}->{$opt};
904 # hide cloudinit password
905 if ($opt eq 'cipassword') {
906 $item->{pending
} = '**********' if defined($item->{pending
});
911 while (my ($opt, $force) = each %$pending_delete_hash) {
912 next if $conf->{pending
}->{$opt}; # just to be sure
913 next if $conf->{$opt};
914 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
921 # POST/PUT {vmid}/config implementation
923 # The original API used PUT (idempotent) an we assumed that all operations
924 # are fast. But it turned out that almost any configuration change can
925 # involve hot-plug actions, or disk alloc/free. Such actions can take long
926 # time to complete and have side effects (not idempotent).
928 # The new implementation uses POST and forks a worker process. We added
929 # a new option 'background_delay'. If specified we wait up to
930 # 'background_delay' second for the worker task to complete. It returns null
931 # if the task is finished within that time, else we return the UPID.
933 my $update_vm_api = sub {
934 my ($param, $sync) = @_;
936 my $rpcenv = PVE
::RPCEnvironment
::get
();
938 my $authuser = $rpcenv->get_user();
940 my $node = extract_param
($param, 'node');
942 my $vmid = extract_param
($param, 'vmid');
944 my $digest = extract_param
($param, 'digest');
946 my $background_delay = extract_param
($param, 'background_delay');
948 if (defined(my $cipassword = $param->{cipassword
})) {
949 # Same logic as in cloud-init (but with the regex fixed...)
950 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
951 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
954 my @paramarr = (); # used for log message
955 foreach my $key (sort keys %$param) {
956 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
957 push @paramarr, "-$key", $value;
960 my $skiplock = extract_param
($param, 'skiplock');
961 raise_param_exc
({ skiplock
=> "Only root may use this option." })
962 if $skiplock && $authuser ne 'root@pam';
964 my $delete_str = extract_param
($param, 'delete');
966 my $revert_str = extract_param
($param, 'revert');
968 my $force = extract_param
($param, 'force');
970 if (defined(my $ssh_keys = $param->{sshkeys
})) {
971 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
972 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
975 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
977 my $storecfg = PVE
::Storage
::config
();
979 my $defaults = PVE
::QemuServer
::load_defaults
();
981 &$resolve_cdrom_alias($param);
983 # now try to verify all parameters
986 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
987 if (!PVE
::QemuServer
::option_exists
($opt)) {
988 raise_param_exc
({ revert
=> "unknown option '$opt'" });
991 raise_param_exc
({ delete => "you can't use '-$opt' and " .
992 "-revert $opt' at the same time" })
993 if defined($param->{$opt});
999 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1000 $opt = 'ide2' if $opt eq 'cdrom';
1002 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1003 "-delete $opt' at the same time" })
1004 if defined($param->{$opt});
1006 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1007 "-revert $opt' at the same time" })
1010 if (!PVE
::QemuServer
::option_exists
($opt)) {
1011 raise_param_exc
({ delete => "unknown option '$opt'" });
1017 my $repl_conf = PVE
::ReplicationConfig-
>new();
1018 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1019 my $check_replication = sub {
1021 return if !$is_replicated;
1022 my $volid = $drive->{file
};
1023 return if !$volid || !($drive->{replicate
}//1);
1024 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1025 my ($storeid, $format);
1026 if ($volid =~ $NEW_DISK_RE) {
1028 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1030 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1031 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1033 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1034 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1035 return if $scfg->{shared
};
1036 die "cannot add non-replicatable volume to a replicated VM\n";
1039 foreach my $opt (keys %$param) {
1040 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1041 # cleanup drive path
1042 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1043 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1044 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1045 $check_replication->($drive);
1046 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1047 } elsif ($opt =~ m/^net(\d+)$/) {
1049 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1050 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1054 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1056 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1058 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1060 my $updatefn = sub {
1062 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1064 die "checksum missmatch (file change by other user?)\n"
1065 if $digest && $digest ne $conf->{digest
};
1067 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1069 foreach my $opt (keys %$revert) {
1070 if (defined($conf->{$opt})) {
1071 $param->{$opt} = $conf->{$opt};
1072 } elsif (defined($conf->{pending
}->{$opt})) {
1077 if ($param->{memory
} || defined($param->{balloon
})) {
1078 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1079 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1081 die "balloon value too large (must be smaller than assigned memory)\n"
1082 if $balloon && $balloon > $maxmem;
1085 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1089 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1091 # write updates to pending section
1093 my $modified = {}; # record what $option we modify
1095 foreach my $opt (@delete) {
1096 $modified->{$opt} = 1;
1097 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1098 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1099 warn "cannot delete '$opt' - not set in current configuration!\n";
1100 $modified->{$opt} = 0;
1104 if ($opt =~ m/^unused/) {
1105 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1106 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1107 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1108 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1109 delete $conf->{$opt};
1110 PVE
::QemuConfig-
>write_config($vmid, $conf);
1112 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1113 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1114 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1115 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1116 if defined($conf->{pending
}->{$opt});
1117 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1118 PVE
::QemuConfig-
>write_config($vmid, $conf);
1120 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1121 PVE
::QemuConfig-
>write_config($vmid, $conf);
1125 foreach my $opt (keys %$param) { # add/change
1126 $modified->{$opt} = 1;
1127 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1128 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1130 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1131 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1132 # FIXME: cloudinit: CDROM or Disk?
1133 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1134 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1136 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1138 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1139 if defined($conf->{pending
}->{$opt});
1141 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1143 $conf->{pending
}->{$opt} = $param->{$opt};
1145 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1146 PVE
::QemuConfig-
>write_config($vmid, $conf);
1149 # remove pending changes when nothing changed
1150 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1151 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1152 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1154 return if !scalar(keys %{$conf->{pending
}});
1156 my $running = PVE
::QemuServer
::check_running
($vmid);
1158 # apply pending changes
1160 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1164 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1165 raise_param_exc
($errors) if scalar(keys %$errors);
1167 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1177 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1179 if ($background_delay) {
1181 # Note: It would be better to do that in the Event based HTTPServer
1182 # to avoid blocking call to sleep.
1184 my $end_time = time() + $background_delay;
1186 my $task = PVE
::Tools
::upid_decode
($upid);
1189 while (time() < $end_time) {
1190 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1192 sleep(1); # this gets interrupted when child process ends
1196 my $status = PVE
::Tools
::upid_read_status
($upid);
1197 return undef if $status eq 'OK';
1206 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1209 my $vm_config_perm_list = [
1214 'VM.Config.Network',
1216 'VM.Config.Options',
1219 __PACKAGE__-
>register_method({
1220 name
=> 'update_vm_async',
1221 path
=> '{vmid}/config',
1225 description
=> "Set virtual machine options (asynchrounous API).",
1227 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1230 additionalProperties
=> 0,
1231 properties
=> PVE
::QemuServer
::json_config_properties
(
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid'),
1235 skiplock
=> get_standard_option
('skiplock'),
1237 type
=> 'string', format
=> 'pve-configid-list',
1238 description
=> "A list of settings you want to delete.",
1242 type
=> 'string', format
=> 'pve-configid-list',
1243 description
=> "Revert a pending change.",
1248 description
=> $opt_force_description,
1250 requires
=> 'delete',
1254 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1258 background_delay
=> {
1260 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1271 code
=> $update_vm_api,
1274 __PACKAGE__-
>register_method({
1275 name
=> 'update_vm',
1276 path
=> '{vmid}/config',
1280 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1282 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1285 additionalProperties
=> 0,
1286 properties
=> PVE
::QemuServer
::json_config_properties
(
1288 node
=> get_standard_option
('pve-node'),
1289 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1290 skiplock
=> get_standard_option
('skiplock'),
1292 type
=> 'string', format
=> 'pve-configid-list',
1293 description
=> "A list of settings you want to delete.",
1297 type
=> 'string', format
=> 'pve-configid-list',
1298 description
=> "Revert a pending change.",
1303 description
=> $opt_force_description,
1305 requires
=> 'delete',
1309 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1315 returns
=> { type
=> 'null' },
1318 &$update_vm_api($param, 1);
1324 __PACKAGE__-
>register_method({
1325 name
=> 'destroy_vm',
1330 description
=> "Destroy the vm (also delete all used/owned volumes).",
1332 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1335 additionalProperties
=> 0,
1337 node
=> get_standard_option
('pve-node'),
1338 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1339 skiplock
=> get_standard_option
('skiplock'),
1348 my $rpcenv = PVE
::RPCEnvironment
::get
();
1350 my $authuser = $rpcenv->get_user();
1352 my $vmid = $param->{vmid
};
1354 my $skiplock = $param->{skiplock
};
1355 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1356 if $skiplock && $authuser ne 'root@pam';
1359 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1361 my $storecfg = PVE
::Storage
::config
();
1363 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1365 die "unable to remove VM $vmid - used in HA resources\n"
1366 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1368 # do not allow destroy if there are replication jobs
1369 my $repl_conf = PVE
::ReplicationConfig-
>new();
1370 $repl_conf->check_for_existing_jobs($vmid);
1372 # early tests (repeat after locking)
1373 die "VM $vmid is running - destroy failed\n"
1374 if PVE
::QemuServer
::check_running
($vmid);
1379 syslog
('info', "destroy VM $vmid: $upid\n");
1381 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1383 PVE
::AccessControl
::remove_vm_access
($vmid);
1385 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1388 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1391 __PACKAGE__-
>register_method({
1393 path
=> '{vmid}/unlink',
1397 description
=> "Unlink/delete disk images.",
1399 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1402 additionalProperties
=> 0,
1404 node
=> get_standard_option
('pve-node'),
1405 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "A list of disk IDs you want to delete.",
1412 description
=> $opt_force_description,
1417 returns
=> { type
=> 'null'},
1421 $param->{delete} = extract_param
($param, 'idlist');
1423 __PACKAGE__-
>update_vm($param);
1430 __PACKAGE__-
>register_method({
1432 path
=> '{vmid}/vncproxy',
1436 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1438 description
=> "Creates a TCP VNC proxy connections.",
1440 additionalProperties
=> 0,
1442 node
=> get_standard_option
('pve-node'),
1443 vmid
=> get_standard_option
('pve-vmid'),
1447 description
=> "starts websockify instead of vncproxy",
1452 additionalProperties
=> 0,
1454 user
=> { type
=> 'string' },
1455 ticket
=> { type
=> 'string' },
1456 cert
=> { type
=> 'string' },
1457 port
=> { type
=> 'integer' },
1458 upid
=> { type
=> 'string' },
1464 my $rpcenv = PVE
::RPCEnvironment
::get
();
1466 my $authuser = $rpcenv->get_user();
1468 my $vmid = $param->{vmid
};
1469 my $node = $param->{node
};
1470 my $websocket = $param->{websocket
};
1472 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1474 my $authpath = "/vms/$vmid";
1476 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1478 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1481 my ($remip, $family);
1484 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1485 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1486 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1487 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1489 $family = PVE
::Tools
::get_host_address_family
($node);
1492 my $port = PVE
::Tools
::next_vnc_port
($family);
1499 syslog
('info', "starting vnc proxy $upid\n");
1503 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1506 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1508 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1509 '-timeout', $timeout, '-authpath', $authpath,
1510 '-perm', 'Sys.Console'];
1512 if ($param->{websocket
}) {
1513 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1514 push @$cmd, '-notls', '-listen', 'localhost';
1517 push @$cmd, '-c', @$remcmd, @$termcmd;
1519 PVE
::Tools
::run_command
($cmd);
1523 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1525 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1527 my $sock = IO
::Socket
::IP-
>new(
1532 GetAddrInfoFlags
=> 0,
1533 ) or die "failed to create socket: $!\n";
1534 # Inside the worker we shouldn't have any previous alarms
1535 # running anyway...:
1537 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1539 accept(my $cli, $sock) or die "connection failed: $!\n";
1542 if (PVE
::Tools
::run_command
($cmd,
1543 output
=> '>&'.fileno($cli),
1544 input
=> '<&'.fileno($cli),
1547 die "Failed to run vncproxy.\n";
1554 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1556 PVE
::Tools
::wait_for_vnc_port
($port);
1567 __PACKAGE__-
>register_method({
1568 name
=> 'termproxy',
1569 path
=> '{vmid}/termproxy',
1573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1575 description
=> "Creates a TCP proxy connections.",
1577 additionalProperties
=> 0,
1579 node
=> get_standard_option
('pve-node'),
1580 vmid
=> get_standard_option
('pve-vmid'),
1584 enum
=> [qw(serial0 serial1 serial2 serial3)],
1585 description
=> "opens a serial terminal (defaults to display)",
1590 additionalProperties
=> 0,
1592 user
=> { type
=> 'string' },
1593 ticket
=> { type
=> 'string' },
1594 port
=> { type
=> 'integer' },
1595 upid
=> { type
=> 'string' },
1601 my $rpcenv = PVE
::RPCEnvironment
::get
();
1603 my $authuser = $rpcenv->get_user();
1605 my $vmid = $param->{vmid
};
1606 my $node = $param->{node
};
1607 my $serial = $param->{serial
};
1609 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1611 if (!defined($serial)) {
1612 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1613 $serial = $conf->{vga
};
1617 my $authpath = "/vms/$vmid";
1619 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1621 my ($remip, $family);
1623 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1624 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1626 $family = PVE
::Tools
::get_host_address_family
($node);
1629 my $port = PVE
::Tools
::next_vnc_port
($family);
1631 my $remcmd = $remip ?
1632 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1634 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1635 push @$termcmd, '-iface', $serial if $serial;
1640 syslog
('info', "starting qemu termproxy $upid\n");
1642 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1643 '--perm', 'VM.Console', '--'];
1644 push @$cmd, @$remcmd, @$termcmd;
1646 PVE
::Tools
::run_command
($cmd);
1649 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1651 PVE
::Tools
::wait_for_vnc_port
($port);
1661 __PACKAGE__-
>register_method({
1662 name
=> 'vncwebsocket',
1663 path
=> '{vmid}/vncwebsocket',
1666 description
=> "You also need to pass a valid ticket (vncticket).",
1667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1669 description
=> "Opens a weksocket for VNC traffic.",
1671 additionalProperties
=> 0,
1673 node
=> get_standard_option
('pve-node'),
1674 vmid
=> get_standard_option
('pve-vmid'),
1676 description
=> "Ticket from previous call to vncproxy.",
1681 description
=> "Port number returned by previous vncproxy call.",
1691 port
=> { type
=> 'string' },
1697 my $rpcenv = PVE
::RPCEnvironment
::get
();
1699 my $authuser = $rpcenv->get_user();
1701 my $vmid = $param->{vmid
};
1702 my $node = $param->{node
};
1704 my $authpath = "/vms/$vmid";
1706 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1708 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1710 # Note: VNC ports are acessible from outside, so we do not gain any
1711 # security if we verify that $param->{port} belongs to VM $vmid. This
1712 # check is done by verifying the VNC ticket (inside VNC protocol).
1714 my $port = $param->{port
};
1716 return { port
=> $port };
1719 __PACKAGE__-
>register_method({
1720 name
=> 'spiceproxy',
1721 path
=> '{vmid}/spiceproxy',
1726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1728 description
=> "Returns a SPICE configuration to connect to the VM.",
1730 additionalProperties
=> 0,
1732 node
=> get_standard_option
('pve-node'),
1733 vmid
=> get_standard_option
('pve-vmid'),
1734 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1737 returns
=> get_standard_option
('remote-viewer-config'),
1741 my $rpcenv = PVE
::RPCEnvironment
::get
();
1743 my $authuser = $rpcenv->get_user();
1745 my $vmid = $param->{vmid
};
1746 my $node = $param->{node
};
1747 my $proxy = $param->{proxy
};
1749 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1750 my $title = "VM $vmid";
1751 $title .= " - ". $conf->{name
} if $conf->{name
};
1753 my $port = PVE
::QemuServer
::spice_port
($vmid);
1755 my ($ticket, undef, $remote_viewer_config) =
1756 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1758 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1759 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1761 return $remote_viewer_config;
1764 __PACKAGE__-
>register_method({
1766 path
=> '{vmid}/status',
1769 description
=> "Directory index",
1774 additionalProperties
=> 0,
1776 node
=> get_standard_option
('pve-node'),
1777 vmid
=> get_standard_option
('pve-vmid'),
1785 subdir
=> { type
=> 'string' },
1788 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1794 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1797 { subdir
=> 'current' },
1798 { subdir
=> 'start' },
1799 { subdir
=> 'stop' },
1805 __PACKAGE__-
>register_method({
1806 name
=> 'vm_status',
1807 path
=> '{vmid}/status/current',
1810 protected
=> 1, # qemu pid files are only readable by root
1811 description
=> "Get virtual machine status.",
1813 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1816 additionalProperties
=> 0,
1818 node
=> get_standard_option
('pve-node'),
1819 vmid
=> get_standard_option
('pve-vmid'),
1822 returns
=> { type
=> 'object' },
1827 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1829 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1830 my $status = $vmstatus->{$param->{vmid
}};
1832 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1834 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1836 $status->{agent
} = 1 if $conf->{agent
};
1841 __PACKAGE__-
>register_method({
1843 path
=> '{vmid}/status/start',
1847 description
=> "Start virtual machine.",
1849 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1852 additionalProperties
=> 0,
1854 node
=> get_standard_option
('pve-node'),
1855 vmid
=> get_standard_option
('pve-vmid',
1856 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1857 skiplock
=> get_standard_option
('skiplock'),
1858 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1859 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1862 enum
=> ['secure', 'insecure'],
1863 description
=> "Migration traffic is encrypted using an SSH " .
1864 "tunnel by default. On secure, completely private networks " .
1865 "this can be disabled to increase performance.",
1868 migration_network
=> {
1869 type
=> 'string', format
=> 'CIDR',
1870 description
=> "CIDR of the (sub) network that is used for migration.",
1873 machine
=> get_standard_option
('pve-qm-machine'),
1875 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1887 my $rpcenv = PVE
::RPCEnvironment
::get
();
1889 my $authuser = $rpcenv->get_user();
1891 my $node = extract_param
($param, 'node');
1893 my $vmid = extract_param
($param, 'vmid');
1895 my $machine = extract_param
($param, 'machine');
1897 my $stateuri = extract_param
($param, 'stateuri');
1898 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1899 if $stateuri && $authuser ne 'root@pam';
1901 my $skiplock = extract_param
($param, 'skiplock');
1902 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1903 if $skiplock && $authuser ne 'root@pam';
1905 my $migratedfrom = extract_param
($param, 'migratedfrom');
1906 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1907 if $migratedfrom && $authuser ne 'root@pam';
1909 my $migration_type = extract_param
($param, 'migration_type');
1910 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1911 if $migration_type && $authuser ne 'root@pam';
1913 my $migration_network = extract_param
($param, 'migration_network');
1914 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1915 if $migration_network && $authuser ne 'root@pam';
1917 my $targetstorage = extract_param
($param, 'targetstorage');
1918 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1919 if $targetstorage && $authuser ne 'root@pam';
1921 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1922 if $targetstorage && !$migratedfrom;
1924 # read spice ticket from STDIN
1926 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1927 if (defined(my $line = <STDIN
>)) {
1929 $spice_ticket = $line;
1933 PVE
::Cluster
::check_cfs_quorum
();
1935 my $storecfg = PVE
::Storage
::config
();
1937 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1938 $rpcenv->{type
} ne 'ha') {
1943 my $service = "vm:$vmid";
1945 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1947 print "Requesting HA start for VM $vmid\n";
1949 PVE
::Tools
::run_command
($cmd);
1954 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1961 syslog
('info', "start VM $vmid: $upid\n");
1963 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1964 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1969 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1973 __PACKAGE__-
>register_method({
1975 path
=> '{vmid}/status/stop',
1979 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1980 "is akin to pulling the power plug of a running computer and may damage the VM data",
1982 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1985 additionalProperties
=> 0,
1987 node
=> get_standard_option
('pve-node'),
1988 vmid
=> get_standard_option
('pve-vmid',
1989 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1990 skiplock
=> get_standard_option
('skiplock'),
1991 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1993 description
=> "Wait maximal timeout seconds.",
1999 description
=> "Do not deactivate storage volumes.",
2012 my $rpcenv = PVE
::RPCEnvironment
::get
();
2014 my $authuser = $rpcenv->get_user();
2016 my $node = extract_param
($param, 'node');
2018 my $vmid = extract_param
($param, 'vmid');
2020 my $skiplock = extract_param
($param, 'skiplock');
2021 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2022 if $skiplock && $authuser ne 'root@pam';
2024 my $keepActive = extract_param
($param, 'keepActive');
2025 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2026 if $keepActive && $authuser ne 'root@pam';
2028 my $migratedfrom = extract_param
($param, 'migratedfrom');
2029 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2030 if $migratedfrom && $authuser ne 'root@pam';
2033 my $storecfg = PVE
::Storage
::config
();
2035 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2040 my $service = "vm:$vmid";
2042 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2044 print "Requesting HA stop for VM $vmid\n";
2046 PVE
::Tools
::run_command
($cmd);
2051 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2057 syslog
('info', "stop VM $vmid: $upid\n");
2059 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2060 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2065 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2069 __PACKAGE__-
>register_method({
2071 path
=> '{vmid}/status/reset',
2075 description
=> "Reset virtual machine.",
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid',
2084 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2085 skiplock
=> get_standard_option
('skiplock'),
2094 my $rpcenv = PVE
::RPCEnvironment
::get
();
2096 my $authuser = $rpcenv->get_user();
2098 my $node = extract_param
($param, 'node');
2100 my $vmid = extract_param
($param, 'vmid');
2102 my $skiplock = extract_param
($param, 'skiplock');
2103 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2104 if $skiplock && $authuser ne 'root@pam';
2106 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2111 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2116 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2119 __PACKAGE__-
>register_method({
2120 name
=> 'vm_shutdown',
2121 path
=> '{vmid}/status/shutdown',
2125 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2126 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2128 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2131 additionalProperties
=> 0,
2133 node
=> get_standard_option
('pve-node'),
2134 vmid
=> get_standard_option
('pve-vmid',
2135 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2136 skiplock
=> get_standard_option
('skiplock'),
2138 description
=> "Wait maximal timeout seconds.",
2144 description
=> "Make sure the VM stops.",
2150 description
=> "Do not deactivate storage volumes.",
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2165 my $authuser = $rpcenv->get_user();
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $skiplock = extract_param
($param, 'skiplock');
2172 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2173 if $skiplock && $authuser ne 'root@pam';
2175 my $keepActive = extract_param
($param, 'keepActive');
2176 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2177 if $keepActive && $authuser ne 'root@pam';
2179 my $storecfg = PVE
::Storage
::config
();
2183 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2184 # otherwise, we will infer a shutdown command, but run into the timeout,
2185 # then when the vm is resumed, it will instantly shutdown
2187 # checking the qmp status here to get feedback to the gui/cli/api
2188 # and the status query should not take too long
2191 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2195 if (!$err && $qmpstatus->{status
} eq "paused") {
2196 if ($param->{forceStop
}) {
2197 warn "VM is paused - stop instead of shutdown\n";
2200 die "VM is paused - cannot shutdown\n";
2204 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2205 ($rpcenv->{type
} ne 'ha')) {
2210 my $service = "vm:$vmid";
2212 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2214 print "Requesting HA stop for VM $vmid\n";
2216 PVE
::Tools
::run_command
($cmd);
2221 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2228 syslog
('info', "shutdown VM $vmid: $upid\n");
2230 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2231 $shutdown, $param->{forceStop
}, $keepActive);
2236 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2240 __PACKAGE__-
>register_method({
2241 name
=> 'vm_suspend',
2242 path
=> '{vmid}/status/suspend',
2246 description
=> "Suspend virtual machine.",
2248 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2251 additionalProperties
=> 0,
2253 node
=> get_standard_option
('pve-node'),
2254 vmid
=> get_standard_option
('pve-vmid',
2255 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2256 skiplock
=> get_standard_option
('skiplock'),
2265 my $rpcenv = PVE
::RPCEnvironment
::get
();
2267 my $authuser = $rpcenv->get_user();
2269 my $node = extract_param
($param, 'node');
2271 my $vmid = extract_param
($param, 'vmid');
2273 my $skiplock = extract_param
($param, 'skiplock');
2274 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2275 if $skiplock && $authuser ne 'root@pam';
2277 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2282 syslog
('info', "suspend VM $vmid: $upid\n");
2284 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2289 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2292 __PACKAGE__-
>register_method({
2293 name
=> 'vm_resume',
2294 path
=> '{vmid}/status/resume',
2298 description
=> "Resume virtual machine.",
2300 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2303 additionalProperties
=> 0,
2305 node
=> get_standard_option
('pve-node'),
2306 vmid
=> get_standard_option
('pve-vmid',
2307 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2308 skiplock
=> get_standard_option
('skiplock'),
2309 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2319 my $rpcenv = PVE
::RPCEnvironment
::get
();
2321 my $authuser = $rpcenv->get_user();
2323 my $node = extract_param
($param, 'node');
2325 my $vmid = extract_param
($param, 'vmid');
2327 my $skiplock = extract_param
($param, 'skiplock');
2328 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2329 if $skiplock && $authuser ne 'root@pam';
2331 my $nocheck = extract_param
($param, 'nocheck');
2333 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2338 syslog
('info', "resume VM $vmid: $upid\n");
2340 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2345 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2348 __PACKAGE__-
>register_method({
2349 name
=> 'vm_sendkey',
2350 path
=> '{vmid}/sendkey',
2354 description
=> "Send key event to virtual machine.",
2356 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2359 additionalProperties
=> 0,
2361 node
=> get_standard_option
('pve-node'),
2362 vmid
=> get_standard_option
('pve-vmid',
2363 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2364 skiplock
=> get_standard_option
('skiplock'),
2366 description
=> "The key (qemu monitor encoding).",
2371 returns
=> { type
=> 'null'},
2375 my $rpcenv = PVE
::RPCEnvironment
::get
();
2377 my $authuser = $rpcenv->get_user();
2379 my $node = extract_param
($param, 'node');
2381 my $vmid = extract_param
($param, 'vmid');
2383 my $skiplock = extract_param
($param, 'skiplock');
2384 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2385 if $skiplock && $authuser ne 'root@pam';
2387 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2392 __PACKAGE__-
>register_method({
2393 name
=> 'vm_feature',
2394 path
=> '{vmid}/feature',
2398 description
=> "Check if feature for virtual machine is available.",
2400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2403 additionalProperties
=> 0,
2405 node
=> get_standard_option
('pve-node'),
2406 vmid
=> get_standard_option
('pve-vmid'),
2408 description
=> "Feature to check.",
2410 enum
=> [ 'snapshot', 'clone', 'copy' ],
2412 snapname
=> get_standard_option
('pve-snapshot-name', {
2420 hasFeature
=> { type
=> 'boolean' },
2423 items
=> { type
=> 'string' },
2430 my $node = extract_param
($param, 'node');
2432 my $vmid = extract_param
($param, 'vmid');
2434 my $snapname = extract_param
($param, 'snapname');
2436 my $feature = extract_param
($param, 'feature');
2438 my $running = PVE
::QemuServer
::check_running
($vmid);
2440 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2443 my $snap = $conf->{snapshots
}->{$snapname};
2444 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2447 my $storecfg = PVE
::Storage
::config
();
2449 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2450 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2453 hasFeature
=> $hasFeature,
2454 nodes
=> [ keys %$nodelist ],
2458 __PACKAGE__-
>register_method({
2460 path
=> '{vmid}/clone',
2464 description
=> "Create a copy of virtual machine/template.",
2466 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2467 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2468 "'Datastore.AllocateSpace' on any used storage.",
2471 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2473 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2474 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2479 additionalProperties
=> 0,
2481 node
=> get_standard_option
('pve-node'),
2482 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2483 newid
=> get_standard_option
('pve-vmid', {
2484 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2485 description
=> 'VMID for the clone.' }),
2488 type
=> 'string', format
=> 'dns-name',
2489 description
=> "Set a name for the new VM.",
2494 description
=> "Description for the new VM.",
2498 type
=> 'string', format
=> 'pve-poolid',
2499 description
=> "Add the new VM to the specified pool.",
2501 snapname
=> get_standard_option
('pve-snapshot-name', {
2504 storage
=> get_standard_option
('pve-storage-id', {
2505 description
=> "Target storage for full clone.",
2509 description
=> "Target format for file storage. Only valid for full clone.",
2512 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2517 description
=> "Create a full copy of all disks. This is always done when " .
2518 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2520 target
=> get_standard_option
('pve-node', {
2521 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2532 my $rpcenv = PVE
::RPCEnvironment
::get
();
2534 my $authuser = $rpcenv->get_user();
2536 my $node = extract_param
($param, 'node');
2538 my $vmid = extract_param
($param, 'vmid');
2540 my $newid = extract_param
($param, 'newid');
2542 my $pool = extract_param
($param, 'pool');
2544 if (defined($pool)) {
2545 $rpcenv->check_pool_exist($pool);
2548 my $snapname = extract_param
($param, 'snapname');
2550 my $storage = extract_param
($param, 'storage');
2552 my $format = extract_param
($param, 'format');
2554 my $target = extract_param
($param, 'target');
2556 my $localnode = PVE
::INotify
::nodename
();
2558 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2560 PVE
::Cluster
::check_node_exists
($target) if $target;
2562 my $storecfg = PVE
::Storage
::config
();
2565 # check if storage is enabled on local node
2566 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2568 # check if storage is available on target node
2569 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2570 # clone only works if target storage is shared
2571 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2572 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2576 PVE
::Cluster
::check_cfs_quorum
();
2578 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2580 # exclusive lock if VM is running - else shared lock is enough;
2581 my $shared_lock = $running ?
0 : 1;
2585 # do all tests after lock
2586 # we also try to do all tests before we fork the worker
2588 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2590 PVE
::QemuConfig-
>check_lock($conf);
2592 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2594 die "unexpected state change\n" if $verify_running != $running;
2596 die "snapshot '$snapname' does not exist\n"
2597 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2599 my $full = extract_param
($param, 'full');
2600 if (!defined($full)) {
2601 $full = !PVE
::QemuConfig-
>is_template($conf);
2604 die "parameter 'storage' not allowed for linked clones\n"
2605 if defined($storage) && !$full;
2607 die "parameter 'format' not allowed for linked clones\n"
2608 if defined($format) && !$full;
2610 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2612 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2614 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2616 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2618 die "unable to create VM $newid: config file already exists\n"
2621 my $newconf = { lock => 'clone' };
2626 foreach my $opt (keys %$oldconf) {
2627 my $value = $oldconf->{$opt};
2629 # do not copy snapshot related info
2630 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2631 $opt eq 'vmstate' || $opt eq 'snapstate';
2633 # no need to copy unused images, because VMID(owner) changes anyways
2634 next if $opt =~ m/^unused\d+$/;
2636 # always change MAC! address
2637 if ($opt =~ m/^net(\d+)$/) {
2638 my $net = PVE
::QemuServer
::parse_net
($value);
2639 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2640 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2641 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2642 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2643 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2644 die "unable to parse drive options for '$opt'\n" if !$drive;
2645 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2646 $newconf->{$opt} = $value; # simply copy configuration
2648 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2649 die "Full clone feature is not supported for drive '$opt'\n"
2650 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2651 $fullclone->{$opt} = 1;
2653 # not full means clone instead of copy
2654 die "Linked clone feature is not supported for drive '$opt'\n"
2655 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2657 $drives->{$opt} = $drive;
2658 push @$vollist, $drive->{file
};
2661 # copy everything else
2662 $newconf->{$opt} = $value;
2666 # auto generate a new uuid
2667 my ($uuid, $uuid_str);
2668 UUID
::generate
($uuid);
2669 UUID
::unparse
($uuid, $uuid_str);
2670 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2671 $smbios1->{uuid
} = $uuid_str;
2672 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2674 delete $newconf->{template
};
2676 if ($param->{name
}) {
2677 $newconf->{name
} = $param->{name
};
2679 if ($oldconf->{name
}) {
2680 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2682 $newconf->{name
} = "Copy-of-VM-$vmid";
2686 if ($param->{description
}) {
2687 $newconf->{description
} = $param->{description
};
2690 # create empty/temp config - this fails if VM already exists on other node
2691 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2696 my $newvollist = [];
2703 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2705 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2707 my $total_jobs = scalar(keys %{$drives});
2710 foreach my $opt (keys %$drives) {
2711 my $drive = $drives->{$opt};
2712 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2714 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2715 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2716 $jobs, $skipcomplete, $oldconf->{agent
});
2718 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2720 PVE
::QemuConfig-
>write_config($newid, $newconf);
2724 delete $newconf->{lock};
2726 # do not write pending changes
2727 if ($newconf->{pending
}) {
2728 warn "found pending changes, discarding for clone\n";
2729 delete $newconf->{pending
};
2732 PVE
::QemuConfig-
>write_config($newid, $newconf);
2735 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2736 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2737 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2739 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2740 die "Failed to move config to node '$target' - rename failed: $!\n"
2741 if !rename($conffile, $newconffile);
2744 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2749 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2751 sleep 1; # some storage like rbd need to wait before release volume - really?
2753 foreach my $volid (@$newvollist) {
2754 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2757 die "clone failed: $err";
2763 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2765 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2768 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2769 # Aquire exclusive lock lock for $newid
2770 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2775 __PACKAGE__-
>register_method({
2776 name
=> 'move_vm_disk',
2777 path
=> '{vmid}/move_disk',
2781 description
=> "Move volume to different storage.",
2783 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2785 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2786 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2790 additionalProperties
=> 0,
2792 node
=> get_standard_option
('pve-node'),
2793 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2796 description
=> "The disk you want to move.",
2797 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2799 storage
=> get_standard_option
('pve-storage-id', {
2800 description
=> "Target storage.",
2801 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2805 description
=> "Target Format.",
2806 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2811 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2817 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2825 description
=> "the task ID.",
2830 my $rpcenv = PVE
::RPCEnvironment
::get
();
2832 my $authuser = $rpcenv->get_user();
2834 my $node = extract_param
($param, 'node');
2836 my $vmid = extract_param
($param, 'vmid');
2838 my $digest = extract_param
($param, 'digest');
2840 my $disk = extract_param
($param, 'disk');
2842 my $storeid = extract_param
($param, 'storage');
2844 my $format = extract_param
($param, 'format');
2846 my $storecfg = PVE
::Storage
::config
();
2848 my $updatefn = sub {
2850 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2852 PVE
::QemuConfig-
>check_lock($conf);
2854 die "checksum missmatch (file change by other user?)\n"
2855 if $digest && $digest ne $conf->{digest
};
2857 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2859 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2861 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2863 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2866 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2867 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2871 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2872 (!$format || !$oldfmt || $oldfmt eq $format);
2874 # this only checks snapshots because $disk is passed!
2875 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2876 die "you can't move a disk with snapshots and delete the source\n"
2877 if $snapshotted && $param->{delete};
2879 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2881 my $running = PVE
::QemuServer
::check_running
($vmid);
2883 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2887 my $newvollist = [];
2893 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2895 warn "moving disk with snapshots, snapshots will not be moved!\n"
2898 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2899 $vmid, $storeid, $format, 1, $newvollist);
2901 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2903 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2905 # convert moved disk to base if part of template
2906 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2907 if PVE
::QemuConfig-
>is_template($conf);
2909 PVE
::QemuConfig-
>write_config($vmid, $conf);
2912 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2913 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2920 foreach my $volid (@$newvollist) {
2921 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2924 die "storage migration failed: $err";
2927 if ($param->{delete}) {
2929 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2930 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2936 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2939 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2942 __PACKAGE__-
>register_method({
2943 name
=> 'migrate_vm',
2944 path
=> '{vmid}/migrate',
2948 description
=> "Migrate virtual machine. Creates a new migration task.",
2950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2953 additionalProperties
=> 0,
2955 node
=> get_standard_option
('pve-node'),
2956 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2957 target
=> get_standard_option
('pve-node', {
2958 description
=> "Target node.",
2959 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2963 description
=> "Use online/live migration.",
2968 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2973 enum
=> ['secure', 'insecure'],
2974 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2977 migration_network
=> {
2978 type
=> 'string', format
=> 'CIDR',
2979 description
=> "CIDR of the (sub) network that is used for migration.",
2982 "with-local-disks" => {
2984 description
=> "Enable live storage migration for local disk",
2987 targetstorage
=> get_standard_option
('pve-storage-id', {
2988 description
=> "Default target storage.",
2990 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2996 description
=> "the task ID.",
3001 my $rpcenv = PVE
::RPCEnvironment
::get
();
3003 my $authuser = $rpcenv->get_user();
3005 my $target = extract_param
($param, 'target');
3007 my $localnode = PVE
::INotify
::nodename
();
3008 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3010 PVE
::Cluster
::check_cfs_quorum
();
3012 PVE
::Cluster
::check_node_exists
($target);
3014 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3016 my $vmid = extract_param
($param, 'vmid');
3018 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3019 if !$param->{online
} && $param->{targetstorage
};
3021 raise_param_exc
({ force
=> "Only root may use this option." })
3022 if $param->{force
} && $authuser ne 'root@pam';
3024 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3025 if $param->{migration_type
} && $authuser ne 'root@pam';
3027 # allow root only until better network permissions are available
3028 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3029 if $param->{migration_network
} && $authuser ne 'root@pam';
3032 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3034 # try to detect errors early
3036 PVE
::QemuConfig-
>check_lock($conf);
3038 if (PVE
::QemuServer
::check_running
($vmid)) {
3039 die "cant migrate running VM without --online\n"
3040 if !$param->{online
};
3043 my $storecfg = PVE
::Storage
::config
();
3045 if( $param->{targetstorage
}) {
3046 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3048 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3051 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3056 my $service = "vm:$vmid";
3058 my $cmd = ['ha-manager', 'migrate', $service, $target];
3060 print "Requesting HA migration for VM $vmid to node $target\n";
3062 PVE
::Tools
::run_command
($cmd);
3067 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3072 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3076 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3079 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3084 __PACKAGE__-
>register_method({
3086 path
=> '{vmid}/monitor',
3090 description
=> "Execute Qemu monitor commands.",
3092 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3093 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3096 additionalProperties
=> 0,
3098 node
=> get_standard_option
('pve-node'),
3099 vmid
=> get_standard_option
('pve-vmid'),
3102 description
=> "The monitor command.",
3106 returns
=> { type
=> 'string'},
3110 my $rpcenv = PVE
::RPCEnvironment
::get
();
3111 my $authuser = $rpcenv->get_user();
3114 my $command = shift;
3115 return $command =~ m/^\s*info(\s+|$)/
3116 || $command =~ m/^\s*help\s*$/;
3119 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3120 if !&$is_ro($param->{command
});
3122 my $vmid = $param->{vmid
};
3124 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3128 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3130 $res = "ERROR: $@" if $@;
3135 __PACKAGE__-
>register_method({
3136 name
=> 'resize_vm',
3137 path
=> '{vmid}/resize',
3141 description
=> "Extend volume size.",
3143 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3146 additionalProperties
=> 0,
3148 node
=> get_standard_option
('pve-node'),
3149 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3150 skiplock
=> get_standard_option
('skiplock'),
3153 description
=> "The disk you want to resize.",
3154 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3158 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3159 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.",
3163 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3169 returns
=> { type
=> 'null'},
3173 my $rpcenv = PVE
::RPCEnvironment
::get
();
3175 my $authuser = $rpcenv->get_user();
3177 my $node = extract_param
($param, 'node');
3179 my $vmid = extract_param
($param, 'vmid');
3181 my $digest = extract_param
($param, 'digest');
3183 my $disk = extract_param
($param, 'disk');
3185 my $sizestr = extract_param
($param, 'size');
3187 my $skiplock = extract_param
($param, 'skiplock');
3188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3189 if $skiplock && $authuser ne 'root@pam';
3191 my $storecfg = PVE
::Storage
::config
();
3193 my $updatefn = sub {
3195 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3197 die "checksum missmatch (file change by other user?)\n"
3198 if $digest && $digest ne $conf->{digest
};
3199 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3201 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3203 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3205 my (undef, undef, undef, undef, undef, undef, $format) =
3206 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3208 die "can't resize volume: $disk if snapshot exists\n"
3209 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3211 my $volid = $drive->{file
};
3213 die "disk '$disk' has no associated volume\n" if !$volid;
3215 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3217 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3219 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3221 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3222 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3224 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3225 my ($ext, $newsize, $unit) = ($1, $2, $4);
3228 $newsize = $newsize * 1024;
3229 } elsif ($unit eq 'M') {
3230 $newsize = $newsize * 1024 * 1024;
3231 } elsif ($unit eq 'G') {
3232 $newsize = $newsize * 1024 * 1024 * 1024;
3233 } elsif ($unit eq 'T') {
3234 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3237 $newsize += $size if $ext;
3238 $newsize = int($newsize);
3240 die "shrinking disks is not supported\n" if $newsize < $size;
3242 return if $size == $newsize;
3244 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3246 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3248 $drive->{size
} = $newsize;
3249 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3251 PVE
::QemuConfig-
>write_config($vmid, $conf);
3254 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3258 __PACKAGE__-
>register_method({
3259 name
=> 'snapshot_list',
3260 path
=> '{vmid}/snapshot',
3262 description
=> "List all snapshots.",
3264 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3267 protected
=> 1, # qemu pid files are only readable by root
3269 additionalProperties
=> 0,
3271 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3272 node
=> get_standard_option
('pve-node'),
3281 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3286 my $vmid = $param->{vmid
};
3288 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3289 my $snaphash = $conf->{snapshots
} || {};
3293 foreach my $name (keys %$snaphash) {
3294 my $d = $snaphash->{$name};
3297 snaptime
=> $d->{snaptime
} || 0,
3298 vmstate
=> $d->{vmstate
} ?
1 : 0,
3299 description
=> $d->{description
} || '',
3301 $item->{parent
} = $d->{parent
} if $d->{parent
};
3302 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3306 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3307 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3308 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3310 push @$res, $current;
3315 __PACKAGE__-
>register_method({
3317 path
=> '{vmid}/snapshot',
3321 description
=> "Snapshot a VM.",
3323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3326 additionalProperties
=> 0,
3328 node
=> get_standard_option
('pve-node'),
3329 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3330 snapname
=> get_standard_option
('pve-snapshot-name'),
3334 description
=> "Save the vmstate",
3339 description
=> "A textual description or comment.",
3345 description
=> "the task ID.",
3350 my $rpcenv = PVE
::RPCEnvironment
::get
();
3352 my $authuser = $rpcenv->get_user();
3354 my $node = extract_param
($param, 'node');
3356 my $vmid = extract_param
($param, 'vmid');
3358 my $snapname = extract_param
($param, 'snapname');
3360 die "unable to use snapshot name 'current' (reserved name)\n"
3361 if $snapname eq 'current';
3364 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3365 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3366 $param->{description
});
3369 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3372 __PACKAGE__-
>register_method({
3373 name
=> 'snapshot_cmd_idx',
3374 path
=> '{vmid}/snapshot/{snapname}',
3381 additionalProperties
=> 0,
3383 vmid
=> get_standard_option
('pve-vmid'),
3384 node
=> get_standard_option
('pve-node'),
3385 snapname
=> get_standard_option
('pve-snapshot-name'),
3394 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3401 push @$res, { cmd
=> 'rollback' };
3402 push @$res, { cmd
=> 'config' };
3407 __PACKAGE__-
>register_method({
3408 name
=> 'update_snapshot_config',
3409 path
=> '{vmid}/snapshot/{snapname}/config',
3413 description
=> "Update snapshot metadata.",
3415 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3418 additionalProperties
=> 0,
3420 node
=> get_standard_option
('pve-node'),
3421 vmid
=> get_standard_option
('pve-vmid'),
3422 snapname
=> get_standard_option
('pve-snapshot-name'),
3426 description
=> "A textual description or comment.",
3430 returns
=> { type
=> 'null' },
3434 my $rpcenv = PVE
::RPCEnvironment
::get
();
3436 my $authuser = $rpcenv->get_user();
3438 my $vmid = extract_param
($param, 'vmid');
3440 my $snapname = extract_param
($param, 'snapname');
3442 return undef if !defined($param->{description
});
3444 my $updatefn = sub {
3446 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3448 PVE
::QemuConfig-
>check_lock($conf);
3450 my $snap = $conf->{snapshots
}->{$snapname};
3452 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3454 $snap->{description
} = $param->{description
} if defined($param->{description
});
3456 PVE
::QemuConfig-
>write_config($vmid, $conf);
3459 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3464 __PACKAGE__-
>register_method({
3465 name
=> 'get_snapshot_config',
3466 path
=> '{vmid}/snapshot/{snapname}/config',
3469 description
=> "Get snapshot configuration",
3471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3474 additionalProperties
=> 0,
3476 node
=> get_standard_option
('pve-node'),
3477 vmid
=> get_standard_option
('pve-vmid'),
3478 snapname
=> get_standard_option
('pve-snapshot-name'),
3481 returns
=> { type
=> "object" },
3485 my $rpcenv = PVE
::RPCEnvironment
::get
();
3487 my $authuser = $rpcenv->get_user();
3489 my $vmid = extract_param
($param, 'vmid');
3491 my $snapname = extract_param
($param, 'snapname');
3493 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3495 my $snap = $conf->{snapshots
}->{$snapname};
3497 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3502 __PACKAGE__-
>register_method({
3504 path
=> '{vmid}/snapshot/{snapname}/rollback',
3508 description
=> "Rollback VM state to specified snapshot.",
3510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3513 additionalProperties
=> 0,
3515 node
=> get_standard_option
('pve-node'),
3516 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3517 snapname
=> get_standard_option
('pve-snapshot-name'),
3522 description
=> "the task ID.",
3527 my $rpcenv = PVE
::RPCEnvironment
::get
();
3529 my $authuser = $rpcenv->get_user();
3531 my $node = extract_param
($param, 'node');
3533 my $vmid = extract_param
($param, 'vmid');
3535 my $snapname = extract_param
($param, 'snapname');
3538 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3539 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3543 # hold migration lock, this makes sure that nobody create replication snapshots
3544 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3547 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3550 __PACKAGE__-
>register_method({
3551 name
=> 'delsnapshot',
3552 path
=> '{vmid}/snapshot/{snapname}',
3556 description
=> "Delete a VM snapshot.",
3558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3561 additionalProperties
=> 0,
3563 node
=> get_standard_option
('pve-node'),
3564 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3565 snapname
=> get_standard_option
('pve-snapshot-name'),
3569 description
=> "For removal from config file, even if removing disk snapshots fails.",
3575 description
=> "the task ID.",
3580 my $rpcenv = PVE
::RPCEnvironment
::get
();
3582 my $authuser = $rpcenv->get_user();
3584 my $node = extract_param
($param, 'node');
3586 my $vmid = extract_param
($param, 'vmid');
3588 my $snapname = extract_param
($param, 'snapname');
3591 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3592 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3595 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3598 __PACKAGE__-
>register_method({
3600 path
=> '{vmid}/template',
3604 description
=> "Create a Template.",
3606 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3607 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3610 additionalProperties
=> 0,
3612 node
=> get_standard_option
('pve-node'),
3613 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3617 description
=> "If you want to convert only 1 disk to base image.",
3618 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3623 returns
=> { type
=> 'null'},
3627 my $rpcenv = PVE
::RPCEnvironment
::get
();
3629 my $authuser = $rpcenv->get_user();
3631 my $node = extract_param
($param, 'node');
3633 my $vmid = extract_param
($param, 'vmid');
3635 my $disk = extract_param
($param, 'disk');
3637 my $updatefn = sub {
3639 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3641 PVE
::QemuConfig-
>check_lock($conf);
3643 die "unable to create template, because VM contains snapshots\n"
3644 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3646 die "you can't convert a template to a template\n"
3647 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3649 die "you can't convert a VM to template if VM is running\n"
3650 if PVE
::QemuServer
::check_running
($vmid);
3653 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3656 $conf->{template
} = 1;
3657 PVE
::QemuConfig-
>write_config($vmid, $conf);
3659 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3662 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);