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'),
1344 additionalProperties
=> 1,
1346 type
=> { type
=> 'string' },
1347 password
=> { type
=> 'string' },
1348 proxy
=> { type
=> 'string' },
1349 host
=> { type
=> 'string' },
1350 'tls-port' => { type
=> 'integer' },
1356 my $rpcenv = PVE
::RPCEnvironment
::get
();
1358 my $authuser = $rpcenv->get_user();
1360 my $vmid = $param->{vmid
};
1361 my $node = $param->{node
};
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 ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1374 # Note: this only works if VM is on local node
1375 my $port = PVE
::QemuServer
::spice_port
($vmid);
1376 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1377 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1380 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1384 my $filename = "/etc/pve/local/pve-ssl.pem";
1385 my $bio = Net
::SSLeay
::BIO_new_file
($filename, 'r');
1386 my $x509 = Net
::SSLeay
::PEM_read_bio_X509
($bio);
1387 Net
::SSLeay
::BIO_free
($bio);
1388 my $nameobj = Net
::SSLeay
::X509_get_subject_name
($x509);
1389 my $subject = Net
::SSLeay
::X509_NAME_oneline
($nameobj);
1390 Net
::SSLeay
::X509_free
($x509);
1392 # remote-viewer wants comma as seperator (not '/')
1394 $subject =~ s!/(\w+=)!,$1!g;
1396 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1397 $cacert =~ s/\n/\\n/g;
1401 title
=> "VM $vmid",
1402 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1403 proxy
=> "http://$host:3128",
1404 'tls-port' => $port,
1405 'host-subject' => $subject,
1407 password
=> $ticket,
1408 'delete-this-file' => 1,
1412 __PACKAGE__-
>register_method({
1414 path
=> '{vmid}/status',
1417 description
=> "Directory index",
1422 additionalProperties
=> 0,
1424 node
=> get_standard_option
('pve-node'),
1425 vmid
=> get_standard_option
('pve-vmid'),
1433 subdir
=> { type
=> 'string' },
1436 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1442 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1445 { subdir
=> 'current' },
1446 { subdir
=> 'start' },
1447 { subdir
=> 'stop' },
1453 my $vm_is_ha_managed = sub {
1456 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1457 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1463 __PACKAGE__-
>register_method({
1464 name
=> 'vm_status',
1465 path
=> '{vmid}/status/current',
1468 protected
=> 1, # qemu pid files are only readable by root
1469 description
=> "Get virtual machine status.",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1480 returns
=> { type
=> 'object' },
1485 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1487 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1488 my $status = $vmstatus->{$param->{vmid
}};
1490 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1492 if ($conf->{vga
} && ($conf->{vga
} eq 'qxl')) {
1493 $status->{spice
} = 1;
1499 __PACKAGE__-
>register_method({
1501 path
=> '{vmid}/status/start',
1505 description
=> "Start virtual machine.",
1507 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid'),
1514 skiplock
=> get_standard_option
('skiplock'),
1515 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1516 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1517 machine
=> get_standard_option
('pve-qm-machine'),
1526 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1530 my $node = extract_param
($param, 'node');
1532 my $vmid = extract_param
($param, 'vmid');
1534 my $machine = extract_param
($param, 'machine');
1536 my $stateuri = extract_param
($param, 'stateuri');
1537 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1538 if $stateuri && $authuser ne 'root@pam';
1540 my $skiplock = extract_param
($param, 'skiplock');
1541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1542 if $skiplock && $authuser ne 'root@pam';
1544 my $migratedfrom = extract_param
($param, 'migratedfrom');
1545 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1546 if $migratedfrom && $authuser ne 'root@pam';
1548 my $storecfg = PVE
::Storage
::config
();
1550 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1551 $rpcenv->{type
} ne 'ha') {
1556 my $service = "pvevm:$vmid";
1558 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1560 print "Executing HA start for VM $vmid\n";
1562 PVE
::Tools
::run_command
($cmd);
1567 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1574 syslog
('info', "start VM $vmid: $upid\n");
1576 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1581 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1585 __PACKAGE__-
>register_method({
1587 path
=> '{vmid}/status/stop',
1591 description
=> "Stop virtual machine.",
1593 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1596 additionalProperties
=> 0,
1598 node
=> get_standard_option
('pve-node'),
1599 vmid
=> get_standard_option
('pve-vmid'),
1600 skiplock
=> get_standard_option
('skiplock'),
1601 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1603 description
=> "Wait maximal timeout seconds.",
1609 description
=> "Do not decativate storage volumes.",
1622 my $rpcenv = PVE
::RPCEnvironment
::get
();
1624 my $authuser = $rpcenv->get_user();
1626 my $node = extract_param
($param, 'node');
1628 my $vmid = extract_param
($param, 'vmid');
1630 my $skiplock = extract_param
($param, 'skiplock');
1631 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1632 if $skiplock && $authuser ne 'root@pam';
1634 my $keepActive = extract_param
($param, 'keepActive');
1635 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1636 if $keepActive && $authuser ne 'root@pam';
1638 my $migratedfrom = extract_param
($param, 'migratedfrom');
1639 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1640 if $migratedfrom && $authuser ne 'root@pam';
1643 my $storecfg = PVE
::Storage
::config
();
1645 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1650 my $service = "pvevm:$vmid";
1652 my $cmd = ['clusvcadm', '-d', $service];
1654 print "Executing HA stop for VM $vmid\n";
1656 PVE
::Tools
::run_command
($cmd);
1661 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1667 syslog
('info', "stop VM $vmid: $upid\n");
1669 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1670 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1675 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1679 __PACKAGE__-
>register_method({
1681 path
=> '{vmid}/status/reset',
1685 description
=> "Reset virtual machine.",
1687 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1690 additionalProperties
=> 0,
1692 node
=> get_standard_option
('pve-node'),
1693 vmid
=> get_standard_option
('pve-vmid'),
1694 skiplock
=> get_standard_option
('skiplock'),
1703 my $rpcenv = PVE
::RPCEnvironment
::get
();
1705 my $authuser = $rpcenv->get_user();
1707 my $node = extract_param
($param, 'node');
1709 my $vmid = extract_param
($param, 'vmid');
1711 my $skiplock = extract_param
($param, 'skiplock');
1712 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1713 if $skiplock && $authuser ne 'root@pam';
1715 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1720 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1725 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1728 __PACKAGE__-
>register_method({
1729 name
=> 'vm_shutdown',
1730 path
=> '{vmid}/status/shutdown',
1734 description
=> "Shutdown virtual machine.",
1736 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1739 additionalProperties
=> 0,
1741 node
=> get_standard_option
('pve-node'),
1742 vmid
=> get_standard_option
('pve-vmid'),
1743 skiplock
=> get_standard_option
('skiplock'),
1745 description
=> "Wait maximal timeout seconds.",
1751 description
=> "Make sure the VM stops.",
1757 description
=> "Do not decativate storage volumes.",
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $node = extract_param
($param, 'node');
1776 my $vmid = extract_param
($param, 'vmid');
1778 my $skiplock = extract_param
($param, 'skiplock');
1779 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1780 if $skiplock && $authuser ne 'root@pam';
1782 my $keepActive = extract_param
($param, 'keepActive');
1783 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1784 if $keepActive && $authuser ne 'root@pam';
1786 my $storecfg = PVE
::Storage
::config
();
1791 syslog
('info', "shutdown VM $vmid: $upid\n");
1793 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1794 1, $param->{forceStop
}, $keepActive);
1799 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1802 __PACKAGE__-
>register_method({
1803 name
=> 'vm_suspend',
1804 path
=> '{vmid}/status/suspend',
1808 description
=> "Suspend virtual machine.",
1810 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1813 additionalProperties
=> 0,
1815 node
=> get_standard_option
('pve-node'),
1816 vmid
=> get_standard_option
('pve-vmid'),
1817 skiplock
=> get_standard_option
('skiplock'),
1826 my $rpcenv = PVE
::RPCEnvironment
::get
();
1828 my $authuser = $rpcenv->get_user();
1830 my $node = extract_param
($param, 'node');
1832 my $vmid = extract_param
($param, 'vmid');
1834 my $skiplock = extract_param
($param, 'skiplock');
1835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1836 if $skiplock && $authuser ne 'root@pam';
1838 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1843 syslog
('info', "suspend VM $vmid: $upid\n");
1845 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1850 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1853 __PACKAGE__-
>register_method({
1854 name
=> 'vm_resume',
1855 path
=> '{vmid}/status/resume',
1859 description
=> "Resume virtual machine.",
1861 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1864 additionalProperties
=> 0,
1866 node
=> get_standard_option
('pve-node'),
1867 vmid
=> get_standard_option
('pve-vmid'),
1868 skiplock
=> get_standard_option
('skiplock'),
1877 my $rpcenv = PVE
::RPCEnvironment
::get
();
1879 my $authuser = $rpcenv->get_user();
1881 my $node = extract_param
($param, 'node');
1883 my $vmid = extract_param
($param, 'vmid');
1885 my $skiplock = extract_param
($param, 'skiplock');
1886 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1887 if $skiplock && $authuser ne 'root@pam';
1889 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1894 syslog
('info', "resume VM $vmid: $upid\n");
1896 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1901 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1904 __PACKAGE__-
>register_method({
1905 name
=> 'vm_sendkey',
1906 path
=> '{vmid}/sendkey',
1910 description
=> "Send key event to virtual machine.",
1912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1919 skiplock
=> get_standard_option
('skiplock'),
1921 description
=> "The key (qemu monitor encoding).",
1926 returns
=> { type
=> 'null'},
1930 my $rpcenv = PVE
::RPCEnvironment
::get
();
1932 my $authuser = $rpcenv->get_user();
1934 my $node = extract_param
($param, 'node');
1936 my $vmid = extract_param
($param, 'vmid');
1938 my $skiplock = extract_param
($param, 'skiplock');
1939 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1940 if $skiplock && $authuser ne 'root@pam';
1942 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1947 __PACKAGE__-
>register_method({
1948 name
=> 'vm_feature',
1949 path
=> '{vmid}/feature',
1953 description
=> "Check if feature for virtual machine is available.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid'),
1963 description
=> "Feature to check.",
1965 enum
=> [ 'snapshot', 'clone', 'copy' ],
1967 snapname
=> get_standard_option
('pve-snapshot-name', {
1975 hasFeature
=> { type
=> 'boolean' },
1978 items
=> { type
=> 'string' },
1985 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $snapname = extract_param
($param, 'snapname');
1991 my $feature = extract_param
($param, 'feature');
1993 my $running = PVE
::QemuServer
::check_running
($vmid);
1995 my $conf = PVE
::QemuServer
::load_config
($vmid);
1998 my $snap = $conf->{snapshots
}->{$snapname};
1999 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2002 my $storecfg = PVE
::Storage
::config
();
2004 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2005 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2008 hasFeature
=> $hasFeature,
2009 nodes
=> [ keys %$nodelist ],
2013 __PACKAGE__-
>register_method({
2015 path
=> '{vmid}/clone',
2019 description
=> "Create a copy of virtual machine/template.",
2021 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2022 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2023 "'Datastore.AllocateSpace' on any used storage.",
2026 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2028 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2029 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2038 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2041 type
=> 'string', format
=> 'dns-name',
2042 description
=> "Set a name for the new VM.",
2047 description
=> "Description for the new VM.",
2051 type
=> 'string', format
=> 'pve-poolid',
2052 description
=> "Add the new VM to the specified pool.",
2054 snapname
=> get_standard_option
('pve-snapshot-name', {
2058 storage
=> get_standard_option
('pve-storage-id', {
2059 description
=> "Target storage for full clone.",
2064 description
=> "Target format for file storage.",
2068 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2073 description
=> "Create a full copy of all disk. This is always done when " .
2074 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2077 target
=> get_standard_option
('pve-node', {
2078 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2089 my $rpcenv = PVE
::RPCEnvironment
::get
();
2091 my $authuser = $rpcenv->get_user();
2093 my $node = extract_param
($param, 'node');
2095 my $vmid = extract_param
($param, 'vmid');
2097 my $newid = extract_param
($param, 'newid');
2099 my $pool = extract_param
($param, 'pool');
2101 if (defined($pool)) {
2102 $rpcenv->check_pool_exist($pool);
2105 my $snapname = extract_param
($param, 'snapname');
2107 my $storage = extract_param
($param, 'storage');
2109 my $format = extract_param
($param, 'format');
2111 my $target = extract_param
($param, 'target');
2113 my $localnode = PVE
::INotify
::nodename
();
2115 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2117 PVE
::Cluster
::check_node_exists
($target) if $target;
2119 my $storecfg = PVE
::Storage
::config
();
2122 # check if storage is enabled on local node
2123 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2125 # check if storage is available on target node
2126 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2127 # clone only works if target storage is shared
2128 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2129 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2133 PVE
::Cluster
::check_cfs_quorum
();
2135 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2137 # exclusive lock if VM is running - else shared lock is enough;
2138 my $shared_lock = $running ?
0 : 1;
2142 # do all tests after lock
2143 # we also try to do all tests before we fork the worker
2145 my $conf = PVE
::QemuServer
::load_config
($vmid);
2147 PVE
::QemuServer
::check_lock
($conf);
2149 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2151 die "unexpected state change\n" if $verify_running != $running;
2153 die "snapshot '$snapname' does not exist\n"
2154 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2156 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2158 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2160 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2162 my $conffile = PVE
::QemuServer
::config_file
($newid);
2164 die "unable to create VM $newid: config file already exists\n"
2167 my $newconf = { lock => 'clone' };
2171 foreach my $opt (keys %$oldconf) {
2172 my $value = $oldconf->{$opt};
2174 # do not copy snapshot related info
2175 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2176 $opt eq 'vmstate' || $opt eq 'snapstate';
2178 # always change MAC! address
2179 if ($opt =~ m/^net(\d+)$/) {
2180 my $net = PVE
::QemuServer
::parse_net
($value);
2181 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2182 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2183 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2184 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2185 $newconf->{$opt} = $value; # simply copy configuration
2187 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2188 die "Full clone feature is not available"
2189 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2192 $drives->{$opt} = $drive;
2193 push @$vollist, $drive->{file
};
2196 # copy everything else
2197 $newconf->{$opt} = $value;
2201 delete $newconf->{template
};
2203 if ($param->{name
}) {
2204 $newconf->{name
} = $param->{name
};
2206 if ($oldconf->{name
}) {
2207 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2209 $newconf->{name
} = "Copy-of-VM-$vmid";
2213 if ($param->{description
}) {
2214 $newconf->{description
} = $param->{description
};
2217 # create empty/temp config - this fails if VM already exists on other node
2218 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2223 my $newvollist = [];
2226 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2228 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2230 foreach my $opt (keys %$drives) {
2231 my $drive = $drives->{$opt};
2233 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2234 $newid, $storage, $format, $drive->{full
}, $newvollist);
2236 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2238 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2241 delete $newconf->{lock};
2242 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2245 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2246 die "Failed to move config to node '$target' - rename failed: $!\n"
2247 if !rename($conffile, $newconffile);
2250 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2255 sleep 1; # some storage like rbd need to wait before release volume - really?
2257 foreach my $volid (@$newvollist) {
2258 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2261 die "clone failed: $err";
2267 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2270 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2271 # Aquire exclusive lock lock for $newid
2272 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2277 __PACKAGE__-
>register_method({
2278 name
=> 'move_vm_disk',
2279 path
=> '{vmid}/move_disk',
2283 description
=> "Move volume to different storage.",
2285 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2286 "and 'Datastore.AllocateSpace' permissions on the storage.",
2289 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2290 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2294 additionalProperties
=> 0,
2296 node
=> get_standard_option
('pve-node'),
2297 vmid
=> get_standard_option
('pve-vmid'),
2300 description
=> "The disk you want to move.",
2301 enum
=> [ PVE
::QemuServer
::disknames
() ],
2303 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2306 description
=> "Target Format.",
2307 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2312 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2318 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2326 description
=> "the task ID.",
2331 my $rpcenv = PVE
::RPCEnvironment
::get
();
2333 my $authuser = $rpcenv->get_user();
2335 my $node = extract_param
($param, 'node');
2337 my $vmid = extract_param
($param, 'vmid');
2339 my $digest = extract_param
($param, 'digest');
2341 my $disk = extract_param
($param, 'disk');
2343 my $storeid = extract_param
($param, 'storage');
2345 my $format = extract_param
($param, 'format');
2347 my $storecfg = PVE
::Storage
::config
();
2349 my $updatefn = sub {
2351 my $conf = PVE
::QemuServer
::load_config
($vmid);
2353 die "checksum missmatch (file change by other user?)\n"
2354 if $digest && $digest ne $conf->{digest
};
2356 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2358 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2360 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2362 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2365 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2366 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2370 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2371 (!$format || !$oldfmt || $oldfmt eq $format);
2373 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2375 my $running = PVE
::QemuServer
::check_running
($vmid);
2377 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2381 my $newvollist = [];
2384 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2386 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2387 $vmid, $storeid, $format, 1, $newvollist);
2389 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2391 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2393 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2397 foreach my $volid (@$newvollist) {
2398 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2401 die "storage migration failed: $err";
2404 if ($param->{delete}) {
2405 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2410 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2413 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2416 __PACKAGE__-
>register_method({
2417 name
=> 'migrate_vm',
2418 path
=> '{vmid}/migrate',
2422 description
=> "Migrate virtual machine. Creates a new migration task.",
2424 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2427 additionalProperties
=> 0,
2429 node
=> get_standard_option
('pve-node'),
2430 vmid
=> get_standard_option
('pve-vmid'),
2431 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2434 description
=> "Use online/live migration.",
2439 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2446 description
=> "the task ID.",
2451 my $rpcenv = PVE
::RPCEnvironment
::get
();
2453 my $authuser = $rpcenv->get_user();
2455 my $target = extract_param
($param, 'target');
2457 my $localnode = PVE
::INotify
::nodename
();
2458 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2460 PVE
::Cluster
::check_cfs_quorum
();
2462 PVE
::Cluster
::check_node_exists
($target);
2464 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2466 my $vmid = extract_param
($param, 'vmid');
2468 raise_param_exc
({ force
=> "Only root may use this option." })
2469 if $param->{force
} && $authuser ne 'root@pam';
2472 my $conf = PVE
::QemuServer
::load_config
($vmid);
2474 # try to detect errors early
2476 PVE
::QemuServer
::check_lock
($conf);
2478 if (PVE
::QemuServer
::check_running
($vmid)) {
2479 die "cant migrate running VM without --online\n"
2480 if !$param->{online
};
2483 my $storecfg = PVE
::Storage
::config
();
2484 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2486 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2491 my $service = "pvevm:$vmid";
2493 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2495 print "Executing HA migrate for VM $vmid to node $target\n";
2497 PVE
::Tools
::run_command
($cmd);
2502 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2509 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2512 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2517 __PACKAGE__-
>register_method({
2519 path
=> '{vmid}/monitor',
2523 description
=> "Execute Qemu monitor commands.",
2525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2528 additionalProperties
=> 0,
2530 node
=> get_standard_option
('pve-node'),
2531 vmid
=> get_standard_option
('pve-vmid'),
2534 description
=> "The monitor command.",
2538 returns
=> { type
=> 'string'},
2542 my $vmid = $param->{vmid
};
2544 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2548 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2550 $res = "ERROR: $@" if $@;
2555 __PACKAGE__-
>register_method({
2556 name
=> 'resize_vm',
2557 path
=> '{vmid}/resize',
2561 description
=> "Extend volume size.",
2563 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2566 additionalProperties
=> 0,
2568 node
=> get_standard_option
('pve-node'),
2569 vmid
=> get_standard_option
('pve-vmid'),
2570 skiplock
=> get_standard_option
('skiplock'),
2573 description
=> "The disk you want to resize.",
2574 enum
=> [PVE
::QemuServer
::disknames
()],
2578 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2579 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.",
2583 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2589 returns
=> { type
=> 'null'},
2593 my $rpcenv = PVE
::RPCEnvironment
::get
();
2595 my $authuser = $rpcenv->get_user();
2597 my $node = extract_param
($param, 'node');
2599 my $vmid = extract_param
($param, 'vmid');
2601 my $digest = extract_param
($param, 'digest');
2603 my $disk = extract_param
($param, 'disk');
2605 my $sizestr = extract_param
($param, 'size');
2607 my $skiplock = extract_param
($param, 'skiplock');
2608 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2609 if $skiplock && $authuser ne 'root@pam';
2611 my $storecfg = PVE
::Storage
::config
();
2613 my $updatefn = sub {
2615 my $conf = PVE
::QemuServer
::load_config
($vmid);
2617 die "checksum missmatch (file change by other user?)\n"
2618 if $digest && $digest ne $conf->{digest
};
2619 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2621 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2623 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2625 my $volid = $drive->{file
};
2627 die "disk '$disk' has no associated volume\n" if !$volid;
2629 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2631 die "you can't online resize a virtio windows bootdisk\n"
2632 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2634 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2636 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2638 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2640 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2641 my ($ext, $newsize, $unit) = ($1, $2, $4);
2644 $newsize = $newsize * 1024;
2645 } elsif ($unit eq 'M') {
2646 $newsize = $newsize * 1024 * 1024;
2647 } elsif ($unit eq 'G') {
2648 $newsize = $newsize * 1024 * 1024 * 1024;
2649 } elsif ($unit eq 'T') {
2650 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2653 $newsize += $size if $ext;
2654 $newsize = int($newsize);
2656 die "unable to skrink disk size\n" if $newsize < $size;
2658 return if $size == $newsize;
2660 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2662 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2664 $drive->{size
} = $newsize;
2665 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2667 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2670 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2674 __PACKAGE__-
>register_method({
2675 name
=> 'snapshot_list',
2676 path
=> '{vmid}/snapshot',
2678 description
=> "List all snapshots.",
2680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2683 protected
=> 1, # qemu pid files are only readable by root
2685 additionalProperties
=> 0,
2687 vmid
=> get_standard_option
('pve-vmid'),
2688 node
=> get_standard_option
('pve-node'),
2697 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2702 my $vmid = $param->{vmid
};
2704 my $conf = PVE
::QemuServer
::load_config
($vmid);
2705 my $snaphash = $conf->{snapshots
} || {};
2709 foreach my $name (keys %$snaphash) {
2710 my $d = $snaphash->{$name};
2713 snaptime
=> $d->{snaptime
} || 0,
2714 vmstate
=> $d->{vmstate
} ?
1 : 0,
2715 description
=> $d->{description
} || '',
2717 $item->{parent
} = $d->{parent
} if $d->{parent
};
2718 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2722 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2723 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2724 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2726 push @$res, $current;
2731 __PACKAGE__-
>register_method({
2733 path
=> '{vmid}/snapshot',
2737 description
=> "Snapshot a VM.",
2739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2742 additionalProperties
=> 0,
2744 node
=> get_standard_option
('pve-node'),
2745 vmid
=> get_standard_option
('pve-vmid'),
2746 snapname
=> get_standard_option
('pve-snapshot-name'),
2750 description
=> "Save the vmstate",
2755 description
=> "Freeze the filesystem",
2760 description
=> "A textual description or comment.",
2766 description
=> "the task ID.",
2771 my $rpcenv = PVE
::RPCEnvironment
::get
();
2773 my $authuser = $rpcenv->get_user();
2775 my $node = extract_param
($param, 'node');
2777 my $vmid = extract_param
($param, 'vmid');
2779 my $snapname = extract_param
($param, 'snapname');
2781 die "unable to use snapshot name 'current' (reserved name)\n"
2782 if $snapname eq 'current';
2785 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2786 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2787 $param->{freezefs
}, $param->{description
});
2790 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2793 __PACKAGE__-
>register_method({
2794 name
=> 'snapshot_cmd_idx',
2795 path
=> '{vmid}/snapshot/{snapname}',
2802 additionalProperties
=> 0,
2804 vmid
=> get_standard_option
('pve-vmid'),
2805 node
=> get_standard_option
('pve-node'),
2806 snapname
=> get_standard_option
('pve-snapshot-name'),
2815 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2822 push @$res, { cmd
=> 'rollback' };
2823 push @$res, { cmd
=> 'config' };
2828 __PACKAGE__-
>register_method({
2829 name
=> 'update_snapshot_config',
2830 path
=> '{vmid}/snapshot/{snapname}/config',
2834 description
=> "Update snapshot metadata.",
2836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2839 additionalProperties
=> 0,
2841 node
=> get_standard_option
('pve-node'),
2842 vmid
=> get_standard_option
('pve-vmid'),
2843 snapname
=> get_standard_option
('pve-snapshot-name'),
2847 description
=> "A textual description or comment.",
2851 returns
=> { type
=> 'null' },
2855 my $rpcenv = PVE
::RPCEnvironment
::get
();
2857 my $authuser = $rpcenv->get_user();
2859 my $vmid = extract_param
($param, 'vmid');
2861 my $snapname = extract_param
($param, 'snapname');
2863 return undef if !defined($param->{description
});
2865 my $updatefn = sub {
2867 my $conf = PVE
::QemuServer
::load_config
($vmid);
2869 PVE
::QemuServer
::check_lock
($conf);
2871 my $snap = $conf->{snapshots
}->{$snapname};
2873 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2875 $snap->{description
} = $param->{description
} if defined($param->{description
});
2877 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2880 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2885 __PACKAGE__-
>register_method({
2886 name
=> 'get_snapshot_config',
2887 path
=> '{vmid}/snapshot/{snapname}/config',
2890 description
=> "Get snapshot configuration",
2892 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2895 additionalProperties
=> 0,
2897 node
=> get_standard_option
('pve-node'),
2898 vmid
=> get_standard_option
('pve-vmid'),
2899 snapname
=> get_standard_option
('pve-snapshot-name'),
2902 returns
=> { type
=> "object" },
2906 my $rpcenv = PVE
::RPCEnvironment
::get
();
2908 my $authuser = $rpcenv->get_user();
2910 my $vmid = extract_param
($param, 'vmid');
2912 my $snapname = extract_param
($param, 'snapname');
2914 my $conf = PVE
::QemuServer
::load_config
($vmid);
2916 my $snap = $conf->{snapshots
}->{$snapname};
2918 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2923 __PACKAGE__-
>register_method({
2925 path
=> '{vmid}/snapshot/{snapname}/rollback',
2929 description
=> "Rollback VM state to specified snapshot.",
2931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2934 additionalProperties
=> 0,
2936 node
=> get_standard_option
('pve-node'),
2937 vmid
=> get_standard_option
('pve-vmid'),
2938 snapname
=> get_standard_option
('pve-snapshot-name'),
2943 description
=> "the task ID.",
2948 my $rpcenv = PVE
::RPCEnvironment
::get
();
2950 my $authuser = $rpcenv->get_user();
2952 my $node = extract_param
($param, 'node');
2954 my $vmid = extract_param
($param, 'vmid');
2956 my $snapname = extract_param
($param, 'snapname');
2959 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2960 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2963 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2966 __PACKAGE__-
>register_method({
2967 name
=> 'delsnapshot',
2968 path
=> '{vmid}/snapshot/{snapname}',
2972 description
=> "Delete a VM snapshot.",
2974 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2977 additionalProperties
=> 0,
2979 node
=> get_standard_option
('pve-node'),
2980 vmid
=> get_standard_option
('pve-vmid'),
2981 snapname
=> get_standard_option
('pve-snapshot-name'),
2985 description
=> "For removal from config file, even if removing disk snapshots fails.",
2991 description
=> "the task ID.",
2996 my $rpcenv = PVE
::RPCEnvironment
::get
();
2998 my $authuser = $rpcenv->get_user();
3000 my $node = extract_param
($param, 'node');
3002 my $vmid = extract_param
($param, 'vmid');
3004 my $snapname = extract_param
($param, 'snapname');
3007 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3008 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3011 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3014 __PACKAGE__-
>register_method({
3016 path
=> '{vmid}/template',
3020 description
=> "Create a Template.",
3022 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3023 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3026 additionalProperties
=> 0,
3028 node
=> get_standard_option
('pve-node'),
3029 vmid
=> get_standard_option
('pve-vmid'),
3033 description
=> "If you want to convert only 1 disk to base image.",
3034 enum
=> [PVE
::QemuServer
::disknames
()],
3039 returns
=> { type
=> 'null'},
3043 my $rpcenv = PVE
::RPCEnvironment
::get
();
3045 my $authuser = $rpcenv->get_user();
3047 my $node = extract_param
($param, 'node');
3049 my $vmid = extract_param
($param, 'vmid');
3051 my $disk = extract_param
($param, 'disk');
3053 my $updatefn = sub {
3055 my $conf = PVE
::QemuServer
::load_config
($vmid);
3057 PVE
::QemuServer
::check_lock
($conf);
3059 die "unable to create template, because VM contains snapshots\n"
3060 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3062 die "you can't convert a template to a template\n"
3063 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3065 die "you can't convert a VM to template if VM is running\n"
3066 if PVE
::QemuServer
::check_running
($vmid);
3069 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3072 $conf->{template
} = 1;
3073 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3075 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3078 PVE
::QemuServer
::lock_config
($vmid, $updatefn);