1 package PVE
::API2
::Qemu
;
8 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
10 use PVE
::Tools
qw(extract_param);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
18 use PVE
::AccessControl
;
22 use Data
::Dumper
; # fixme: remove
24 use base
qw(PVE::RESTHandler);
26 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.";
28 my $resolve_cdrom_alias = sub {
31 if (my $value = $param->{cdrom
}) {
32 $value .= ",media=cdrom" if $value !~ m/media=/;
33 $param->{ide2
} = $value;
34 delete $param->{cdrom
};
39 my $check_storage_access = sub {
40 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
42 PVE
::QemuServer
::foreach_drive
($settings, sub {
43 my ($ds, $drive) = @_;
45 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
47 my $volid = $drive->{file
};
49 if (!$volid || $volid eq 'none') {
51 } elsif ($isCDROM && ($volid eq 'cdrom')) {
52 $rpcenv->check($authuser, "/", ['Sys.Console']);
53 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
54 my ($storeid, $size) = ($2 || $default_storage, $3);
55 die "no storage ID specified (and no default storage)\n" if !$storeid;
56 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
58 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
63 my $check_storage_access_clone = sub {
64 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
68 PVE
::QemuServer
::foreach_drive
($conf, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
75 return if !$volid || $volid eq 'none';
78 if ($volid eq 'cdrom') {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
81 # we simply allow access
82 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
83 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
84 $sharedvm = 0 if !$scfg->{shared
};
88 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
89 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
90 $sharedvm = 0 if !$scfg->{shared
};
92 $sid = $storage if $storage;
93 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
100 # Note: $pool is only needed when creating a VM, because pool permissions
101 # are automatically inherited if VM already exists inside a pool.
102 my $create_disks = sub {
103 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
108 PVE
::QemuServer
::foreach_drive
($settings, sub {
109 my ($ds, $disk) = @_;
111 my $volid = $disk->{file
};
113 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
114 delete $disk->{size
};
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
116 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
117 my ($storeid, $size) = ($2 || $default_storage, $3);
118 die "no storage ID specified (and no default storage)\n" if !$storeid;
119 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
120 my $fmt = $disk->{format
} || $defformat;
121 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
122 $fmt, undef, $size*1024*1024);
123 $disk->{file
} = $volid;
124 $disk->{size
} = $size*1024*1024*1024;
125 push @$vollist, $volid;
126 delete $disk->{format
}; # no longer needed
127 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
130 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
132 my $volid_is_new = 1;
135 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
136 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
141 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
143 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
145 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
147 die "volume $volid does not exists\n" if !$size;
149 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
378 if PVE
::Storage
::parse_volume_id
($archive, 1);
380 die "can't find archive file '$archive'\n" if !($path && -f
$path);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
448 die "create failed - $err";
451 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
454 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
457 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
460 __PACKAGE__-
>register_method({
465 description
=> "Directory index",
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
473 vmid
=> get_standard_option
('pve-vmid'),
481 subdir
=> { type
=> 'string' },
484 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'unlink' },
493 { subdir
=> 'vncproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'resize' },
496 { subdir
=> 'move' },
498 { subdir
=> 'rrddata' },
499 { subdir
=> 'monitor' },
500 { subdir
=> 'snapshot' },
501 { subdir
=> 'spiceproxy' },
507 __PACKAGE__-
>register_method({
509 path
=> '{vmid}/rrd',
511 protected
=> 1, # fixme: can we avoid that?
513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
515 description
=> "Read VM RRD statistics (returns PNG)",
517 additionalProperties
=> 0,
519 node
=> get_standard_option
('pve-node'),
520 vmid
=> get_standard_option
('pve-vmid'),
522 description
=> "Specify the time frame you are interested in.",
524 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
527 description
=> "The list of datasources you want to display.",
528 type
=> 'string', format
=> 'pve-configid-list',
531 description
=> "The RRD consolidation function",
533 enum
=> [ 'AVERAGE', 'MAX' ],
541 filename
=> { type
=> 'string' },
547 return PVE
::Cluster
::create_rrd_graph
(
548 "pve2-vm/$param->{vmid}", $param->{timeframe
},
549 $param->{ds
}, $param->{cf
});
553 __PACKAGE__-
>register_method({
555 path
=> '{vmid}/rrddata',
557 protected
=> 1, # fixme: can we avoid that?
559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
561 description
=> "Read VM RRD statistics",
563 additionalProperties
=> 0,
565 node
=> get_standard_option
('pve-node'),
566 vmid
=> get_standard_option
('pve-vmid'),
568 description
=> "Specify the time frame you are interested in.",
570 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
573 description
=> "The RRD consolidation function",
575 enum
=> [ 'AVERAGE', 'MAX' ],
590 return PVE
::Cluster
::create_rrd_data
(
591 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
595 __PACKAGE__-
>register_method({
597 path
=> '{vmid}/config',
600 description
=> "Get virtual machine configuration.",
602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
605 additionalProperties
=> 0,
607 node
=> get_standard_option
('pve-node'),
608 vmid
=> get_standard_option
('pve-vmid'),
616 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
623 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
625 delete $conf->{snapshots
};
630 my $vm_is_volid_owner = sub {
631 my ($storecfg, $vmid, $volid) =@_;
633 if ($volid !~ m
|^/|) {
635 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
636 if ($owner && ($owner == $vmid)) {
644 my $test_deallocate_drive = sub {
645 my ($storecfg, $vmid, $key, $drive, $force) = @_;
647 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
648 my $volid = $drive->{file
};
649 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
650 if ($force || $key =~ m/^unused/) {
651 my $sid = PVE
::Storage
::parse_volume_id
($volid);
660 my $delete_drive = sub {
661 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
663 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
664 my $volid = $drive->{file
};
666 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
667 if ($force || $key =~ m/^unused/) {
669 # check if the disk is really unused
670 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
671 my $path = PVE
::Storage
::path
($storecfg, $volid);
673 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
674 if $used_paths->{$path};
676 PVE
::Storage
::vdisk_free
($storecfg, $volid);
680 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
685 delete $conf->{$key};
688 my $vmconfig_delete_option = sub {
689 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
691 return if !defined($conf->{$opt});
693 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
696 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
698 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
699 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
700 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
704 my $unplugwarning = "";
705 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
706 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
707 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
708 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
709 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
710 $unplugwarning = "<br>verify that your guest support acpi hotplug";
713 if($opt eq 'tablet'){
714 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
716 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
720 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
721 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
723 delete $conf->{$opt};
726 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
729 my $safe_num_ne = sub {
732 return 0 if !defined($a) && !defined($b);
733 return 1 if !defined($a);
734 return 1 if !defined($b);
739 my $vmconfig_update_disk = sub {
740 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
742 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
744 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
745 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
747 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
752 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
754 my $media = $drive->{media
} || 'disk';
755 my $oldmedia = $old_drive->{media
} || 'disk';
756 die "unable to change media type\n" if $media ne $oldmedia;
758 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
759 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
761 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
762 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
765 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
766 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
767 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
768 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
769 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
770 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
771 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
772 ($drive->{mbps
} || 0)*1024*1024,
773 ($drive->{mbps_rd
} || 0)*1024*1024,
774 ($drive->{mbps_wr
} || 0)*1024*1024,
776 $drive->{iops_rd
} || 0,
777 $drive->{iops_wr
} || 0)
778 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
783 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
784 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
786 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
787 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
789 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
791 if (PVE
::QemuServer
::check_running
($vmid)) {
792 if ($drive->{file
} eq 'none') {
793 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
795 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
797 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
801 } else { # hotplug new disks
803 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
807 my $vmconfig_update_net = sub {
808 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
810 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
811 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
812 my $newnet = PVE
::QemuServer
::parse_net
($value);
814 if($oldnet->{model
} ne $newnet->{model
}){
815 #if model change, we try to hot-unplug
816 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
819 if($newnet->{bridge
} && $oldnet->{bridge
}){
820 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
822 if($newnet->{rate
} ne $oldnet->{rate
}){
823 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
826 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
827 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
828 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
832 #if bridge/nat mode change, we try to hot-unplug
833 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
838 $conf->{$opt} = $value;
839 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
840 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
842 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
844 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
847 # POST/PUT {vmid}/config implementation
849 # The original API used PUT (idempotent) an we assumed that all operations
850 # are fast. But it turned out that almost any configuration change can
851 # involve hot-plug actions, or disk alloc/free. Such actions can take long
852 # time to complete and have side effects (not idempotent).
854 # The new implementation uses POST and forks a worker process. We added
855 # a new option 'background_delay'. If specified we wait up to
856 # 'background_delay' second for the worker task to complete. It returns null
857 # if the task is finished within that time, else we return the UPID.
859 my $update_vm_api = sub {
860 my ($param, $sync) = @_;
862 my $rpcenv = PVE
::RPCEnvironment
::get
();
864 my $authuser = $rpcenv->get_user();
866 my $node = extract_param
($param, 'node');
868 my $vmid = extract_param
($param, 'vmid');
870 my $digest = extract_param
($param, 'digest');
872 my $background_delay = extract_param
($param, 'background_delay');
874 my @paramarr = (); # used for log message
875 foreach my $key (keys %$param) {
876 push @paramarr, "-$key", $param->{$key};
879 my $skiplock = extract_param
($param, 'skiplock');
880 raise_param_exc
({ skiplock
=> "Only root may use this option." })
881 if $skiplock && $authuser ne 'root@pam';
883 my $delete_str = extract_param
($param, 'delete');
885 my $force = extract_param
($param, 'force');
887 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
889 my $storecfg = PVE
::Storage
::config
();
891 my $defaults = PVE
::QemuServer
::load_defaults
();
893 &$resolve_cdrom_alias($param);
895 # now try to verify all parameters
898 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
899 $opt = 'ide2' if $opt eq 'cdrom';
900 raise_param_exc
({ delete => "you can't use '-$opt' and " .
901 "-delete $opt' at the same time" })
902 if defined($param->{$opt});
904 if (!PVE
::QemuServer
::option_exists
($opt)) {
905 raise_param_exc
({ delete => "unknown option '$opt'" });
911 foreach my $opt (keys %$param) {
912 if (PVE
::QemuServer
::valid_drivename
($opt)) {
914 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
915 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
916 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
917 } elsif ($opt =~ m/^net(\d+)$/) {
919 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
920 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
924 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
926 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
928 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
932 my $conf = PVE
::QemuServer
::load_config
($vmid);
934 die "checksum missmatch (file change by other user?)\n"
935 if $digest && $digest ne $conf->{digest
};
937 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
939 if ($param->{memory
} || defined($param->{balloon
})) {
940 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
941 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
943 die "balloon value too large (must be smaller than assigned memory)\n"
944 if $balloon && $balloon > $maxmem;
947 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
951 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
953 foreach my $opt (@delete) { # delete
954 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
955 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
958 my $running = PVE
::QemuServer
::check_running
($vmid);
960 foreach my $opt (keys %$param) { # add/change
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
964 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
966 if (PVE
::QemuServer
::valid_drivename
($opt)) {
968 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt}, $force);
971 } elsif ($opt =~ m/^net(\d+)$/) { #nics
973 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
974 $opt, $param->{$opt});
978 if($opt eq 'tablet' && $param->{$opt} == 1){
979 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
980 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
981 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
984 $conf->{$opt} = $param->{$opt};
985 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
989 # allow manual ballooning if shares is set to zero
990 if ($running && defined($param->{balloon
}) &&
991 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
992 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
993 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1001 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1003 if ($background_delay) {
1005 # Note: It would be better to do that in the Event based HTTPServer
1006 # to avoid blocking call to sleep.
1008 my $end_time = time() + $background_delay;
1010 my $task = PVE
::Tools
::upid_decode
($upid);
1013 while (time() < $end_time) {
1014 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1016 sleep(1); # this gets interrupted when child process ends
1020 my $status = PVE
::Tools
::upid_read_status
($upid);
1021 return undef if $status eq 'OK';
1030 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1033 my $vm_config_perm_list = [
1038 'VM.Config.Network',
1040 'VM.Config.Options',
1043 __PACKAGE__-
>register_method({
1044 name
=> 'update_vm_async',
1045 path
=> '{vmid}/config',
1049 description
=> "Set virtual machine options (asynchrounous API).",
1051 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1054 additionalProperties
=> 0,
1055 properties
=> PVE
::QemuServer
::json_config_properties
(
1057 node
=> get_standard_option
('pve-node'),
1058 vmid
=> get_standard_option
('pve-vmid'),
1059 skiplock
=> get_standard_option
('skiplock'),
1061 type
=> 'string', format
=> 'pve-configid-list',
1062 description
=> "A list of settings you want to delete.",
1067 description
=> $opt_force_description,
1069 requires
=> 'delete',
1073 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1077 background_delay
=> {
1079 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1090 code
=> $update_vm_api,
1093 __PACKAGE__-
>register_method({
1094 name
=> 'update_vm',
1095 path
=> '{vmid}/config',
1099 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1101 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1104 additionalProperties
=> 0,
1105 properties
=> PVE
::QemuServer
::json_config_properties
(
1107 node
=> get_standard_option
('pve-node'),
1108 vmid
=> get_standard_option
('pve-vmid'),
1109 skiplock
=> get_standard_option
('skiplock'),
1111 type
=> 'string', format
=> 'pve-configid-list',
1112 description
=> "A list of settings you want to delete.",
1117 description
=> $opt_force_description,
1119 requires
=> 'delete',
1123 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1129 returns
=> { type
=> 'null' },
1132 &$update_vm_api($param, 1);
1138 __PACKAGE__-
>register_method({
1139 name
=> 'destroy_vm',
1144 description
=> "Destroy the vm (also delete all used/owned volumes).",
1146 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1149 additionalProperties
=> 0,
1151 node
=> get_standard_option
('pve-node'),
1152 vmid
=> get_standard_option
('pve-vmid'),
1153 skiplock
=> get_standard_option
('skiplock'),
1162 my $rpcenv = PVE
::RPCEnvironment
::get
();
1164 my $authuser = $rpcenv->get_user();
1166 my $vmid = $param->{vmid
};
1168 my $skiplock = $param->{skiplock
};
1169 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1170 if $skiplock && $authuser ne 'root@pam';
1173 my $conf = PVE
::QemuServer
::load_config
($vmid);
1175 my $storecfg = PVE
::Storage
::config
();
1177 my $delVMfromPoolFn = sub {
1178 my $usercfg = cfs_read_file
("user.cfg");
1179 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1180 if (my $data = $usercfg->{pools
}->{$pool}) {
1181 delete $data->{vms
}->{$vmid};
1182 delete $usercfg->{vms
}->{$vmid};
1183 cfs_write_file
("user.cfg", $usercfg);
1191 syslog
('info', "destroy VM $vmid: $upid\n");
1193 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1195 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1198 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1201 __PACKAGE__-
>register_method({
1203 path
=> '{vmid}/unlink',
1207 description
=> "Unlink/delete disk images.",
1209 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1212 additionalProperties
=> 0,
1214 node
=> get_standard_option
('pve-node'),
1215 vmid
=> get_standard_option
('pve-vmid'),
1217 type
=> 'string', format
=> 'pve-configid-list',
1218 description
=> "A list of disk IDs you want to delete.",
1222 description
=> $opt_force_description,
1227 returns
=> { type
=> 'null'},
1231 $param->{delete} = extract_param
($param, 'idlist');
1233 __PACKAGE__-
>update_vm($param);
1240 __PACKAGE__-
>register_method({
1242 path
=> '{vmid}/vncproxy',
1246 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1248 description
=> "Creates a TCP VNC proxy connections.",
1250 additionalProperties
=> 0,
1252 node
=> get_standard_option
('pve-node'),
1253 vmid
=> get_standard_option
('pve-vmid'),
1257 additionalProperties
=> 0,
1259 user
=> { type
=> 'string' },
1260 ticket
=> { type
=> 'string' },
1261 cert
=> { type
=> 'string' },
1262 port
=> { type
=> 'integer' },
1263 upid
=> { type
=> 'string' },
1269 my $rpcenv = PVE
::RPCEnvironment
::get
();
1271 my $authuser = $rpcenv->get_user();
1273 my $vmid = $param->{vmid
};
1274 my $node = $param->{node
};
1276 my $authpath = "/vms/$vmid";
1278 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1280 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1283 my $port = PVE
::Tools
::next_vnc_port
();
1287 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1288 $remip = PVE
::Cluster
::remote_node_ip
($node);
1291 # NOTE: kvm VNC traffic is already TLS encrypted
1292 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1299 syslog
('info', "starting vnc proxy $upid\n");
1301 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1303 my $qmstr = join(' ', @$qmcmd);
1305 # also redirect stderr (else we get RFB protocol errors)
1306 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1308 PVE
::Tools
::run_command
($cmd);
1313 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1315 PVE
::Tools
::wait_for_vnc_port
($port);
1326 __PACKAGE__-
>register_method({
1327 name
=> 'spiceproxy',
1328 path
=> '{vmid}/spiceproxy',
1331 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1335 description
=> "Returns a SPICE configuration to connect to the VM.",
1337 additionalProperties
=> 0,
1339 node
=> get_standard_option
('pve-node'),
1340 vmid
=> get_standard_option
('pve-vmid'),
1342 description
=> "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
1343 type
=> 'string', format
=> 'dns-name',
1349 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1350 additionalProperties
=> 1,
1352 type
=> { type
=> 'string' },
1353 password
=> { type
=> 'string' },
1354 proxy
=> { type
=> 'string' },
1355 host
=> { type
=> 'string' },
1356 'tls-port' => { type
=> 'integer' },
1362 my $rpcenv = PVE
::RPCEnvironment
::get
();
1364 my $authuser = $rpcenv->get_user();
1366 my $vmid = $param->{vmid
};
1367 my $node = $param->{node
};
1368 my $proxy = $param->{proxy
};
1370 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1374 my $port = PVE
::QemuServer
::spice_port
($vmid);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1376 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1379 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1384 my $filename = "/etc/pve/local/pve-ssl.pem";
1385 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1387 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1388 $cacert =~ s/\n/\\n/g;
1392 title
=> "VM $vmid",
1393 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1394 proxy
=> "http://$proxy:3128",
1395 'tls-port' => $port,
1396 'host-subject' => $subject,
1398 password
=> $ticket,
1399 'delete-this-file' => 1,
1403 __PACKAGE__-
>register_method({
1405 path
=> '{vmid}/status',
1408 description
=> "Directory index",
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid'),
1424 subdir
=> { type
=> 'string' },
1427 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1433 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1436 { subdir
=> 'current' },
1437 { subdir
=> 'start' },
1438 { subdir
=> 'stop' },
1444 my $vm_is_ha_managed = sub {
1447 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1448 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1454 __PACKAGE__-
>register_method({
1455 name
=> 'vm_status',
1456 path
=> '{vmid}/status/current',
1459 protected
=> 1, # qemu pid files are only readable by root
1460 description
=> "Get virtual machine status.",
1462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1465 additionalProperties
=> 0,
1467 node
=> get_standard_option
('pve-node'),
1468 vmid
=> get_standard_option
('pve-vmid'),
1471 returns
=> { type
=> 'object' },
1476 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1478 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1479 my $status = $vmstatus->{$param->{vmid
}};
1481 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1483 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1488 __PACKAGE__-
>register_method({
1490 path
=> '{vmid}/status/start',
1494 description
=> "Start virtual machine.",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1503 skiplock
=> get_standard_option
('skiplock'),
1504 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1505 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1506 machine
=> get_standard_option
('pve-qm-machine'),
1515 my $rpcenv = PVE
::RPCEnvironment
::get
();
1517 my $authuser = $rpcenv->get_user();
1519 my $node = extract_param
($param, 'node');
1521 my $vmid = extract_param
($param, 'vmid');
1523 my $machine = extract_param
($param, 'machine');
1525 my $stateuri = extract_param
($param, 'stateuri');
1526 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1527 if $stateuri && $authuser ne 'root@pam';
1529 my $skiplock = extract_param
($param, 'skiplock');
1530 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1531 if $skiplock && $authuser ne 'root@pam';
1533 my $migratedfrom = extract_param
($param, 'migratedfrom');
1534 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1535 if $migratedfrom && $authuser ne 'root@pam';
1537 my $storecfg = PVE
::Storage
::config
();
1539 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1540 $rpcenv->{type
} ne 'ha') {
1545 my $service = "pvevm:$vmid";
1547 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1549 print "Executing HA start for VM $vmid\n";
1551 PVE
::Tools
::run_command
($cmd);
1556 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1563 syslog
('info', "start VM $vmid: $upid\n");
1565 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1570 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1574 __PACKAGE__-
>register_method({
1576 path
=> '{vmid}/status/stop',
1580 description
=> "Stop virtual machine.",
1582 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1585 additionalProperties
=> 0,
1587 node
=> get_standard_option
('pve-node'),
1588 vmid
=> get_standard_option
('pve-vmid'),
1589 skiplock
=> get_standard_option
('skiplock'),
1590 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1592 description
=> "Wait maximal timeout seconds.",
1598 description
=> "Do not decativate storage volumes.",
1611 my $rpcenv = PVE
::RPCEnvironment
::get
();
1613 my $authuser = $rpcenv->get_user();
1615 my $node = extract_param
($param, 'node');
1617 my $vmid = extract_param
($param, 'vmid');
1619 my $skiplock = extract_param
($param, 'skiplock');
1620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1621 if $skiplock && $authuser ne 'root@pam';
1623 my $keepActive = extract_param
($param, 'keepActive');
1624 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1625 if $keepActive && $authuser ne 'root@pam';
1627 my $migratedfrom = extract_param
($param, 'migratedfrom');
1628 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1629 if $migratedfrom && $authuser ne 'root@pam';
1632 my $storecfg = PVE
::Storage
::config
();
1634 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1639 my $service = "pvevm:$vmid";
1641 my $cmd = ['clusvcadm', '-d', $service];
1643 print "Executing HA stop for VM $vmid\n";
1645 PVE
::Tools
::run_command
($cmd);
1650 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1656 syslog
('info', "stop VM $vmid: $upid\n");
1658 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1659 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1664 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1668 __PACKAGE__-
>register_method({
1670 path
=> '{vmid}/status/reset',
1674 description
=> "Reset virtual machine.",
1676 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1679 additionalProperties
=> 0,
1681 node
=> get_standard_option
('pve-node'),
1682 vmid
=> get_standard_option
('pve-vmid'),
1683 skiplock
=> get_standard_option
('skiplock'),
1692 my $rpcenv = PVE
::RPCEnvironment
::get
();
1694 my $authuser = $rpcenv->get_user();
1696 my $node = extract_param
($param, 'node');
1698 my $vmid = extract_param
($param, 'vmid');
1700 my $skiplock = extract_param
($param, 'skiplock');
1701 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1702 if $skiplock && $authuser ne 'root@pam';
1704 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1709 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1714 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1717 __PACKAGE__-
>register_method({
1718 name
=> 'vm_shutdown',
1719 path
=> '{vmid}/status/shutdown',
1723 description
=> "Shutdown virtual machine.",
1725 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1728 additionalProperties
=> 0,
1730 node
=> get_standard_option
('pve-node'),
1731 vmid
=> get_standard_option
('pve-vmid'),
1732 skiplock
=> get_standard_option
('skiplock'),
1734 description
=> "Wait maximal timeout seconds.",
1740 description
=> "Make sure the VM stops.",
1746 description
=> "Do not decativate storage volumes.",
1759 my $rpcenv = PVE
::RPCEnvironment
::get
();
1761 my $authuser = $rpcenv->get_user();
1763 my $node = extract_param
($param, 'node');
1765 my $vmid = extract_param
($param, 'vmid');
1767 my $skiplock = extract_param
($param, 'skiplock');
1768 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1769 if $skiplock && $authuser ne 'root@pam';
1771 my $keepActive = extract_param
($param, 'keepActive');
1772 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1773 if $keepActive && $authuser ne 'root@pam';
1775 my $storecfg = PVE
::Storage
::config
();
1780 syslog
('info', "shutdown VM $vmid: $upid\n");
1782 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1783 1, $param->{forceStop
}, $keepActive);
1788 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1791 __PACKAGE__-
>register_method({
1792 name
=> 'vm_suspend',
1793 path
=> '{vmid}/status/suspend',
1797 description
=> "Suspend virtual machine.",
1799 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1806 skiplock
=> get_standard_option
('skiplock'),
1815 my $rpcenv = PVE
::RPCEnvironment
::get
();
1817 my $authuser = $rpcenv->get_user();
1819 my $node = extract_param
($param, 'node');
1821 my $vmid = extract_param
($param, 'vmid');
1823 my $skiplock = extract_param
($param, 'skiplock');
1824 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1825 if $skiplock && $authuser ne 'root@pam';
1827 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1832 syslog
('info', "suspend VM $vmid: $upid\n");
1834 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1839 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1842 __PACKAGE__-
>register_method({
1843 name
=> 'vm_resume',
1844 path
=> '{vmid}/status/resume',
1848 description
=> "Resume virtual machine.",
1850 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1853 additionalProperties
=> 0,
1855 node
=> get_standard_option
('pve-node'),
1856 vmid
=> get_standard_option
('pve-vmid'),
1857 skiplock
=> get_standard_option
('skiplock'),
1866 my $rpcenv = PVE
::RPCEnvironment
::get
();
1868 my $authuser = $rpcenv->get_user();
1870 my $node = extract_param
($param, 'node');
1872 my $vmid = extract_param
($param, 'vmid');
1874 my $skiplock = extract_param
($param, 'skiplock');
1875 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1876 if $skiplock && $authuser ne 'root@pam';
1878 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1883 syslog
('info', "resume VM $vmid: $upid\n");
1885 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1890 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1893 __PACKAGE__-
>register_method({
1894 name
=> 'vm_sendkey',
1895 path
=> '{vmid}/sendkey',
1899 description
=> "Send key event to virtual machine.",
1901 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1904 additionalProperties
=> 0,
1906 node
=> get_standard_option
('pve-node'),
1907 vmid
=> get_standard_option
('pve-vmid'),
1908 skiplock
=> get_standard_option
('skiplock'),
1910 description
=> "The key (qemu monitor encoding).",
1915 returns
=> { type
=> 'null'},
1919 my $rpcenv = PVE
::RPCEnvironment
::get
();
1921 my $authuser = $rpcenv->get_user();
1923 my $node = extract_param
($param, 'node');
1925 my $vmid = extract_param
($param, 'vmid');
1927 my $skiplock = extract_param
($param, 'skiplock');
1928 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1929 if $skiplock && $authuser ne 'root@pam';
1931 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1936 __PACKAGE__-
>register_method({
1937 name
=> 'vm_feature',
1938 path
=> '{vmid}/feature',
1942 description
=> "Check if feature for virtual machine is available.",
1944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1947 additionalProperties
=> 0,
1949 node
=> get_standard_option
('pve-node'),
1950 vmid
=> get_standard_option
('pve-vmid'),
1952 description
=> "Feature to check.",
1954 enum
=> [ 'snapshot', 'clone', 'copy' ],
1956 snapname
=> get_standard_option
('pve-snapshot-name', {
1964 hasFeature
=> { type
=> 'boolean' },
1967 items
=> { type
=> 'string' },
1974 my $node = extract_param
($param, 'node');
1976 my $vmid = extract_param
($param, 'vmid');
1978 my $snapname = extract_param
($param, 'snapname');
1980 my $feature = extract_param
($param, 'feature');
1982 my $running = PVE
::QemuServer
::check_running
($vmid);
1984 my $conf = PVE
::QemuServer
::load_config
($vmid);
1987 my $snap = $conf->{snapshots
}->{$snapname};
1988 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1991 my $storecfg = PVE
::Storage
::config
();
1993 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1994 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1997 hasFeature
=> $hasFeature,
1998 nodes
=> [ keys %$nodelist ],
2002 __PACKAGE__-
>register_method({
2004 path
=> '{vmid}/clone',
2008 description
=> "Create a copy of virtual machine/template.",
2010 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2011 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2012 "'Datastore.AllocateSpace' on any used storage.",
2015 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2017 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2018 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2023 additionalProperties
=> 0,
2025 node
=> get_standard_option
('pve-node'),
2026 vmid
=> get_standard_option
('pve-vmid'),
2027 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2030 type
=> 'string', format
=> 'dns-name',
2031 description
=> "Set a name for the new VM.",
2036 description
=> "Description for the new VM.",
2040 type
=> 'string', format
=> 'pve-poolid',
2041 description
=> "Add the new VM to the specified pool.",
2043 snapname
=> get_standard_option
('pve-snapshot-name', {
2047 storage
=> get_standard_option
('pve-storage-id', {
2048 description
=> "Target storage for full clone.",
2053 description
=> "Target format for file storage.",
2057 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2062 description
=> "Create a full copy of all disk. This is always done when " .
2063 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2066 target
=> get_standard_option
('pve-node', {
2067 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2078 my $rpcenv = PVE
::RPCEnvironment
::get
();
2080 my $authuser = $rpcenv->get_user();
2082 my $node = extract_param
($param, 'node');
2084 my $vmid = extract_param
($param, 'vmid');
2086 my $newid = extract_param
($param, 'newid');
2088 my $pool = extract_param
($param, 'pool');
2090 if (defined($pool)) {
2091 $rpcenv->check_pool_exist($pool);
2094 my $snapname = extract_param
($param, 'snapname');
2096 my $storage = extract_param
($param, 'storage');
2098 my $format = extract_param
($param, 'format');
2100 my $target = extract_param
($param, 'target');
2102 my $localnode = PVE
::INotify
::nodename
();
2104 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2106 PVE
::Cluster
::check_node_exists
($target) if $target;
2108 my $storecfg = PVE
::Storage
::config
();
2111 # check if storage is enabled on local node
2112 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2114 # check if storage is available on target node
2115 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2116 # clone only works if target storage is shared
2117 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2118 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2122 PVE
::Cluster
::check_cfs_quorum
();
2124 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2126 # exclusive lock if VM is running - else shared lock is enough;
2127 my $shared_lock = $running ?
0 : 1;
2131 # do all tests after lock
2132 # we also try to do all tests before we fork the worker
2134 my $conf = PVE
::QemuServer
::load_config
($vmid);
2136 PVE
::QemuServer
::check_lock
($conf);
2138 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2140 die "unexpected state change\n" if $verify_running != $running;
2142 die "snapshot '$snapname' does not exist\n"
2143 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2145 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2147 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2149 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2151 my $conffile = PVE
::QemuServer
::config_file
($newid);
2153 die "unable to create VM $newid: config file already exists\n"
2156 my $newconf = { lock => 'clone' };
2160 foreach my $opt (keys %$oldconf) {
2161 my $value = $oldconf->{$opt};
2163 # do not copy snapshot related info
2164 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2165 $opt eq 'vmstate' || $opt eq 'snapstate';
2167 # always change MAC! address
2168 if ($opt =~ m/^net(\d+)$/) {
2169 my $net = PVE
::QemuServer
::parse_net
($value);
2170 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2171 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2172 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2173 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2174 $newconf->{$opt} = $value; # simply copy configuration
2176 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2177 die "Full clone feature is not available"
2178 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2181 $drives->{$opt} = $drive;
2182 push @$vollist, $drive->{file
};
2185 # copy everything else
2186 $newconf->{$opt} = $value;
2190 delete $newconf->{template
};
2192 if ($param->{name
}) {
2193 $newconf->{name
} = $param->{name
};
2195 if ($oldconf->{name
}) {
2196 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2198 $newconf->{name
} = "Copy-of-VM-$vmid";
2202 if ($param->{description
}) {
2203 $newconf->{description
} = $param->{description
};
2206 # create empty/temp config - this fails if VM already exists on other node
2207 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2212 my $newvollist = [];
2215 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2217 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2219 foreach my $opt (keys %$drives) {
2220 my $drive = $drives->{$opt};
2222 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2223 $newid, $storage, $format, $drive->{full
}, $newvollist);
2225 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2227 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2230 delete $newconf->{lock};
2231 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2234 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2235 die "Failed to move config to node '$target' - rename failed: $!\n"
2236 if !rename($conffile, $newconffile);
2239 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2244 sleep 1; # some storage like rbd need to wait before release volume - really?
2246 foreach my $volid (@$newvollist) {
2247 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2250 die "clone failed: $err";
2256 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2259 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2260 # Aquire exclusive lock lock for $newid
2261 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2266 __PACKAGE__-
>register_method({
2267 name
=> 'move_vm_disk',
2268 path
=> '{vmid}/move_disk',
2272 description
=> "Move volume to different storage.",
2274 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2275 "and 'Datastore.AllocateSpace' permissions on the storage.",
2278 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2279 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2283 additionalProperties
=> 0,
2285 node
=> get_standard_option
('pve-node'),
2286 vmid
=> get_standard_option
('pve-vmid'),
2289 description
=> "The disk you want to move.",
2290 enum
=> [ PVE
::QemuServer
::disknames
() ],
2292 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2295 description
=> "Target Format.",
2296 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2301 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2307 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2315 description
=> "the task ID.",
2320 my $rpcenv = PVE
::RPCEnvironment
::get
();
2322 my $authuser = $rpcenv->get_user();
2324 my $node = extract_param
($param, 'node');
2326 my $vmid = extract_param
($param, 'vmid');
2328 my $digest = extract_param
($param, 'digest');
2330 my $disk = extract_param
($param, 'disk');
2332 my $storeid = extract_param
($param, 'storage');
2334 my $format = extract_param
($param, 'format');
2336 my $storecfg = PVE
::Storage
::config
();
2338 my $updatefn = sub {
2340 my $conf = PVE
::QemuServer
::load_config
($vmid);
2342 die "checksum missmatch (file change by other user?)\n"
2343 if $digest && $digest ne $conf->{digest
};
2345 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2347 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2349 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2351 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2354 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2355 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2359 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2360 (!$format || !$oldfmt || $oldfmt eq $format);
2362 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2364 my $running = PVE
::QemuServer
::check_running
($vmid);
2366 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2370 my $newvollist = [];
2373 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2375 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2376 $vmid, $storeid, $format, 1, $newvollist);
2378 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2380 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2382 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2386 foreach my $volid (@$newvollist) {
2387 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2390 die "storage migration failed: $err";
2393 if ($param->{delete}) {
2394 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2399 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2402 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2405 __PACKAGE__-
>register_method({
2406 name
=> 'migrate_vm',
2407 path
=> '{vmid}/migrate',
2411 description
=> "Migrate virtual machine. Creates a new migration task.",
2413 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2416 additionalProperties
=> 0,
2418 node
=> get_standard_option
('pve-node'),
2419 vmid
=> get_standard_option
('pve-vmid'),
2420 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2423 description
=> "Use online/live migration.",
2428 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2435 description
=> "the task ID.",
2440 my $rpcenv = PVE
::RPCEnvironment
::get
();
2442 my $authuser = $rpcenv->get_user();
2444 my $target = extract_param
($param, 'target');
2446 my $localnode = PVE
::INotify
::nodename
();
2447 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2449 PVE
::Cluster
::check_cfs_quorum
();
2451 PVE
::Cluster
::check_node_exists
($target);
2453 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2455 my $vmid = extract_param
($param, 'vmid');
2457 raise_param_exc
({ force
=> "Only root may use this option." })
2458 if $param->{force
} && $authuser ne 'root@pam';
2461 my $conf = PVE
::QemuServer
::load_config
($vmid);
2463 # try to detect errors early
2465 PVE
::QemuServer
::check_lock
($conf);
2467 if (PVE
::QemuServer
::check_running
($vmid)) {
2468 die "cant migrate running VM without --online\n"
2469 if !$param->{online
};
2472 my $storecfg = PVE
::Storage
::config
();
2473 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2475 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2480 my $service = "pvevm:$vmid";
2482 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2484 print "Executing HA migrate for VM $vmid to node $target\n";
2486 PVE
::Tools
::run_command
($cmd);
2491 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2498 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2501 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2506 __PACKAGE__-
>register_method({
2508 path
=> '{vmid}/monitor',
2512 description
=> "Execute Qemu monitor commands.",
2514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2517 additionalProperties
=> 0,
2519 node
=> get_standard_option
('pve-node'),
2520 vmid
=> get_standard_option
('pve-vmid'),
2523 description
=> "The monitor command.",
2527 returns
=> { type
=> 'string'},
2531 my $vmid = $param->{vmid
};
2533 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2537 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2539 $res = "ERROR: $@" if $@;
2544 __PACKAGE__-
>register_method({
2545 name
=> 'resize_vm',
2546 path
=> '{vmid}/resize',
2550 description
=> "Extend volume size.",
2552 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2555 additionalProperties
=> 0,
2557 node
=> get_standard_option
('pve-node'),
2558 vmid
=> get_standard_option
('pve-vmid'),
2559 skiplock
=> get_standard_option
('skiplock'),
2562 description
=> "The disk you want to resize.",
2563 enum
=> [PVE
::QemuServer
::disknames
()],
2567 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2568 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.",
2572 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2578 returns
=> { type
=> 'null'},
2582 my $rpcenv = PVE
::RPCEnvironment
::get
();
2584 my $authuser = $rpcenv->get_user();
2586 my $node = extract_param
($param, 'node');
2588 my $vmid = extract_param
($param, 'vmid');
2590 my $digest = extract_param
($param, 'digest');
2592 my $disk = extract_param
($param, 'disk');
2594 my $sizestr = extract_param
($param, 'size');
2596 my $skiplock = extract_param
($param, 'skiplock');
2597 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2598 if $skiplock && $authuser ne 'root@pam';
2600 my $storecfg = PVE
::Storage
::config
();
2602 my $updatefn = sub {
2604 my $conf = PVE
::QemuServer
::load_config
($vmid);
2606 die "checksum missmatch (file change by other user?)\n"
2607 if $digest && $digest ne $conf->{digest
};
2608 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2610 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2612 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2614 my $volid = $drive->{file
};
2616 die "disk '$disk' has no associated volume\n" if !$volid;
2618 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2620 die "you can't online resize a virtio windows bootdisk\n"
2621 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2623 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2625 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2627 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2629 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2630 my ($ext, $newsize, $unit) = ($1, $2, $4);
2633 $newsize = $newsize * 1024;
2634 } elsif ($unit eq 'M') {
2635 $newsize = $newsize * 1024 * 1024;
2636 } elsif ($unit eq 'G') {
2637 $newsize = $newsize * 1024 * 1024 * 1024;
2638 } elsif ($unit eq 'T') {
2639 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2642 $newsize += $size if $ext;
2643 $newsize = int($newsize);
2645 die "unable to skrink disk size\n" if $newsize < $size;
2647 return if $size == $newsize;
2649 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2651 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2653 $drive->{size
} = $newsize;
2654 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2656 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2659 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2663 __PACKAGE__-
>register_method({
2664 name
=> 'snapshot_list',
2665 path
=> '{vmid}/snapshot',
2667 description
=> "List all snapshots.",
2669 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2672 protected
=> 1, # qemu pid files are only readable by root
2674 additionalProperties
=> 0,
2676 vmid
=> get_standard_option
('pve-vmid'),
2677 node
=> get_standard_option
('pve-node'),
2686 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2691 my $vmid = $param->{vmid
};
2693 my $conf = PVE
::QemuServer
::load_config
($vmid);
2694 my $snaphash = $conf->{snapshots
} || {};
2698 foreach my $name (keys %$snaphash) {
2699 my $d = $snaphash->{$name};
2702 snaptime
=> $d->{snaptime
} || 0,
2703 vmstate
=> $d->{vmstate
} ?
1 : 0,
2704 description
=> $d->{description
} || '',
2706 $item->{parent
} = $d->{parent
} if $d->{parent
};
2707 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2711 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2712 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2713 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2715 push @$res, $current;
2720 __PACKAGE__-
>register_method({
2722 path
=> '{vmid}/snapshot',
2726 description
=> "Snapshot a VM.",
2728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2731 additionalProperties
=> 0,
2733 node
=> get_standard_option
('pve-node'),
2734 vmid
=> get_standard_option
('pve-vmid'),
2735 snapname
=> get_standard_option
('pve-snapshot-name'),
2739 description
=> "Save the vmstate",
2744 description
=> "Freeze the filesystem",
2749 description
=> "A textual description or comment.",
2755 description
=> "the task ID.",
2760 my $rpcenv = PVE
::RPCEnvironment
::get
();
2762 my $authuser = $rpcenv->get_user();
2764 my $node = extract_param
($param, 'node');
2766 my $vmid = extract_param
($param, 'vmid');
2768 my $snapname = extract_param
($param, 'snapname');
2770 die "unable to use snapshot name 'current' (reserved name)\n"
2771 if $snapname eq 'current';
2774 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2775 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2776 $param->{freezefs
}, $param->{description
});
2779 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2782 __PACKAGE__-
>register_method({
2783 name
=> 'snapshot_cmd_idx',
2784 path
=> '{vmid}/snapshot/{snapname}',
2791 additionalProperties
=> 0,
2793 vmid
=> get_standard_option
('pve-vmid'),
2794 node
=> get_standard_option
('pve-node'),
2795 snapname
=> get_standard_option
('pve-snapshot-name'),
2804 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2811 push @$res, { cmd
=> 'rollback' };
2812 push @$res, { cmd
=> 'config' };
2817 __PACKAGE__-
>register_method({
2818 name
=> 'update_snapshot_config',
2819 path
=> '{vmid}/snapshot/{snapname}/config',
2823 description
=> "Update snapshot metadata.",
2825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2828 additionalProperties
=> 0,
2830 node
=> get_standard_option
('pve-node'),
2831 vmid
=> get_standard_option
('pve-vmid'),
2832 snapname
=> get_standard_option
('pve-snapshot-name'),
2836 description
=> "A textual description or comment.",
2840 returns
=> { type
=> 'null' },
2844 my $rpcenv = PVE
::RPCEnvironment
::get
();
2846 my $authuser = $rpcenv->get_user();
2848 my $vmid = extract_param
($param, 'vmid');
2850 my $snapname = extract_param
($param, 'snapname');
2852 return undef if !defined($param->{description
});
2854 my $updatefn = sub {
2856 my $conf = PVE
::QemuServer
::load_config
($vmid);
2858 PVE
::QemuServer
::check_lock
($conf);
2860 my $snap = $conf->{snapshots
}->{$snapname};
2862 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2864 $snap->{description
} = $param->{description
} if defined($param->{description
});
2866 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2869 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2874 __PACKAGE__-
>register_method({
2875 name
=> 'get_snapshot_config',
2876 path
=> '{vmid}/snapshot/{snapname}/config',
2879 description
=> "Get snapshot configuration",
2881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2884 additionalProperties
=> 0,
2886 node
=> get_standard_option
('pve-node'),
2887 vmid
=> get_standard_option
('pve-vmid'),
2888 snapname
=> get_standard_option
('pve-snapshot-name'),
2891 returns
=> { type
=> "object" },
2895 my $rpcenv = PVE
::RPCEnvironment
::get
();
2897 my $authuser = $rpcenv->get_user();
2899 my $vmid = extract_param
($param, 'vmid');
2901 my $snapname = extract_param
($param, 'snapname');
2903 my $conf = PVE
::QemuServer
::load_config
($vmid);
2905 my $snap = $conf->{snapshots
}->{$snapname};
2907 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2912 __PACKAGE__-
>register_method({
2914 path
=> '{vmid}/snapshot/{snapname}/rollback',
2918 description
=> "Rollback VM state to specified snapshot.",
2920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2923 additionalProperties
=> 0,
2925 node
=> get_standard_option
('pve-node'),
2926 vmid
=> get_standard_option
('pve-vmid'),
2927 snapname
=> get_standard_option
('pve-snapshot-name'),
2932 description
=> "the task ID.",
2937 my $rpcenv = PVE
::RPCEnvironment
::get
();
2939 my $authuser = $rpcenv->get_user();
2941 my $node = extract_param
($param, 'node');
2943 my $vmid = extract_param
($param, 'vmid');
2945 my $snapname = extract_param
($param, 'snapname');
2948 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2949 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2952 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2955 __PACKAGE__-
>register_method({
2956 name
=> 'delsnapshot',
2957 path
=> '{vmid}/snapshot/{snapname}',
2961 description
=> "Delete a VM snapshot.",
2963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2966 additionalProperties
=> 0,
2968 node
=> get_standard_option
('pve-node'),
2969 vmid
=> get_standard_option
('pve-vmid'),
2970 snapname
=> get_standard_option
('pve-snapshot-name'),
2974 description
=> "For removal from config file, even if removing disk snapshots fails.",
2980 description
=> "the task ID.",
2985 my $rpcenv = PVE
::RPCEnvironment
::get
();
2987 my $authuser = $rpcenv->get_user();
2989 my $node = extract_param
($param, 'node');
2991 my $vmid = extract_param
($param, 'vmid');
2993 my $snapname = extract_param
($param, 'snapname');
2996 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2997 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3000 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3003 __PACKAGE__-
>register_method({
3005 path
=> '{vmid}/template',
3009 description
=> "Create a Template.",
3011 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3012 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3015 additionalProperties
=> 0,
3017 node
=> get_standard_option
('pve-node'),
3018 vmid
=> get_standard_option
('pve-vmid'),
3022 description
=> "If you want to convert only 1 disk to base image.",
3023 enum
=> [PVE
::QemuServer
::disknames
()],
3028 returns
=> { type
=> 'null'},
3032 my $rpcenv = PVE
::RPCEnvironment
::get
();
3034 my $authuser = $rpcenv->get_user();
3036 my $node = extract_param
($param, 'node');
3038 my $vmid = extract_param
($param, 'vmid');
3040 my $disk = extract_param
($param, 'disk');
3042 my $updatefn = sub {
3044 my $conf = PVE
::QemuServer
::load_config
($vmid);
3046 PVE
::QemuServer
::check_lock
($conf);
3048 die "unable to create template, because VM contains snapshots\n"
3049 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3051 die "you can't convert a template to a template\n"
3052 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3054 die "you can't convert a VM to template if VM is running\n"
3055 if PVE
::QemuServer
::check_running
($vmid);
3058 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3061 $conf->{template
} = 1;
3062 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3064 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3067 PVE
::QemuServer
::lock_config
($vmid, $updatefn);