1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 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.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my $volid_is_new = 1;
134 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
135 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
140 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
142 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
144 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
146 die "volume $volid does not exists\n" if !$size;
148 $disk->{size
} = $size;
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
155 # free allocated images on error
157 syslog
('err', "VM $vmid creating disks failed");
158 foreach my $volid (@$vollist) {
159 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
165 # modify vm config if everything went well
166 foreach my $ds (keys %$res) {
167 $conf->{$ds} = $res->{$ds};
173 my $check_vm_modify_config_perm = sub {
174 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
176 return 1 if $authuser eq 'root@pam';
178 foreach my $opt (@$key_list) {
179 # disk checks need to be done somewhere else
180 next if PVE
::QemuServer
::valid_drivename
($opt);
182 if ($opt eq 'sockets' || $opt eq 'cores' ||
183 $opt eq 'cpu' || $opt eq 'smp' ||
184 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
186 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
188 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
190 } elsif ($opt eq 'args' || $opt eq 'lock') {
191 die "only root can set '$opt' config\n";
192 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
193 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
194 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
195 } elsif ($opt =~ m/^net\d+$/) {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
205 __PACKAGE__-
>register_method({
209 description
=> "Virtual machine index (per node).",
211 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
215 protected
=> 1, # qemu pid files are only readable by root
217 additionalProperties
=> 0,
219 node
=> get_standard_option
('pve-node'),
228 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
233 my $rpcenv = PVE
::RPCEnvironment
::get
();
234 my $authuser = $rpcenv->get_user();
236 my $vmstatus = PVE
::QemuServer
::vmstatus
();
239 foreach my $vmid (keys %$vmstatus) {
240 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
242 my $data = $vmstatus->{$vmid};
243 $data->{vmid
} = $vmid;
252 __PACKAGE__-
>register_method({
256 description
=> "Create or restore a virtual machine.",
258 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
259 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
260 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
261 user
=> 'all', # check inside
266 additionalProperties
=> 0,
267 properties
=> PVE
::QemuServer
::json_config_properties
(
269 node
=> get_standard_option
('pve-node'),
270 vmid
=> get_standard_option
('pve-vmid'),
272 description
=> "The backup file.",
277 storage
=> get_standard_option
('pve-storage-id', {
278 description
=> "Default storage.",
284 description
=> "Allow to overwrite existing VM.",
285 requires
=> 'archive',
290 description
=> "Assign a unique random ethernet address.",
291 requires
=> 'archive',
295 type
=> 'string', format
=> 'pve-poolid',
296 description
=> "Add the VM to the specified pool.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 my $node = extract_param
($param, 'node');
312 my $vmid = extract_param
($param, 'vmid');
314 my $archive = extract_param
($param, 'archive');
316 my $storage = extract_param
($param, 'storage');
318 my $force = extract_param
($param, 'force');
320 my $unique = extract_param
($param, 'unique');
322 my $pool = extract_param
($param, 'pool');
324 my $filename = PVE
::QemuServer
::config_file
($vmid);
326 my $storecfg = PVE
::Storage
::config
();
328 PVE
::Cluster
::check_cfs_quorum
();
330 if (defined($pool)) {
331 $rpcenv->check_pool_exist($pool);
334 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
335 if defined($storage);
337 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
339 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
341 } elsif ($archive && $force && (-f
$filename) &&
342 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
343 # OK: user has VM.Backup permissions, and want to restore an existing VM
349 &$resolve_cdrom_alias($param);
351 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
353 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
355 foreach my $opt (keys %$param) {
356 if (PVE
::QemuServer
::valid_drivename
($opt)) {
357 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
358 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
360 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
361 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
365 PVE
::QemuServer
::add_random_macs
($param);
367 my $keystr = join(' ', keys %$param);
368 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
370 if ($archive eq '-') {
371 die "pipe requires cli environment\n"
372 if $rpcenv->{type
} ne 'cli';
374 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
377 if PVE
::Storage
::parse_volume_id
($archive, 1);
379 die "can't find archive file '$archive'\n" if !($path && -f
$path);
384 my $restorefn = sub {
386 # fixme: this test does not work if VM exists on other node!
388 die "unable to restore vm $vmid: config file already exists\n"
391 die "unable to restore vm $vmid: vm is running\n"
392 if PVE
::QemuServer
::check_running
($vmid);
396 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
399 unique
=> $unique });
401 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
404 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
410 die "unable to create vm $vmid: config file already exists\n"
421 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
423 # try to be smart about bootdisk
424 my @disks = PVE
::QemuServer
::disknames
();
426 foreach my $ds (reverse @disks) {
427 next if !$conf->{$ds};
428 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
429 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
433 if (!$conf->{bootdisk
} && $firstdisk) {
434 $conf->{bootdisk
} = $firstdisk;
437 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
443 foreach my $volid (@$vollist) {
444 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
447 die "create failed - $err";
450 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
453 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
456 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
459 __PACKAGE__-
>register_method({
464 description
=> "Directory index",
469 additionalProperties
=> 0,
471 node
=> get_standard_option
('pve-node'),
472 vmid
=> get_standard_option
('pve-vmid'),
480 subdir
=> { type
=> 'string' },
483 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
489 { subdir
=> 'config' },
490 { subdir
=> 'status' },
491 { subdir
=> 'unlink' },
492 { subdir
=> 'vncproxy' },
493 { subdir
=> 'migrate' },
494 { subdir
=> 'resize' },
495 { subdir
=> 'move' },
497 { subdir
=> 'rrddata' },
498 { subdir
=> 'monitor' },
499 { subdir
=> 'snapshot' },
505 __PACKAGE__-
>register_method({
507 path
=> '{vmid}/rrd',
509 protected
=> 1, # fixme: can we avoid that?
511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
513 description
=> "Read VM RRD statistics (returns PNG)",
515 additionalProperties
=> 0,
517 node
=> get_standard_option
('pve-node'),
518 vmid
=> get_standard_option
('pve-vmid'),
520 description
=> "Specify the time frame you are interested in.",
522 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
525 description
=> "The list of datasources you want to display.",
526 type
=> 'string', format
=> 'pve-configid-list',
529 description
=> "The RRD consolidation function",
531 enum
=> [ 'AVERAGE', 'MAX' ],
539 filename
=> { type
=> 'string' },
545 return PVE
::Cluster
::create_rrd_graph
(
546 "pve2-vm/$param->{vmid}", $param->{timeframe
},
547 $param->{ds
}, $param->{cf
});
551 __PACKAGE__-
>register_method({
553 path
=> '{vmid}/rrddata',
555 protected
=> 1, # fixme: can we avoid that?
557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
559 description
=> "Read VM RRD statistics",
561 additionalProperties
=> 0,
563 node
=> get_standard_option
('pve-node'),
564 vmid
=> get_standard_option
('pve-vmid'),
566 description
=> "Specify the time frame you are interested in.",
568 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
571 description
=> "The RRD consolidation function",
573 enum
=> [ 'AVERAGE', 'MAX' ],
588 return PVE
::Cluster
::create_rrd_data
(
589 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
593 __PACKAGE__-
>register_method({
595 path
=> '{vmid}/config',
598 description
=> "Get virtual machine configuration.",
600 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
603 additionalProperties
=> 0,
605 node
=> get_standard_option
('pve-node'),
606 vmid
=> get_standard_option
('pve-vmid'),
614 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
621 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
623 delete $conf->{snapshots
};
628 my $vm_is_volid_owner = sub {
629 my ($storecfg, $vmid, $volid) =@_;
631 if ($volid !~ m
|^/|) {
633 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
634 if ($owner && ($owner == $vmid)) {
642 my $test_deallocate_drive = sub {
643 my ($storecfg, $vmid, $key, $drive, $force) = @_;
645 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
646 my $volid = $drive->{file
};
647 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
648 if ($force || $key =~ m/^unused/) {
649 my $sid = PVE
::Storage
::parse_volume_id
($volid);
658 my $delete_drive = sub {
659 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
661 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
662 my $volid = $drive->{file
};
664 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
665 if ($force || $key =~ m/^unused/) {
667 # check if the disk is really unused
668 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
669 my $path = PVE
::Storage
::path
($storecfg, $volid);
671 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
672 if $used_paths->{$path};
674 PVE
::Storage
::vdisk_free
($storecfg, $volid);
678 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
683 delete $conf->{$key};
686 my $vmconfig_delete_option = sub {
687 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
689 return if !defined($conf->{$opt});
691 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
694 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
696 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
697 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
698 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
702 my $unplugwarning = "";
703 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
704 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
705 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
706 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
707 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
708 $unplugwarning = "<br>verify that your guest support acpi hotplug";
711 if($opt eq 'tablet'){
712 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
714 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
718 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
719 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
721 delete $conf->{$opt};
724 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
727 my $safe_num_ne = sub {
730 return 0 if !defined($a) && !defined($b);
731 return 1 if !defined($a);
732 return 1 if !defined($b);
737 my $vmconfig_update_disk = sub {
738 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
740 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
742 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
743 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
745 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
750 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
752 my $media = $drive->{media
} || 'disk';
753 my $oldmedia = $old_drive->{media
} || 'disk';
754 die "unable to change media type\n" if $media ne $oldmedia;
756 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
757 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
759 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
760 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
763 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
764 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
765 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
766 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
767 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
768 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
769 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
770 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
771 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
772 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
777 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
778 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
780 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
781 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
783 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
785 if (PVE
::QemuServer
::check_running
($vmid)) {
786 if ($drive->{file
} eq 'none') {
787 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
789 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
790 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
795 } else { # hotplug new disks
797 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
801 my $vmconfig_update_net = sub {
802 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
804 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
805 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
806 my $newnet = PVE
::QemuServer
::parse_net
($value);
808 if($oldnet->{model
} ne $newnet->{model
}){
809 #if model change, we try to hot-unplug
810 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
813 if($newnet->{bridge
} && $oldnet->{bridge
}){
814 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
816 if($newnet->{rate
} ne $oldnet->{rate
}){
817 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
820 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
821 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
822 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
826 #if bridge/nat mode change, we try to hot-unplug
827 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
832 $conf->{$opt} = $value;
833 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
834 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
836 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
838 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
841 # POST/PUT {vmid}/config implementation
843 # The original API used PUT (idempotent) an we assumed that all operations
844 # are fast. But it turned out that almost any configuration change can
845 # involve hot-plug actions, or disk alloc/free. Such actions can take long
846 # time to complete and have side effects (not idempotent).
848 # The new implementation uses POST and forks a worker process. We added
849 # a new option 'background_delay'. If specified we wait up to
850 # 'background_delay' second for the worker task to complete. It returns null
851 # if the task is finished within that time, else we return the UPID.
853 my $update_vm_api = sub {
854 my ($param, $sync) = @_;
856 my $rpcenv = PVE
::RPCEnvironment
::get
();
858 my $authuser = $rpcenv->get_user();
860 my $node = extract_param
($param, 'node');
862 my $vmid = extract_param
($param, 'vmid');
864 my $digest = extract_param
($param, 'digest');
866 my $background_delay = extract_param
($param, 'background_delay');
868 my @paramarr = (); # used for log message
869 foreach my $key (keys %$param) {
870 push @paramarr, "-$key", $param->{$key};
873 my $skiplock = extract_param
($param, 'skiplock');
874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
875 if $skiplock && $authuser ne 'root@pam';
877 my $delete_str = extract_param
($param, 'delete');
879 my $force = extract_param
($param, 'force');
881 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
883 my $storecfg = PVE
::Storage
::config
();
885 my $defaults = PVE
::QemuServer
::load_defaults
();
887 &$resolve_cdrom_alias($param);
889 # now try to verify all parameters
892 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
893 $opt = 'ide2' if $opt eq 'cdrom';
894 raise_param_exc
({ delete => "you can't use '-$opt' and " .
895 "-delete $opt' at the same time" })
896 if defined($param->{$opt});
898 if (!PVE
::QemuServer
::option_exists
($opt)) {
899 raise_param_exc
({ delete => "unknown option '$opt'" });
905 foreach my $opt (keys %$param) {
906 if (PVE
::QemuServer
::valid_drivename
($opt)) {
908 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
909 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
910 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
911 } elsif ($opt =~ m/^net(\d+)$/) {
913 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
914 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
918 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
920 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
922 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
926 my $conf = PVE
::QemuServer
::load_config
($vmid);
928 die "checksum missmatch (file change by other user?)\n"
929 if $digest && $digest ne $conf->{digest
};
931 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
933 if ($param->{memory
} || defined($param->{balloon
})) {
934 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
935 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
937 die "balloon value too large (must be smaller than assigned memory)\n"
938 if $balloon && $balloon > $maxmem;
941 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
945 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
947 foreach my $opt (@delete) { # delete
948 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
949 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
952 my $running = PVE
::QemuServer
::check_running
($vmid);
954 foreach my $opt (keys %$param) { # add/change
956 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
958 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
960 if (PVE
::QemuServer
::valid_drivename
($opt)) {
962 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
963 $opt, $param->{$opt}, $force);
965 } elsif ($opt =~ m/^net(\d+)$/) { #nics
967 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt});
972 if($opt eq 'tablet' && $param->{$opt} == 1){
973 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
974 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
975 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
978 $conf->{$opt} = $param->{$opt};
979 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
983 # allow manual ballooning if shares is set to zero
984 if ($running && defined($param->{balloon
}) &&
985 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
986 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
987 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
995 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
997 if ($background_delay) {
999 # Note: It would be better to do that in the Event based HTTPServer
1000 # to avoid blocking call to sleep.
1002 my $end_time = time() + $background_delay;
1004 my $task = PVE
::Tools
::upid_decode
($upid);
1007 while (time() < $end_time) {
1008 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1010 sleep(1); # this gets interrupted when child process ends
1014 my $status = PVE
::Tools
::upid_read_status
($upid);
1015 return undef if $status eq 'OK';
1024 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1027 my $vm_config_perm_list = [
1032 'VM.Config.Network',
1034 'VM.Config.Options',
1037 __PACKAGE__-
>register_method({
1038 name
=> 'update_vm_async',
1039 path
=> '{vmid}/config',
1043 description
=> "Set virtual machine options (asynchrounous API).",
1045 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1048 additionalProperties
=> 0,
1049 properties
=> PVE
::QemuServer
::json_config_properties
(
1051 node
=> get_standard_option
('pve-node'),
1052 vmid
=> get_standard_option
('pve-vmid'),
1053 skiplock
=> get_standard_option
('skiplock'),
1055 type
=> 'string', format
=> 'pve-configid-list',
1056 description
=> "A list of settings you want to delete.",
1061 description
=> $opt_force_description,
1063 requires
=> 'delete',
1067 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1071 background_delay
=> {
1073 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1084 code
=> $update_vm_api,
1087 __PACKAGE__-
>register_method({
1088 name
=> 'update_vm',
1089 path
=> '{vmid}/config',
1093 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1095 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1098 additionalProperties
=> 0,
1099 properties
=> PVE
::QemuServer
::json_config_properties
(
1101 node
=> get_standard_option
('pve-node'),
1102 vmid
=> get_standard_option
('pve-vmid'),
1103 skiplock
=> get_standard_option
('skiplock'),
1105 type
=> 'string', format
=> 'pve-configid-list',
1106 description
=> "A list of settings you want to delete.",
1111 description
=> $opt_force_description,
1113 requires
=> 'delete',
1117 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1123 returns
=> { type
=> 'null' },
1126 &$update_vm_api($param, 1);
1132 __PACKAGE__-
>register_method({
1133 name
=> 'destroy_vm',
1138 description
=> "Destroy the vm (also delete all used/owned volumes).",
1140 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1143 additionalProperties
=> 0,
1145 node
=> get_standard_option
('pve-node'),
1146 vmid
=> get_standard_option
('pve-vmid'),
1147 skiplock
=> get_standard_option
('skiplock'),
1156 my $rpcenv = PVE
::RPCEnvironment
::get
();
1158 my $authuser = $rpcenv->get_user();
1160 my $vmid = $param->{vmid
};
1162 my $skiplock = $param->{skiplock
};
1163 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1164 if $skiplock && $authuser ne 'root@pam';
1167 my $conf = PVE
::QemuServer
::load_config
($vmid);
1169 my $storecfg = PVE
::Storage
::config
();
1171 my $delVMfromPoolFn = sub {
1172 my $usercfg = cfs_read_file
("user.cfg");
1173 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1174 if (my $data = $usercfg->{pools
}->{$pool}) {
1175 delete $data->{vms
}->{$vmid};
1176 delete $usercfg->{vms
}->{$vmid};
1177 cfs_write_file
("user.cfg", $usercfg);
1185 syslog
('info', "destroy VM $vmid: $upid\n");
1187 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1189 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1192 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1195 __PACKAGE__-
>register_method({
1197 path
=> '{vmid}/unlink',
1201 description
=> "Unlink/delete disk images.",
1203 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1206 additionalProperties
=> 0,
1208 node
=> get_standard_option
('pve-node'),
1209 vmid
=> get_standard_option
('pve-vmid'),
1211 type
=> 'string', format
=> 'pve-configid-list',
1212 description
=> "A list of disk IDs you want to delete.",
1216 description
=> $opt_force_description,
1221 returns
=> { type
=> 'null'},
1225 $param->{delete} = extract_param
($param, 'idlist');
1227 __PACKAGE__-
>update_vm($param);
1234 __PACKAGE__-
>register_method({
1236 path
=> '{vmid}/vncproxy',
1240 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1242 description
=> "Creates a TCP VNC proxy connections.",
1244 additionalProperties
=> 0,
1246 node
=> get_standard_option
('pve-node'),
1247 vmid
=> get_standard_option
('pve-vmid'),
1251 additionalProperties
=> 0,
1253 user
=> { type
=> 'string' },
1254 ticket
=> { type
=> 'string' },
1255 cert
=> { type
=> 'string' },
1256 port
=> { type
=> 'integer' },
1257 upid
=> { type
=> 'string' },
1263 my $rpcenv = PVE
::RPCEnvironment
::get
();
1265 my $authuser = $rpcenv->get_user();
1267 my $vmid = $param->{vmid
};
1268 my $node = $param->{node
};
1270 my $authpath = "/vms/$vmid";
1272 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1274 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1277 my $port = PVE
::Tools
::next_vnc_port
();
1281 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1282 $remip = PVE
::Cluster
::remote_node_ip
($node);
1285 # NOTE: kvm VNC traffic is already TLS encrypted
1286 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1293 syslog
('info', "starting vnc proxy $upid\n");
1295 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1297 my $qmstr = join(' ', @$qmcmd);
1299 # also redirect stderr (else we get RFB protocol errors)
1300 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1302 PVE
::Tools
::run_command
($cmd);
1307 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1309 PVE
::Tools
::wait_for_vnc_port
($port);
1320 __PACKAGE__-
>register_method({
1322 path
=> '{vmid}/status',
1325 description
=> "Directory index",
1330 additionalProperties
=> 0,
1332 node
=> get_standard_option
('pve-node'),
1333 vmid
=> get_standard_option
('pve-vmid'),
1341 subdir
=> { type
=> 'string' },
1344 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1350 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1353 { subdir
=> 'current' },
1354 { subdir
=> 'start' },
1355 { subdir
=> 'stop' },
1361 my $vm_is_ha_managed = sub {
1364 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1365 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1371 __PACKAGE__-
>register_method({
1372 name
=> 'vm_status',
1373 path
=> '{vmid}/status/current',
1376 protected
=> 1, # qemu pid files are only readable by root
1377 description
=> "Get virtual machine status.",
1379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1382 additionalProperties
=> 0,
1384 node
=> get_standard_option
('pve-node'),
1385 vmid
=> get_standard_option
('pve-vmid'),
1388 returns
=> { type
=> 'object' },
1393 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1395 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1396 my $status = $vmstatus->{$param->{vmid
}};
1398 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1403 __PACKAGE__-
>register_method({
1405 path
=> '{vmid}/status/start',
1409 description
=> "Start virtual machine.",
1411 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1414 additionalProperties
=> 0,
1416 node
=> get_standard_option
('pve-node'),
1417 vmid
=> get_standard_option
('pve-vmid'),
1418 skiplock
=> get_standard_option
('skiplock'),
1419 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1420 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1421 machine
=> get_standard_option
('pve-qm-machine'),
1430 my $rpcenv = PVE
::RPCEnvironment
::get
();
1432 my $authuser = $rpcenv->get_user();
1434 my $node = extract_param
($param, 'node');
1436 my $vmid = extract_param
($param, 'vmid');
1438 my $machine = extract_param
($param, 'machine');
1440 my $stateuri = extract_param
($param, 'stateuri');
1441 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1442 if $stateuri && $authuser ne 'root@pam';
1444 my $skiplock = extract_param
($param, 'skiplock');
1445 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1446 if $skiplock && $authuser ne 'root@pam';
1448 my $migratedfrom = extract_param
($param, 'migratedfrom');
1449 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1450 if $migratedfrom && $authuser ne 'root@pam';
1452 my $storecfg = PVE
::Storage
::config
();
1454 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1455 $rpcenv->{type
} ne 'ha') {
1460 my $service = "pvevm:$vmid";
1462 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1464 print "Executing HA start for VM $vmid\n";
1466 PVE
::Tools
::run_command
($cmd);
1471 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1478 syslog
('info', "start VM $vmid: $upid\n");
1480 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1485 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1489 __PACKAGE__-
>register_method({
1491 path
=> '{vmid}/status/stop',
1495 description
=> "Stop virtual machine.",
1497 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1500 additionalProperties
=> 0,
1502 node
=> get_standard_option
('pve-node'),
1503 vmid
=> get_standard_option
('pve-vmid'),
1504 skiplock
=> get_standard_option
('skiplock'),
1505 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1507 description
=> "Wait maximal timeout seconds.",
1513 description
=> "Do not decativate storage volumes.",
1526 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1530 my $node = extract_param
($param, 'node');
1532 my $vmid = extract_param
($param, 'vmid');
1534 my $skiplock = extract_param
($param, 'skiplock');
1535 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1536 if $skiplock && $authuser ne 'root@pam';
1538 my $keepActive = extract_param
($param, 'keepActive');
1539 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1540 if $keepActive && $authuser ne 'root@pam';
1542 my $migratedfrom = extract_param
($param, 'migratedfrom');
1543 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1544 if $migratedfrom && $authuser ne 'root@pam';
1547 my $storecfg = PVE
::Storage
::config
();
1549 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1554 my $service = "pvevm:$vmid";
1556 my $cmd = ['clusvcadm', '-d', $service];
1558 print "Executing HA stop for VM $vmid\n";
1560 PVE
::Tools
::run_command
($cmd);
1565 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1571 syslog
('info', "stop VM $vmid: $upid\n");
1573 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1574 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1579 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1583 __PACKAGE__-
>register_method({
1585 path
=> '{vmid}/status/reset',
1589 description
=> "Reset virtual machine.",
1591 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1594 additionalProperties
=> 0,
1596 node
=> get_standard_option
('pve-node'),
1597 vmid
=> get_standard_option
('pve-vmid'),
1598 skiplock
=> get_standard_option
('skiplock'),
1607 my $rpcenv = PVE
::RPCEnvironment
::get
();
1609 my $authuser = $rpcenv->get_user();
1611 my $node = extract_param
($param, 'node');
1613 my $vmid = extract_param
($param, 'vmid');
1615 my $skiplock = extract_param
($param, 'skiplock');
1616 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1617 if $skiplock && $authuser ne 'root@pam';
1619 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1624 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1629 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1632 __PACKAGE__-
>register_method({
1633 name
=> 'vm_shutdown',
1634 path
=> '{vmid}/status/shutdown',
1638 description
=> "Shutdown virtual machine.",
1640 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1643 additionalProperties
=> 0,
1645 node
=> get_standard_option
('pve-node'),
1646 vmid
=> get_standard_option
('pve-vmid'),
1647 skiplock
=> get_standard_option
('skiplock'),
1649 description
=> "Wait maximal timeout seconds.",
1655 description
=> "Make sure the VM stops.",
1661 description
=> "Do not decativate storage volumes.",
1674 my $rpcenv = PVE
::RPCEnvironment
::get
();
1676 my $authuser = $rpcenv->get_user();
1678 my $node = extract_param
($param, 'node');
1680 my $vmid = extract_param
($param, 'vmid');
1682 my $skiplock = extract_param
($param, 'skiplock');
1683 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1684 if $skiplock && $authuser ne 'root@pam';
1686 my $keepActive = extract_param
($param, 'keepActive');
1687 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1688 if $keepActive && $authuser ne 'root@pam';
1690 my $storecfg = PVE
::Storage
::config
();
1695 syslog
('info', "shutdown VM $vmid: $upid\n");
1697 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1698 1, $param->{forceStop
}, $keepActive);
1703 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1706 __PACKAGE__-
>register_method({
1707 name
=> 'vm_suspend',
1708 path
=> '{vmid}/status/suspend',
1712 description
=> "Suspend virtual machine.",
1714 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1717 additionalProperties
=> 0,
1719 node
=> get_standard_option
('pve-node'),
1720 vmid
=> get_standard_option
('pve-vmid'),
1721 skiplock
=> get_standard_option
('skiplock'),
1730 my $rpcenv = PVE
::RPCEnvironment
::get
();
1732 my $authuser = $rpcenv->get_user();
1734 my $node = extract_param
($param, 'node');
1736 my $vmid = extract_param
($param, 'vmid');
1738 my $skiplock = extract_param
($param, 'skiplock');
1739 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1740 if $skiplock && $authuser ne 'root@pam';
1742 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1747 syslog
('info', "suspend VM $vmid: $upid\n");
1749 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1754 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1757 __PACKAGE__-
>register_method({
1758 name
=> 'vm_resume',
1759 path
=> '{vmid}/status/resume',
1763 description
=> "Resume virtual machine.",
1765 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1768 additionalProperties
=> 0,
1770 node
=> get_standard_option
('pve-node'),
1771 vmid
=> get_standard_option
('pve-vmid'),
1772 skiplock
=> get_standard_option
('skiplock'),
1781 my $rpcenv = PVE
::RPCEnvironment
::get
();
1783 my $authuser = $rpcenv->get_user();
1785 my $node = extract_param
($param, 'node');
1787 my $vmid = extract_param
($param, 'vmid');
1789 my $skiplock = extract_param
($param, 'skiplock');
1790 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1791 if $skiplock && $authuser ne 'root@pam';
1793 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1798 syslog
('info', "resume VM $vmid: $upid\n");
1800 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1805 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1808 __PACKAGE__-
>register_method({
1809 name
=> 'vm_sendkey',
1810 path
=> '{vmid}/sendkey',
1814 description
=> "Send key event to virtual machine.",
1816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid'),
1823 skiplock
=> get_standard_option
('skiplock'),
1825 description
=> "The key (qemu monitor encoding).",
1830 returns
=> { type
=> 'null'},
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $node = extract_param
($param, 'node');
1840 my $vmid = extract_param
($param, 'vmid');
1842 my $skiplock = extract_param
($param, 'skiplock');
1843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1844 if $skiplock && $authuser ne 'root@pam';
1846 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1851 __PACKAGE__-
>register_method({
1852 name
=> 'vm_feature',
1853 path
=> '{vmid}/feature',
1857 description
=> "Check if feature for virtual machine is available.",
1859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1867 description
=> "Feature to check.",
1869 enum
=> [ 'snapshot', 'clone', 'copy' ],
1871 snapname
=> get_standard_option
('pve-snapshot-name', {
1879 hasFeature
=> { type
=> 'boolean' },
1882 items
=> { type
=> 'string' },
1889 my $node = extract_param
($param, 'node');
1891 my $vmid = extract_param
($param, 'vmid');
1893 my $snapname = extract_param
($param, 'snapname');
1895 my $feature = extract_param
($param, 'feature');
1897 my $running = PVE
::QemuServer
::check_running
($vmid);
1899 my $conf = PVE
::QemuServer
::load_config
($vmid);
1902 my $snap = $conf->{snapshots
}->{$snapname};
1903 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1906 my $storecfg = PVE
::Storage
::config
();
1908 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1909 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1912 hasFeature
=> $hasFeature,
1913 nodes
=> [ keys %$nodelist ],
1917 __PACKAGE__-
>register_method({
1919 path
=> '{vmid}/clone',
1923 description
=> "Create a copy of virtual machine/template.",
1925 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1926 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1927 "'Datastore.AllocateSpace' on any used storage.",
1930 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1932 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1933 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1938 additionalProperties
=> 0,
1940 node
=> get_standard_option
('pve-node'),
1941 vmid
=> get_standard_option
('pve-vmid'),
1942 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1945 type
=> 'string', format
=> 'dns-name',
1946 description
=> "Set a name for the new VM.",
1951 description
=> "Description for the new VM.",
1955 type
=> 'string', format
=> 'pve-poolid',
1956 description
=> "Add the new VM to the specified pool.",
1958 snapname
=> get_standard_option
('pve-snapshot-name', {
1962 storage
=> get_standard_option
('pve-storage-id', {
1963 description
=> "Target storage for full clone.",
1968 description
=> "Target format for file storage.",
1972 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1977 description
=> "Create a full copy of all disk. This is always done when " .
1978 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1981 target
=> get_standard_option
('pve-node', {
1982 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1993 my $rpcenv = PVE
::RPCEnvironment
::get
();
1995 my $authuser = $rpcenv->get_user();
1997 my $node = extract_param
($param, 'node');
1999 my $vmid = extract_param
($param, 'vmid');
2001 my $newid = extract_param
($param, 'newid');
2003 my $pool = extract_param
($param, 'pool');
2005 if (defined($pool)) {
2006 $rpcenv->check_pool_exist($pool);
2009 my $snapname = extract_param
($param, 'snapname');
2011 my $storage = extract_param
($param, 'storage');
2013 my $format = extract_param
($param, 'format');
2015 my $target = extract_param
($param, 'target');
2017 my $localnode = PVE
::INotify
::nodename
();
2019 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2021 PVE
::Cluster
::check_node_exists
($target) if $target;
2023 my $storecfg = PVE
::Storage
::config
();
2026 # check if storage is enabled on local node
2027 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2029 # check if storage is available on target node
2030 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2031 # clone only works if target storage is shared
2032 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2033 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2037 PVE
::Cluster
::check_cfs_quorum
();
2039 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2041 # exclusive lock if VM is running - else shared lock is enough;
2042 my $shared_lock = $running ?
0 : 1;
2046 # do all tests after lock
2047 # we also try to do all tests before we fork the worker
2049 my $conf = PVE
::QemuServer
::load_config
($vmid);
2051 PVE
::QemuServer
::check_lock
($conf);
2053 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2055 die "unexpected state change\n" if $verify_running != $running;
2057 die "snapshot '$snapname' does not exist\n"
2058 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2060 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2062 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2064 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2066 my $conffile = PVE
::QemuServer
::config_file
($newid);
2068 die "unable to create VM $newid: config file already exists\n"
2071 my $newconf = { lock => 'clone' };
2075 foreach my $opt (keys %$oldconf) {
2076 my $value = $oldconf->{$opt};
2078 # do not copy snapshot related info
2079 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2080 $opt eq 'vmstate' || $opt eq 'snapstate';
2082 # always change MAC! address
2083 if ($opt =~ m/^net(\d+)$/) {
2084 my $net = PVE
::QemuServer
::parse_net
($value);
2085 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2086 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2087 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2088 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2089 $newconf->{$opt} = $value; # simply copy configuration
2091 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2092 die "Full clone feature is not available"
2093 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2096 $drives->{$opt} = $drive;
2097 push @$vollist, $drive->{file
};
2100 # copy everything else
2101 $newconf->{$opt} = $value;
2105 delete $newconf->{template
};
2107 if ($param->{name
}) {
2108 $newconf->{name
} = $param->{name
};
2110 if ($oldconf->{name
}) {
2111 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2113 $newconf->{name
} = "Copy-of-VM-$vmid";
2117 if ($param->{description
}) {
2118 $newconf->{description
} = $param->{description
};
2121 # create empty/temp config - this fails if VM already exists on other node
2122 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2127 my $newvollist = [];
2130 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2132 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2134 foreach my $opt (keys %$drives) {
2135 my $drive = $drives->{$opt};
2137 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2138 $newid, $storage, $format, $drive->{full
}, $newvollist);
2140 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2142 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2145 delete $newconf->{lock};
2146 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2149 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2150 die "Failed to move config to node '$target' - rename failed: $!\n"
2151 if !rename($conffile, $newconffile);
2154 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2159 sleep 1; # some storage like rbd need to wait before release volume - really?
2161 foreach my $volid (@$newvollist) {
2162 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2165 die "clone failed: $err";
2171 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2174 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2175 # Aquire exclusive lock lock for $newid
2176 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2181 __PACKAGE__-
>register_method({
2182 name
=> 'move_vm_disk',
2183 path
=> '{vmid}/move_disk',
2187 description
=> "Move volume to different storage.",
2189 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2190 "and 'Datastore.AllocateSpace' permissions on the storage.",
2193 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2194 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2198 additionalProperties
=> 0,
2200 node
=> get_standard_option
('pve-node'),
2201 vmid
=> get_standard_option
('pve-vmid'),
2204 description
=> "The disk you want to move.",
2205 enum
=> [ PVE
::QemuServer
::disknames
() ],
2207 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2210 description
=> "Target Format.",
2211 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2216 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2222 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2230 description
=> "the task ID.",
2235 my $rpcenv = PVE
::RPCEnvironment
::get
();
2237 my $authuser = $rpcenv->get_user();
2239 my $node = extract_param
($param, 'node');
2241 my $vmid = extract_param
($param, 'vmid');
2243 my $digest = extract_param
($param, 'digest');
2245 my $disk = extract_param
($param, 'disk');
2247 my $storeid = extract_param
($param, 'storage');
2249 my $format = extract_param
($param, 'format');
2251 my $storecfg = PVE
::Storage
::config
();
2253 my $updatefn = sub {
2255 my $conf = PVE
::QemuServer
::load_config
($vmid);
2257 die "checksum missmatch (file change by other user?)\n"
2258 if $digest && $digest ne $conf->{digest
};
2260 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2262 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2264 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2266 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2269 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2270 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2274 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2275 (!$format || !$oldfmt || $oldfmt eq $format);
2277 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2279 my $running = PVE
::QemuServer
::check_running
($vmid);
2281 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2285 my $newvollist = [];
2288 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2290 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2291 $vmid, $storeid, $format, 1, $newvollist);
2293 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2295 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2297 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2301 foreach my $volid (@$newvollist) {
2302 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2305 die "storage migration failed: $err";
2308 if ($param->{delete}) {
2309 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2314 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2317 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2320 __PACKAGE__-
>register_method({
2321 name
=> 'migrate_vm',
2322 path
=> '{vmid}/migrate',
2326 description
=> "Migrate virtual machine. Creates a new migration task.",
2328 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2331 additionalProperties
=> 0,
2333 node
=> get_standard_option
('pve-node'),
2334 vmid
=> get_standard_option
('pve-vmid'),
2335 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2338 description
=> "Use online/live migration.",
2343 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2350 description
=> "the task ID.",
2355 my $rpcenv = PVE
::RPCEnvironment
::get
();
2357 my $authuser = $rpcenv->get_user();
2359 my $target = extract_param
($param, 'target');
2361 my $localnode = PVE
::INotify
::nodename
();
2362 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2364 PVE
::Cluster
::check_cfs_quorum
();
2366 PVE
::Cluster
::check_node_exists
($target);
2368 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2370 my $vmid = extract_param
($param, 'vmid');
2372 raise_param_exc
({ force
=> "Only root may use this option." })
2373 if $param->{force
} && $authuser ne 'root@pam';
2376 my $conf = PVE
::QemuServer
::load_config
($vmid);
2378 # try to detect errors early
2380 PVE
::QemuServer
::check_lock
($conf);
2382 if (PVE
::QemuServer
::check_running
($vmid)) {
2383 die "cant migrate running VM without --online\n"
2384 if !$param->{online
};
2387 my $storecfg = PVE
::Storage
::config
();
2388 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2390 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2395 my $service = "pvevm:$vmid";
2397 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2399 print "Executing HA migrate for VM $vmid to node $target\n";
2401 PVE
::Tools
::run_command
($cmd);
2406 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2413 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2416 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2421 __PACKAGE__-
>register_method({
2423 path
=> '{vmid}/monitor',
2427 description
=> "Execute Qemu monitor commands.",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2432 additionalProperties
=> 0,
2434 node
=> get_standard_option
('pve-node'),
2435 vmid
=> get_standard_option
('pve-vmid'),
2438 description
=> "The monitor command.",
2442 returns
=> { type
=> 'string'},
2446 my $vmid = $param->{vmid
};
2448 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2452 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2454 $res = "ERROR: $@" if $@;
2459 __PACKAGE__-
>register_method({
2460 name
=> 'resize_vm',
2461 path
=> '{vmid}/resize',
2465 description
=> "Extend volume size.",
2467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2470 additionalProperties
=> 0,
2472 node
=> get_standard_option
('pve-node'),
2473 vmid
=> get_standard_option
('pve-vmid'),
2474 skiplock
=> get_standard_option
('skiplock'),
2477 description
=> "The disk you want to resize.",
2478 enum
=> [PVE
::QemuServer
::disknames
()],
2482 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2483 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.",
2487 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2493 returns
=> { type
=> 'null'},
2497 my $rpcenv = PVE
::RPCEnvironment
::get
();
2499 my $authuser = $rpcenv->get_user();
2501 my $node = extract_param
($param, 'node');
2503 my $vmid = extract_param
($param, 'vmid');
2505 my $digest = extract_param
($param, 'digest');
2507 my $disk = extract_param
($param, 'disk');
2509 my $sizestr = extract_param
($param, 'size');
2511 my $skiplock = extract_param
($param, 'skiplock');
2512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2513 if $skiplock && $authuser ne 'root@pam';
2515 my $storecfg = PVE
::Storage
::config
();
2517 my $updatefn = sub {
2519 my $conf = PVE
::QemuServer
::load_config
($vmid);
2521 die "checksum missmatch (file change by other user?)\n"
2522 if $digest && $digest ne $conf->{digest
};
2523 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2525 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2527 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2529 my $volid = $drive->{file
};
2531 die "disk '$disk' has no associated volume\n" if !$volid;
2533 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2535 die "you can't online resize a virtio windows bootdisk\n"
2536 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2538 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2540 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2542 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2544 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2545 my ($ext, $newsize, $unit) = ($1, $2, $4);
2548 $newsize = $newsize * 1024;
2549 } elsif ($unit eq 'M') {
2550 $newsize = $newsize * 1024 * 1024;
2551 } elsif ($unit eq 'G') {
2552 $newsize = $newsize * 1024 * 1024 * 1024;
2553 } elsif ($unit eq 'T') {
2554 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2557 $newsize += $size if $ext;
2558 $newsize = int($newsize);
2560 die "unable to skrink disk size\n" if $newsize < $size;
2562 return if $size == $newsize;
2564 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2566 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2568 $drive->{size
} = $newsize;
2569 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2571 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2574 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2578 __PACKAGE__-
>register_method({
2579 name
=> 'snapshot_list',
2580 path
=> '{vmid}/snapshot',
2582 description
=> "List all snapshots.",
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2587 protected
=> 1, # qemu pid files are only readable by root
2589 additionalProperties
=> 0,
2591 vmid
=> get_standard_option
('pve-vmid'),
2592 node
=> get_standard_option
('pve-node'),
2601 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2606 my $vmid = $param->{vmid
};
2608 my $conf = PVE
::QemuServer
::load_config
($vmid);
2609 my $snaphash = $conf->{snapshots
} || {};
2613 foreach my $name (keys %$snaphash) {
2614 my $d = $snaphash->{$name};
2617 snaptime
=> $d->{snaptime
} || 0,
2618 vmstate
=> $d->{vmstate
} ?
1 : 0,
2619 description
=> $d->{description
} || '',
2621 $item->{parent
} = $d->{parent
} if $d->{parent
};
2622 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2626 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2627 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2628 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2630 push @$res, $current;
2635 __PACKAGE__-
>register_method({
2637 path
=> '{vmid}/snapshot',
2641 description
=> "Snapshot a VM.",
2643 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2646 additionalProperties
=> 0,
2648 node
=> get_standard_option
('pve-node'),
2649 vmid
=> get_standard_option
('pve-vmid'),
2650 snapname
=> get_standard_option
('pve-snapshot-name'),
2654 description
=> "Save the vmstate",
2659 description
=> "Freeze the filesystem",
2664 description
=> "A textual description or comment.",
2670 description
=> "the task ID.",
2675 my $rpcenv = PVE
::RPCEnvironment
::get
();
2677 my $authuser = $rpcenv->get_user();
2679 my $node = extract_param
($param, 'node');
2681 my $vmid = extract_param
($param, 'vmid');
2683 my $snapname = extract_param
($param, 'snapname');
2685 die "unable to use snapshot name 'current' (reserved name)\n"
2686 if $snapname eq 'current';
2689 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2690 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2691 $param->{freezefs
}, $param->{description
});
2694 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2697 __PACKAGE__-
>register_method({
2698 name
=> 'snapshot_cmd_idx',
2699 path
=> '{vmid}/snapshot/{snapname}',
2706 additionalProperties
=> 0,
2708 vmid
=> get_standard_option
('pve-vmid'),
2709 node
=> get_standard_option
('pve-node'),
2710 snapname
=> get_standard_option
('pve-snapshot-name'),
2719 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2726 push @$res, { cmd
=> 'rollback' };
2727 push @$res, { cmd
=> 'config' };
2732 __PACKAGE__-
>register_method({
2733 name
=> 'update_snapshot_config',
2734 path
=> '{vmid}/snapshot/{snapname}/config',
2738 description
=> "Update snapshot metadata.",
2740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2743 additionalProperties
=> 0,
2745 node
=> get_standard_option
('pve-node'),
2746 vmid
=> get_standard_option
('pve-vmid'),
2747 snapname
=> get_standard_option
('pve-snapshot-name'),
2751 description
=> "A textual description or comment.",
2755 returns
=> { type
=> 'null' },
2759 my $rpcenv = PVE
::RPCEnvironment
::get
();
2761 my $authuser = $rpcenv->get_user();
2763 my $vmid = extract_param
($param, 'vmid');
2765 my $snapname = extract_param
($param, 'snapname');
2767 return undef if !defined($param->{description
});
2769 my $updatefn = sub {
2771 my $conf = PVE
::QemuServer
::load_config
($vmid);
2773 PVE
::QemuServer
::check_lock
($conf);
2775 my $snap = $conf->{snapshots
}->{$snapname};
2777 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2779 $snap->{description
} = $param->{description
} if defined($param->{description
});
2781 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2784 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2789 __PACKAGE__-
>register_method({
2790 name
=> 'get_snapshot_config',
2791 path
=> '{vmid}/snapshot/{snapname}/config',
2794 description
=> "Get snapshot configuration",
2796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2799 additionalProperties
=> 0,
2801 node
=> get_standard_option
('pve-node'),
2802 vmid
=> get_standard_option
('pve-vmid'),
2803 snapname
=> get_standard_option
('pve-snapshot-name'),
2806 returns
=> { type
=> "object" },
2810 my $rpcenv = PVE
::RPCEnvironment
::get
();
2812 my $authuser = $rpcenv->get_user();
2814 my $vmid = extract_param
($param, 'vmid');
2816 my $snapname = extract_param
($param, 'snapname');
2818 my $conf = PVE
::QemuServer
::load_config
($vmid);
2820 my $snap = $conf->{snapshots
}->{$snapname};
2822 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2827 __PACKAGE__-
>register_method({
2829 path
=> '{vmid}/snapshot/{snapname}/rollback',
2833 description
=> "Rollback VM state to specified snapshot.",
2835 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2838 additionalProperties
=> 0,
2840 node
=> get_standard_option
('pve-node'),
2841 vmid
=> get_standard_option
('pve-vmid'),
2842 snapname
=> get_standard_option
('pve-snapshot-name'),
2847 description
=> "the task ID.",
2852 my $rpcenv = PVE
::RPCEnvironment
::get
();
2854 my $authuser = $rpcenv->get_user();
2856 my $node = extract_param
($param, 'node');
2858 my $vmid = extract_param
($param, 'vmid');
2860 my $snapname = extract_param
($param, 'snapname');
2863 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2864 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2867 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2870 __PACKAGE__-
>register_method({
2871 name
=> 'delsnapshot',
2872 path
=> '{vmid}/snapshot/{snapname}',
2876 description
=> "Delete a VM snapshot.",
2878 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2881 additionalProperties
=> 0,
2883 node
=> get_standard_option
('pve-node'),
2884 vmid
=> get_standard_option
('pve-vmid'),
2885 snapname
=> get_standard_option
('pve-snapshot-name'),
2889 description
=> "For removal from config file, even if removing disk snapshots fails.",
2895 description
=> "the task ID.",
2900 my $rpcenv = PVE
::RPCEnvironment
::get
();
2902 my $authuser = $rpcenv->get_user();
2904 my $node = extract_param
($param, 'node');
2906 my $vmid = extract_param
($param, 'vmid');
2908 my $snapname = extract_param
($param, 'snapname');
2911 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2912 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2915 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2918 __PACKAGE__-
>register_method({
2920 path
=> '{vmid}/template',
2924 description
=> "Create a Template.",
2926 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2927 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2930 additionalProperties
=> 0,
2932 node
=> get_standard_option
('pve-node'),
2933 vmid
=> get_standard_option
('pve-vmid'),
2937 description
=> "If you want to convert only 1 disk to base image.",
2938 enum
=> [PVE
::QemuServer
::disknames
()],
2943 returns
=> { type
=> 'null'},
2947 my $rpcenv = PVE
::RPCEnvironment
::get
();
2949 my $authuser = $rpcenv->get_user();
2951 my $node = extract_param
($param, 'node');
2953 my $vmid = extract_param
($param, 'vmid');
2955 my $disk = extract_param
($param, 'disk');
2957 my $updatefn = sub {
2959 my $conf = PVE
::QemuServer
::load_config
($vmid);
2961 PVE
::QemuServer
::check_lock
($conf);
2963 die "unable to create template, because VM contains snapshots\n"
2964 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2966 die "you can't convert a template to a template\n"
2967 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2969 die "you can't convert a VM to template if VM is running\n"
2970 if PVE
::QemuServer
::check_running
($vmid);
2973 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2976 $conf->{template
} = 1;
2977 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2979 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2982 PVE
::QemuServer
::lock_config
($vmid, $updatefn);