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' },
500 { subdir
=> 'spiceproxy' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
771 ($drive->{mbps
} || 0)*1024*1024,
772 ($drive->{mbps_rd
} || 0)*1024*1024,
773 ($drive->{mbps_wr
} || 0)*1024*1024,
775 $drive->{iops_rd
} || 0,
776 $drive->{iops_wr
} || 0)
777 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
782 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
783 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
785 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
786 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
788 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
790 if (PVE
::QemuServer
::check_running
($vmid)) {
791 if ($drive->{file
} eq 'none') {
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
794 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
795 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
800 } else { # hotplug new disks
802 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
806 my $vmconfig_update_net = sub {
807 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
809 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
810 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
811 my $newnet = PVE
::QemuServer
::parse_net
($value);
813 if($oldnet->{model
} ne $newnet->{model
}){
814 #if model change, we try to hot-unplug
815 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
818 if($newnet->{bridge
} && $oldnet->{bridge
}){
819 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
821 if($newnet->{rate
} ne $oldnet->{rate
}){
822 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
825 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
826 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
827 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
831 #if bridge/nat mode change, we try to hot-unplug
832 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
837 $conf->{$opt} = $value;
838 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
839 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
841 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
843 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
846 # POST/PUT {vmid}/config implementation
848 # The original API used PUT (idempotent) an we assumed that all operations
849 # are fast. But it turned out that almost any configuration change can
850 # involve hot-plug actions, or disk alloc/free. Such actions can take long
851 # time to complete and have side effects (not idempotent).
853 # The new implementation uses POST and forks a worker process. We added
854 # a new option 'background_delay'. If specified we wait up to
855 # 'background_delay' second for the worker task to complete. It returns null
856 # if the task is finished within that time, else we return the UPID.
858 my $update_vm_api = sub {
859 my ($param, $sync) = @_;
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $node = extract_param
($param, 'node');
867 my $vmid = extract_param
($param, 'vmid');
869 my $digest = extract_param
($param, 'digest');
871 my $background_delay = extract_param
($param, 'background_delay');
873 my @paramarr = (); # used for log message
874 foreach my $key (keys %$param) {
875 push @paramarr, "-$key", $param->{$key};
878 my $skiplock = extract_param
($param, 'skiplock');
879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
880 if $skiplock && $authuser ne 'root@pam';
882 my $delete_str = extract_param
($param, 'delete');
884 my $force = extract_param
($param, 'force');
886 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
888 my $storecfg = PVE
::Storage
::config
();
890 my $defaults = PVE
::QemuServer
::load_defaults
();
892 &$resolve_cdrom_alias($param);
894 # now try to verify all parameters
897 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
898 $opt = 'ide2' if $opt eq 'cdrom';
899 raise_param_exc
({ delete => "you can't use '-$opt' and " .
900 "-delete $opt' at the same time" })
901 if defined($param->{$opt});
903 if (!PVE
::QemuServer
::option_exists
($opt)) {
904 raise_param_exc
({ delete => "unknown option '$opt'" });
910 foreach my $opt (keys %$param) {
911 if (PVE
::QemuServer
::valid_drivename
($opt)) {
913 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
914 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
915 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
916 } elsif ($opt =~ m/^net(\d+)$/) {
918 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
919 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
923 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
927 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
931 my $conf = PVE
::QemuServer
::load_config
($vmid);
933 die "checksum missmatch (file change by other user?)\n"
934 if $digest && $digest ne $conf->{digest
};
936 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
938 if ($param->{memory
} || defined($param->{balloon
})) {
939 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
940 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
942 die "balloon value too large (must be smaller than assigned memory)\n"
943 if $balloon && $balloon > $maxmem;
946 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
950 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
952 foreach my $opt (@delete) { # delete
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
957 my $running = PVE
::QemuServer
::check_running
($vmid);
959 foreach my $opt (keys %$param) { # add/change
961 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt}, $force);
970 } elsif ($opt =~ m/^net(\d+)$/) { #nics
972 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
973 $opt, $param->{$opt});
977 if($opt eq 'tablet' && $param->{$opt} == 1){
978 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
979 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
980 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
983 $conf->{$opt} = $param->{$opt};
984 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
988 # allow manual ballooning if shares is set to zero
989 if ($running && defined($param->{balloon
}) &&
990 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
991 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
992 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1000 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1002 if ($background_delay) {
1004 # Note: It would be better to do that in the Event based HTTPServer
1005 # to avoid blocking call to sleep.
1007 my $end_time = time() + $background_delay;
1009 my $task = PVE
::Tools
::upid_decode
($upid);
1012 while (time() < $end_time) {
1013 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1015 sleep(1); # this gets interrupted when child process ends
1019 my $status = PVE
::Tools
::upid_read_status
($upid);
1020 return undef if $status eq 'OK';
1029 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1032 my $vm_config_perm_list = [
1037 'VM.Config.Network',
1039 'VM.Config.Options',
1042 __PACKAGE__-
>register_method({
1043 name
=> 'update_vm_async',
1044 path
=> '{vmid}/config',
1048 description
=> "Set virtual machine options (asynchrounous API).",
1050 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1053 additionalProperties
=> 0,
1054 properties
=> PVE
::QemuServer
::json_config_properties
(
1056 node
=> get_standard_option
('pve-node'),
1057 vmid
=> get_standard_option
('pve-vmid'),
1058 skiplock
=> get_standard_option
('skiplock'),
1060 type
=> 'string', format
=> 'pve-configid-list',
1061 description
=> "A list of settings you want to delete.",
1066 description
=> $opt_force_description,
1068 requires
=> 'delete',
1072 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1076 background_delay
=> {
1078 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1089 code
=> $update_vm_api,
1092 __PACKAGE__-
>register_method({
1093 name
=> 'update_vm',
1094 path
=> '{vmid}/config',
1098 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1100 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1103 additionalProperties
=> 0,
1104 properties
=> PVE
::QemuServer
::json_config_properties
(
1106 node
=> get_standard_option
('pve-node'),
1107 vmid
=> get_standard_option
('pve-vmid'),
1108 skiplock
=> get_standard_option
('skiplock'),
1110 type
=> 'string', format
=> 'pve-configid-list',
1111 description
=> "A list of settings you want to delete.",
1116 description
=> $opt_force_description,
1118 requires
=> 'delete',
1122 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1128 returns
=> { type
=> 'null' },
1131 &$update_vm_api($param, 1);
1137 __PACKAGE__-
>register_method({
1138 name
=> 'destroy_vm',
1143 description
=> "Destroy the vm (also delete all used/owned volumes).",
1145 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1152 skiplock
=> get_standard_option
('skiplock'),
1161 my $rpcenv = PVE
::RPCEnvironment
::get
();
1163 my $authuser = $rpcenv->get_user();
1165 my $vmid = $param->{vmid
};
1167 my $skiplock = $param->{skiplock
};
1168 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1169 if $skiplock && $authuser ne 'root@pam';
1172 my $conf = PVE
::QemuServer
::load_config
($vmid);
1174 my $storecfg = PVE
::Storage
::config
();
1176 my $delVMfromPoolFn = sub {
1177 my $usercfg = cfs_read_file
("user.cfg");
1178 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1179 if (my $data = $usercfg->{pools
}->{$pool}) {
1180 delete $data->{vms
}->{$vmid};
1181 delete $usercfg->{vms
}->{$vmid};
1182 cfs_write_file
("user.cfg", $usercfg);
1190 syslog
('info', "destroy VM $vmid: $upid\n");
1192 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1194 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1197 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1200 __PACKAGE__-
>register_method({
1202 path
=> '{vmid}/unlink',
1206 description
=> "Unlink/delete disk images.",
1208 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1211 additionalProperties
=> 0,
1213 node
=> get_standard_option
('pve-node'),
1214 vmid
=> get_standard_option
('pve-vmid'),
1216 type
=> 'string', format
=> 'pve-configid-list',
1217 description
=> "A list of disk IDs you want to delete.",
1221 description
=> $opt_force_description,
1226 returns
=> { type
=> 'null'},
1230 $param->{delete} = extract_param
($param, 'idlist');
1232 __PACKAGE__-
>update_vm($param);
1239 __PACKAGE__-
>register_method({
1241 path
=> '{vmid}/vncproxy',
1245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1247 description
=> "Creates a TCP VNC proxy connections.",
1249 additionalProperties
=> 0,
1251 node
=> get_standard_option
('pve-node'),
1252 vmid
=> get_standard_option
('pve-vmid'),
1256 additionalProperties
=> 0,
1258 user
=> { type
=> 'string' },
1259 ticket
=> { type
=> 'string' },
1260 cert
=> { type
=> 'string' },
1261 port
=> { type
=> 'integer' },
1262 upid
=> { type
=> 'string' },
1268 my $rpcenv = PVE
::RPCEnvironment
::get
();
1270 my $authuser = $rpcenv->get_user();
1272 my $vmid = $param->{vmid
};
1273 my $node = $param->{node
};
1275 my $authpath = "/vms/$vmid";
1277 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1279 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1282 my $port = PVE
::Tools
::next_vnc_port
();
1286 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1287 $remip = PVE
::Cluster
::remote_node_ip
($node);
1290 # NOTE: kvm VNC traffic is already TLS encrypted
1291 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1298 syslog
('info', "starting vnc proxy $upid\n");
1300 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1302 my $qmstr = join(' ', @$qmcmd);
1304 # also redirect stderr (else we get RFB protocol errors)
1305 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1307 PVE
::Tools
::run_command
($cmd);
1312 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1314 PVE
::Tools
::wait_for_vnc_port
($port);
1325 __PACKAGE__-
>register_method({
1326 name
=> 'spiceproxy',
1327 path
=> '{vmid}/spiceproxy',
1328 method => 'GET', # fixme: should be POST, but howto handle that in the HTML client
1330 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1332 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1334 description
=> "Returns a SPICE configuration to connect to the VM.",
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid'),
1343 additionalProperties
=> 1,
1345 type
=> { type
=> 'string' },
1346 password
=> { type
=> 'string' },
1347 host
=> { type
=> 'string' },
1348 port
=> { type
=> 'integer' },
1354 my $rpcenv = PVE
::RPCEnvironment
::get
();
1356 my $authuser = $rpcenv->get_user();
1358 my $vmid = $param->{vmid
};
1359 my $node = $param->{node
};
1361 my $port = PVE
::Tools
::next_vnc_port
();
1365 # Note: we currectly use "proxyto => 'node'", so this code will never trigger
1366 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1367 $remip = PVE
::Cluster
::remote_node_ip
($node);
1370 my $authpath = "/vms/$vmid";
1372 my $ticket = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $authpath);
1376 # Note: this only works if VM is on local node
1377 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1378 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1380 my $remcmd = []; #fixme
1385 syslog
('info', "starting spice proxy $upid\n");
1387 my $socket = PVE
::QemuServer
::spice_socket
($vmid);
1389 my $cmd = ['/usr/bin/socat', '-d', '-d',
1390 "TCP-LISTEN:$port,reuseaddr,fork" ];
1393 push @$cmd, "EXEC:'ssh root@$remip socat STDIO UNIX-CONNECT:$socket";
1395 push @$cmd, "UNIX-CONNECT:$socket";
1403 if ($line =~ /successfully connected from/) {
1405 } elsif ($line =~ /exiting with status/) {
1407 # Note: counting connections seems unreliable here
1408 die "client exit\n"; # if $conn_count <= 0;
1412 eval { PVE
::Tools
::run_command
($cmd, errfunc
=> $parser, outfunc
=> sub{}); };
1414 die $err if $err !~ m/client exit$/;
1420 my $upid = $rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd);
1422 PVE
::Tools
::wait_for_vnc_port
($port);
1425 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1432 password
=> $ticket,
1437 __PACKAGE__-
>register_method({
1439 path
=> '{vmid}/status',
1442 description
=> "Directory index",
1447 additionalProperties
=> 0,
1449 node
=> get_standard_option
('pve-node'),
1450 vmid
=> get_standard_option
('pve-vmid'),
1458 subdir
=> { type
=> 'string' },
1461 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1467 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1470 { subdir
=> 'current' },
1471 { subdir
=> 'start' },
1472 { subdir
=> 'stop' },
1478 my $vm_is_ha_managed = sub {
1481 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1482 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1488 __PACKAGE__-
>register_method({
1489 name
=> 'vm_status',
1490 path
=> '{vmid}/status/current',
1493 protected
=> 1, # qemu pid files are only readable by root
1494 description
=> "Get virtual machine status.",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1505 returns
=> { type
=> 'object' },
1510 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1512 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1513 my $status = $vmstatus->{$param->{vmid
}};
1515 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/status/start',
1526 description
=> "Start virtual machine.",
1528 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid'),
1535 skiplock
=> get_standard_option
('skiplock'),
1536 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1537 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1538 machine
=> get_standard_option
('pve-qm-machine'),
1547 my $rpcenv = PVE
::RPCEnvironment
::get
();
1549 my $authuser = $rpcenv->get_user();
1551 my $node = extract_param
($param, 'node');
1553 my $vmid = extract_param
($param, 'vmid');
1555 my $machine = extract_param
($param, 'machine');
1557 my $stateuri = extract_param
($param, 'stateuri');
1558 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1559 if $stateuri && $authuser ne 'root@pam';
1561 my $skiplock = extract_param
($param, 'skiplock');
1562 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1563 if $skiplock && $authuser ne 'root@pam';
1565 my $migratedfrom = extract_param
($param, 'migratedfrom');
1566 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1567 if $migratedfrom && $authuser ne 'root@pam';
1569 my $storecfg = PVE
::Storage
::config
();
1571 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1572 $rpcenv->{type
} ne 'ha') {
1577 my $service = "pvevm:$vmid";
1579 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1581 print "Executing HA start for VM $vmid\n";
1583 PVE
::Tools
::run_command
($cmd);
1588 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1595 syslog
('info', "start VM $vmid: $upid\n");
1597 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1602 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1606 __PACKAGE__-
>register_method({
1608 path
=> '{vmid}/status/stop',
1612 description
=> "Stop virtual machine.",
1614 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1617 additionalProperties
=> 0,
1619 node
=> get_standard_option
('pve-node'),
1620 vmid
=> get_standard_option
('pve-vmid'),
1621 skiplock
=> get_standard_option
('skiplock'),
1622 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1624 description
=> "Wait maximal timeout seconds.",
1630 description
=> "Do not decativate storage volumes.",
1643 my $rpcenv = PVE
::RPCEnvironment
::get
();
1645 my $authuser = $rpcenv->get_user();
1647 my $node = extract_param
($param, 'node');
1649 my $vmid = extract_param
($param, 'vmid');
1651 my $skiplock = extract_param
($param, 'skiplock');
1652 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1653 if $skiplock && $authuser ne 'root@pam';
1655 my $keepActive = extract_param
($param, 'keepActive');
1656 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1657 if $keepActive && $authuser ne 'root@pam';
1659 my $migratedfrom = extract_param
($param, 'migratedfrom');
1660 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1661 if $migratedfrom && $authuser ne 'root@pam';
1664 my $storecfg = PVE
::Storage
::config
();
1666 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1671 my $service = "pvevm:$vmid";
1673 my $cmd = ['clusvcadm', '-d', $service];
1675 print "Executing HA stop for VM $vmid\n";
1677 PVE
::Tools
::run_command
($cmd);
1682 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1688 syslog
('info', "stop VM $vmid: $upid\n");
1690 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1691 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1696 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1700 __PACKAGE__-
>register_method({
1702 path
=> '{vmid}/status/reset',
1706 description
=> "Reset virtual machine.",
1708 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1711 additionalProperties
=> 0,
1713 node
=> get_standard_option
('pve-node'),
1714 vmid
=> get_standard_option
('pve-vmid'),
1715 skiplock
=> get_standard_option
('skiplock'),
1724 my $rpcenv = PVE
::RPCEnvironment
::get
();
1726 my $authuser = $rpcenv->get_user();
1728 my $node = extract_param
($param, 'node');
1730 my $vmid = extract_param
($param, 'vmid');
1732 my $skiplock = extract_param
($param, 'skiplock');
1733 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1734 if $skiplock && $authuser ne 'root@pam';
1736 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1741 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1746 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1749 __PACKAGE__-
>register_method({
1750 name
=> 'vm_shutdown',
1751 path
=> '{vmid}/status/shutdown',
1755 description
=> "Shutdown virtual machine.",
1757 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1760 additionalProperties
=> 0,
1762 node
=> get_standard_option
('pve-node'),
1763 vmid
=> get_standard_option
('pve-vmid'),
1764 skiplock
=> get_standard_option
('skiplock'),
1766 description
=> "Wait maximal timeout seconds.",
1772 description
=> "Make sure the VM stops.",
1778 description
=> "Do not decativate storage volumes.",
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $node = extract_param
($param, 'node');
1797 my $vmid = extract_param
($param, 'vmid');
1799 my $skiplock = extract_param
($param, 'skiplock');
1800 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1801 if $skiplock && $authuser ne 'root@pam';
1803 my $keepActive = extract_param
($param, 'keepActive');
1804 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1805 if $keepActive && $authuser ne 'root@pam';
1807 my $storecfg = PVE
::Storage
::config
();
1812 syslog
('info', "shutdown VM $vmid: $upid\n");
1814 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1815 1, $param->{forceStop
}, $keepActive);
1820 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1823 __PACKAGE__-
>register_method({
1824 name
=> 'vm_suspend',
1825 path
=> '{vmid}/status/suspend',
1829 description
=> "Suspend virtual machine.",
1831 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid'),
1838 skiplock
=> get_standard_option
('skiplock'),
1847 my $rpcenv = PVE
::RPCEnvironment
::get
();
1849 my $authuser = $rpcenv->get_user();
1851 my $node = extract_param
($param, 'node');
1853 my $vmid = extract_param
($param, 'vmid');
1855 my $skiplock = extract_param
($param, 'skiplock');
1856 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1857 if $skiplock && $authuser ne 'root@pam';
1859 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1864 syslog
('info', "suspend VM $vmid: $upid\n");
1866 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1871 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1874 __PACKAGE__-
>register_method({
1875 name
=> 'vm_resume',
1876 path
=> '{vmid}/status/resume',
1880 description
=> "Resume virtual machine.",
1882 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1885 additionalProperties
=> 0,
1887 node
=> get_standard_option
('pve-node'),
1888 vmid
=> get_standard_option
('pve-vmid'),
1889 skiplock
=> get_standard_option
('skiplock'),
1898 my $rpcenv = PVE
::RPCEnvironment
::get
();
1900 my $authuser = $rpcenv->get_user();
1902 my $node = extract_param
($param, 'node');
1904 my $vmid = extract_param
($param, 'vmid');
1906 my $skiplock = extract_param
($param, 'skiplock');
1907 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1908 if $skiplock && $authuser ne 'root@pam';
1910 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1915 syslog
('info', "resume VM $vmid: $upid\n");
1917 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1922 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_sendkey',
1927 path
=> '{vmid}/sendkey',
1931 description
=> "Send key event to virtual machine.",
1933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1936 additionalProperties
=> 0,
1938 node
=> get_standard_option
('pve-node'),
1939 vmid
=> get_standard_option
('pve-vmid'),
1940 skiplock
=> get_standard_option
('skiplock'),
1942 description
=> "The key (qemu monitor encoding).",
1947 returns
=> { type
=> 'null'},
1951 my $rpcenv = PVE
::RPCEnvironment
::get
();
1953 my $authuser = $rpcenv->get_user();
1955 my $node = extract_param
($param, 'node');
1957 my $vmid = extract_param
($param, 'vmid');
1959 my $skiplock = extract_param
($param, 'skiplock');
1960 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1961 if $skiplock && $authuser ne 'root@pam';
1963 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1968 __PACKAGE__-
>register_method({
1969 name
=> 'vm_feature',
1970 path
=> '{vmid}/feature',
1974 description
=> "Check if feature for virtual machine is available.",
1976 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1979 additionalProperties
=> 0,
1981 node
=> get_standard_option
('pve-node'),
1982 vmid
=> get_standard_option
('pve-vmid'),
1984 description
=> "Feature to check.",
1986 enum
=> [ 'snapshot', 'clone', 'copy' ],
1988 snapname
=> get_standard_option
('pve-snapshot-name', {
1996 hasFeature
=> { type
=> 'boolean' },
1999 items
=> { type
=> 'string' },
2006 my $node = extract_param
($param, 'node');
2008 my $vmid = extract_param
($param, 'vmid');
2010 my $snapname = extract_param
($param, 'snapname');
2012 my $feature = extract_param
($param, 'feature');
2014 my $running = PVE
::QemuServer
::check_running
($vmid);
2016 my $conf = PVE
::QemuServer
::load_config
($vmid);
2019 my $snap = $conf->{snapshots
}->{$snapname};
2020 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2023 my $storecfg = PVE
::Storage
::config
();
2025 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2026 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2029 hasFeature
=> $hasFeature,
2030 nodes
=> [ keys %$nodelist ],
2034 __PACKAGE__-
>register_method({
2036 path
=> '{vmid}/clone',
2040 description
=> "Create a copy of virtual machine/template.",
2042 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2043 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2044 "'Datastore.AllocateSpace' on any used storage.",
2047 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2049 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2050 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2055 additionalProperties
=> 0,
2057 node
=> get_standard_option
('pve-node'),
2058 vmid
=> get_standard_option
('pve-vmid'),
2059 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2062 type
=> 'string', format
=> 'dns-name',
2063 description
=> "Set a name for the new VM.",
2068 description
=> "Description for the new VM.",
2072 type
=> 'string', format
=> 'pve-poolid',
2073 description
=> "Add the new VM to the specified pool.",
2075 snapname
=> get_standard_option
('pve-snapshot-name', {
2079 storage
=> get_standard_option
('pve-storage-id', {
2080 description
=> "Target storage for full clone.",
2085 description
=> "Target format for file storage.",
2089 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2094 description
=> "Create a full copy of all disk. This is always done when " .
2095 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2098 target
=> get_standard_option
('pve-node', {
2099 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $newid = extract_param
($param, 'newid');
2120 my $pool = extract_param
($param, 'pool');
2122 if (defined($pool)) {
2123 $rpcenv->check_pool_exist($pool);
2126 my $snapname = extract_param
($param, 'snapname');
2128 my $storage = extract_param
($param, 'storage');
2130 my $format = extract_param
($param, 'format');
2132 my $target = extract_param
($param, 'target');
2134 my $localnode = PVE
::INotify
::nodename
();
2136 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2138 PVE
::Cluster
::check_node_exists
($target) if $target;
2140 my $storecfg = PVE
::Storage
::config
();
2143 # check if storage is enabled on local node
2144 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2146 # check if storage is available on target node
2147 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2148 # clone only works if target storage is shared
2149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2150 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2154 PVE
::Cluster
::check_cfs_quorum
();
2156 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2158 # exclusive lock if VM is running - else shared lock is enough;
2159 my $shared_lock = $running ?
0 : 1;
2163 # do all tests after lock
2164 # we also try to do all tests before we fork the worker
2166 my $conf = PVE
::QemuServer
::load_config
($vmid);
2168 PVE
::QemuServer
::check_lock
($conf);
2170 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2172 die "unexpected state change\n" if $verify_running != $running;
2174 die "snapshot '$snapname' does not exist\n"
2175 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2177 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2179 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2181 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2183 my $conffile = PVE
::QemuServer
::config_file
($newid);
2185 die "unable to create VM $newid: config file already exists\n"
2188 my $newconf = { lock => 'clone' };
2192 foreach my $opt (keys %$oldconf) {
2193 my $value = $oldconf->{$opt};
2195 # do not copy snapshot related info
2196 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2197 $opt eq 'vmstate' || $opt eq 'snapstate';
2199 # always change MAC! address
2200 if ($opt =~ m/^net(\d+)$/) {
2201 my $net = PVE
::QemuServer
::parse_net
($value);
2202 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2203 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2204 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2205 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2206 $newconf->{$opt} = $value; # simply copy configuration
2208 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2209 die "Full clone feature is not available"
2210 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2213 $drives->{$opt} = $drive;
2214 push @$vollist, $drive->{file
};
2217 # copy everything else
2218 $newconf->{$opt} = $value;
2222 delete $newconf->{template
};
2224 if ($param->{name
}) {
2225 $newconf->{name
} = $param->{name
};
2227 if ($oldconf->{name
}) {
2228 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2230 $newconf->{name
} = "Copy-of-VM-$vmid";
2234 if ($param->{description
}) {
2235 $newconf->{description
} = $param->{description
};
2238 # create empty/temp config - this fails if VM already exists on other node
2239 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2244 my $newvollist = [];
2247 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2249 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2251 foreach my $opt (keys %$drives) {
2252 my $drive = $drives->{$opt};
2254 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2255 $newid, $storage, $format, $drive->{full
}, $newvollist);
2257 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2259 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2262 delete $newconf->{lock};
2263 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2266 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2267 die "Failed to move config to node '$target' - rename failed: $!\n"
2268 if !rename($conffile, $newconffile);
2271 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2276 sleep 1; # some storage like rbd need to wait before release volume - really?
2278 foreach my $volid (@$newvollist) {
2279 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2282 die "clone failed: $err";
2288 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2291 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2292 # Aquire exclusive lock lock for $newid
2293 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2298 __PACKAGE__-
>register_method({
2299 name
=> 'move_vm_disk',
2300 path
=> '{vmid}/move_disk',
2304 description
=> "Move volume to different storage.",
2306 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2307 "and 'Datastore.AllocateSpace' permissions on the storage.",
2310 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2311 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2315 additionalProperties
=> 0,
2317 node
=> get_standard_option
('pve-node'),
2318 vmid
=> get_standard_option
('pve-vmid'),
2321 description
=> "The disk you want to move.",
2322 enum
=> [ PVE
::QemuServer
::disknames
() ],
2324 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2327 description
=> "Target Format.",
2328 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2333 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2339 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2347 description
=> "the task ID.",
2352 my $rpcenv = PVE
::RPCEnvironment
::get
();
2354 my $authuser = $rpcenv->get_user();
2356 my $node = extract_param
($param, 'node');
2358 my $vmid = extract_param
($param, 'vmid');
2360 my $digest = extract_param
($param, 'digest');
2362 my $disk = extract_param
($param, 'disk');
2364 my $storeid = extract_param
($param, 'storage');
2366 my $format = extract_param
($param, 'format');
2368 my $storecfg = PVE
::Storage
::config
();
2370 my $updatefn = sub {
2372 my $conf = PVE
::QemuServer
::load_config
($vmid);
2374 die "checksum missmatch (file change by other user?)\n"
2375 if $digest && $digest ne $conf->{digest
};
2377 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2379 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2381 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2383 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2386 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2387 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2391 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2392 (!$format || !$oldfmt || $oldfmt eq $format);
2394 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2396 my $running = PVE
::QemuServer
::check_running
($vmid);
2398 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2402 my $newvollist = [];
2405 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2407 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2408 $vmid, $storeid, $format, 1, $newvollist);
2410 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2412 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2414 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2418 foreach my $volid (@$newvollist) {
2419 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2422 die "storage migration failed: $err";
2425 if ($param->{delete}) {
2426 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2431 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2434 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2437 __PACKAGE__-
>register_method({
2438 name
=> 'migrate_vm',
2439 path
=> '{vmid}/migrate',
2443 description
=> "Migrate virtual machine. Creates a new migration task.",
2445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2448 additionalProperties
=> 0,
2450 node
=> get_standard_option
('pve-node'),
2451 vmid
=> get_standard_option
('pve-vmid'),
2452 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2455 description
=> "Use online/live migration.",
2460 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2467 description
=> "the task ID.",
2472 my $rpcenv = PVE
::RPCEnvironment
::get
();
2474 my $authuser = $rpcenv->get_user();
2476 my $target = extract_param
($param, 'target');
2478 my $localnode = PVE
::INotify
::nodename
();
2479 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2481 PVE
::Cluster
::check_cfs_quorum
();
2483 PVE
::Cluster
::check_node_exists
($target);
2485 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2487 my $vmid = extract_param
($param, 'vmid');
2489 raise_param_exc
({ force
=> "Only root may use this option." })
2490 if $param->{force
} && $authuser ne 'root@pam';
2493 my $conf = PVE
::QemuServer
::load_config
($vmid);
2495 # try to detect errors early
2497 PVE
::QemuServer
::check_lock
($conf);
2499 if (PVE
::QemuServer
::check_running
($vmid)) {
2500 die "cant migrate running VM without --online\n"
2501 if !$param->{online
};
2504 my $storecfg = PVE
::Storage
::config
();
2505 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2507 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2512 my $service = "pvevm:$vmid";
2514 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2516 print "Executing HA migrate for VM $vmid to node $target\n";
2518 PVE
::Tools
::run_command
($cmd);
2523 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2530 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2533 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2538 __PACKAGE__-
>register_method({
2540 path
=> '{vmid}/monitor',
2544 description
=> "Execute Qemu monitor commands.",
2546 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2549 additionalProperties
=> 0,
2551 node
=> get_standard_option
('pve-node'),
2552 vmid
=> get_standard_option
('pve-vmid'),
2555 description
=> "The monitor command.",
2559 returns
=> { type
=> 'string'},
2563 my $vmid = $param->{vmid
};
2565 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2569 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2571 $res = "ERROR: $@" if $@;
2576 __PACKAGE__-
>register_method({
2577 name
=> 'resize_vm',
2578 path
=> '{vmid}/resize',
2582 description
=> "Extend volume size.",
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2587 additionalProperties
=> 0,
2589 node
=> get_standard_option
('pve-node'),
2590 vmid
=> get_standard_option
('pve-vmid'),
2591 skiplock
=> get_standard_option
('skiplock'),
2594 description
=> "The disk you want to resize.",
2595 enum
=> [PVE
::QemuServer
::disknames
()],
2599 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2600 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.",
2604 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2610 returns
=> { type
=> 'null'},
2614 my $rpcenv = PVE
::RPCEnvironment
::get
();
2616 my $authuser = $rpcenv->get_user();
2618 my $node = extract_param
($param, 'node');
2620 my $vmid = extract_param
($param, 'vmid');
2622 my $digest = extract_param
($param, 'digest');
2624 my $disk = extract_param
($param, 'disk');
2626 my $sizestr = extract_param
($param, 'size');
2628 my $skiplock = extract_param
($param, 'skiplock');
2629 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2630 if $skiplock && $authuser ne 'root@pam';
2632 my $storecfg = PVE
::Storage
::config
();
2634 my $updatefn = sub {
2636 my $conf = PVE
::QemuServer
::load_config
($vmid);
2638 die "checksum missmatch (file change by other user?)\n"
2639 if $digest && $digest ne $conf->{digest
};
2640 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2642 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2644 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2646 my $volid = $drive->{file
};
2648 die "disk '$disk' has no associated volume\n" if !$volid;
2650 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2652 die "you can't online resize a virtio windows bootdisk\n"
2653 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2655 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2657 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2659 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2661 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2662 my ($ext, $newsize, $unit) = ($1, $2, $4);
2665 $newsize = $newsize * 1024;
2666 } elsif ($unit eq 'M') {
2667 $newsize = $newsize * 1024 * 1024;
2668 } elsif ($unit eq 'G') {
2669 $newsize = $newsize * 1024 * 1024 * 1024;
2670 } elsif ($unit eq 'T') {
2671 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2674 $newsize += $size if $ext;
2675 $newsize = int($newsize);
2677 die "unable to skrink disk size\n" if $newsize < $size;
2679 return if $size == $newsize;
2681 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2683 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2685 $drive->{size
} = $newsize;
2686 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2688 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2691 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2695 __PACKAGE__-
>register_method({
2696 name
=> 'snapshot_list',
2697 path
=> '{vmid}/snapshot',
2699 description
=> "List all snapshots.",
2701 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2704 protected
=> 1, # qemu pid files are only readable by root
2706 additionalProperties
=> 0,
2708 vmid
=> get_standard_option
('pve-vmid'),
2709 node
=> get_standard_option
('pve-node'),
2718 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2723 my $vmid = $param->{vmid
};
2725 my $conf = PVE
::QemuServer
::load_config
($vmid);
2726 my $snaphash = $conf->{snapshots
} || {};
2730 foreach my $name (keys %$snaphash) {
2731 my $d = $snaphash->{$name};
2734 snaptime
=> $d->{snaptime
} || 0,
2735 vmstate
=> $d->{vmstate
} ?
1 : 0,
2736 description
=> $d->{description
} || '',
2738 $item->{parent
} = $d->{parent
} if $d->{parent
};
2739 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2743 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2744 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2745 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2747 push @$res, $current;
2752 __PACKAGE__-
>register_method({
2754 path
=> '{vmid}/snapshot',
2758 description
=> "Snapshot a VM.",
2760 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2763 additionalProperties
=> 0,
2765 node
=> get_standard_option
('pve-node'),
2766 vmid
=> get_standard_option
('pve-vmid'),
2767 snapname
=> get_standard_option
('pve-snapshot-name'),
2771 description
=> "Save the vmstate",
2776 description
=> "Freeze the filesystem",
2781 description
=> "A textual description or comment.",
2787 description
=> "the task ID.",
2792 my $rpcenv = PVE
::RPCEnvironment
::get
();
2794 my $authuser = $rpcenv->get_user();
2796 my $node = extract_param
($param, 'node');
2798 my $vmid = extract_param
($param, 'vmid');
2800 my $snapname = extract_param
($param, 'snapname');
2802 die "unable to use snapshot name 'current' (reserved name)\n"
2803 if $snapname eq 'current';
2806 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2807 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2808 $param->{freezefs
}, $param->{description
});
2811 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2814 __PACKAGE__-
>register_method({
2815 name
=> 'snapshot_cmd_idx',
2816 path
=> '{vmid}/snapshot/{snapname}',
2823 additionalProperties
=> 0,
2825 vmid
=> get_standard_option
('pve-vmid'),
2826 node
=> get_standard_option
('pve-node'),
2827 snapname
=> get_standard_option
('pve-snapshot-name'),
2836 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2843 push @$res, { cmd
=> 'rollback' };
2844 push @$res, { cmd
=> 'config' };
2849 __PACKAGE__-
>register_method({
2850 name
=> 'update_snapshot_config',
2851 path
=> '{vmid}/snapshot/{snapname}/config',
2855 description
=> "Update snapshot metadata.",
2857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2860 additionalProperties
=> 0,
2862 node
=> get_standard_option
('pve-node'),
2863 vmid
=> get_standard_option
('pve-vmid'),
2864 snapname
=> get_standard_option
('pve-snapshot-name'),
2868 description
=> "A textual description or comment.",
2872 returns
=> { type
=> 'null' },
2876 my $rpcenv = PVE
::RPCEnvironment
::get
();
2878 my $authuser = $rpcenv->get_user();
2880 my $vmid = extract_param
($param, 'vmid');
2882 my $snapname = extract_param
($param, 'snapname');
2884 return undef if !defined($param->{description
});
2886 my $updatefn = sub {
2888 my $conf = PVE
::QemuServer
::load_config
($vmid);
2890 PVE
::QemuServer
::check_lock
($conf);
2892 my $snap = $conf->{snapshots
}->{$snapname};
2894 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2896 $snap->{description
} = $param->{description
} if defined($param->{description
});
2898 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2901 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2906 __PACKAGE__-
>register_method({
2907 name
=> 'get_snapshot_config',
2908 path
=> '{vmid}/snapshot/{snapname}/config',
2911 description
=> "Get snapshot configuration",
2913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2916 additionalProperties
=> 0,
2918 node
=> get_standard_option
('pve-node'),
2919 vmid
=> get_standard_option
('pve-vmid'),
2920 snapname
=> get_standard_option
('pve-snapshot-name'),
2923 returns
=> { type
=> "object" },
2927 my $rpcenv = PVE
::RPCEnvironment
::get
();
2929 my $authuser = $rpcenv->get_user();
2931 my $vmid = extract_param
($param, 'vmid');
2933 my $snapname = extract_param
($param, 'snapname');
2935 my $conf = PVE
::QemuServer
::load_config
($vmid);
2937 my $snap = $conf->{snapshots
}->{$snapname};
2939 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2944 __PACKAGE__-
>register_method({
2946 path
=> '{vmid}/snapshot/{snapname}/rollback',
2950 description
=> "Rollback VM state to specified snapshot.",
2952 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2955 additionalProperties
=> 0,
2957 node
=> get_standard_option
('pve-node'),
2958 vmid
=> get_standard_option
('pve-vmid'),
2959 snapname
=> get_standard_option
('pve-snapshot-name'),
2964 description
=> "the task ID.",
2969 my $rpcenv = PVE
::RPCEnvironment
::get
();
2971 my $authuser = $rpcenv->get_user();
2973 my $node = extract_param
($param, 'node');
2975 my $vmid = extract_param
($param, 'vmid');
2977 my $snapname = extract_param
($param, 'snapname');
2980 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2981 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2984 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2987 __PACKAGE__-
>register_method({
2988 name
=> 'delsnapshot',
2989 path
=> '{vmid}/snapshot/{snapname}',
2993 description
=> "Delete a VM snapshot.",
2995 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2998 additionalProperties
=> 0,
3000 node
=> get_standard_option
('pve-node'),
3001 vmid
=> get_standard_option
('pve-vmid'),
3002 snapname
=> get_standard_option
('pve-snapshot-name'),
3006 description
=> "For removal from config file, even if removing disk snapshots fails.",
3012 description
=> "the task ID.",
3017 my $rpcenv = PVE
::RPCEnvironment
::get
();
3019 my $authuser = $rpcenv->get_user();
3021 my $node = extract_param
($param, 'node');
3023 my $vmid = extract_param
($param, 'vmid');
3025 my $snapname = extract_param
($param, 'snapname');
3028 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3029 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3032 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3035 __PACKAGE__-
>register_method({
3037 path
=> '{vmid}/template',
3041 description
=> "Create a Template.",
3043 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3044 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3047 additionalProperties
=> 0,
3049 node
=> get_standard_option
('pve-node'),
3050 vmid
=> get_standard_option
('pve-vmid'),
3054 description
=> "If you want to convert only 1 disk to base image.",
3055 enum
=> [PVE
::QemuServer
::disknames
()],
3060 returns
=> { type
=> 'null'},
3064 my $rpcenv = PVE
::RPCEnvironment
::get
();
3066 my $authuser = $rpcenv->get_user();
3068 my $node = extract_param
($param, 'node');
3070 my $vmid = extract_param
($param, 'vmid');
3072 my $disk = extract_param
($param, 'disk');
3074 my $updatefn = sub {
3076 my $conf = PVE
::QemuServer
::load_config
($vmid);
3078 PVE
::QemuServer
::check_lock
($conf);
3080 die "unable to create template, because VM contains snapshots\n"
3081 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3083 die "you can't convert a template to a template\n"
3084 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3086 die "you can't convert a VM to template if VM is running\n"
3087 if PVE
::QemuServer
::check_running
($vmid);
3090 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3093 $conf->{template
} = 1;
3094 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3096 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3099 PVE
::QemuServer
::lock_config
($vmid, $updatefn);