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
;
21 use PVE
::API2
::Firewall
::VM
;
23 use Data
::Dumper
; # fixme: remove
25 use base
qw(PVE::RESTHandler);
27 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.";
29 my $resolve_cdrom_alias = sub {
32 if (my $value = $param->{cdrom
}) {
33 $value .= ",media=cdrom" if $value !~ m/media=/;
34 $param->{ide2
} = $value;
35 delete $param->{cdrom
};
40 my $check_storage_access = sub {
41 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
43 PVE
::QemuServer
::foreach_drive
($settings, sub {
44 my ($ds, $drive) = @_;
46 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
48 my $volid = $drive->{file
};
50 if (!$volid || $volid eq 'none') {
52 } elsif ($isCDROM && ($volid eq 'cdrom')) {
53 $rpcenv->check($authuser, "/", ['Sys.Console']);
54 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
55 my ($storeid, $size) = ($2 || $default_storage, $3);
56 die "no storage ID specified (and no default storage)\n" if !$storeid;
57 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
59 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
64 my $check_storage_access_clone = sub {
65 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
69 PVE
::QemuServer
::foreach_drive
($conf, sub {
70 my ($ds, $drive) = @_;
72 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
74 my $volid = $drive->{file
};
76 return if !$volid || $volid eq 'none';
79 if ($volid eq 'cdrom') {
80 $rpcenv->check($authuser, "/", ['Sys.Console']);
82 # we simply allow access
83 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
85 $sharedvm = 0 if !$scfg->{shared
};
89 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
90 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
91 $sharedvm = 0 if !$scfg->{shared
};
93 $sid = $storage if $storage;
94 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
101 # Note: $pool is only needed when creating a VM, because pool permissions
102 # are automatically inherited if VM already exists inside a pool.
103 my $create_disks = sub {
104 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
109 PVE
::QemuServer
::foreach_drive
($settings, sub {
110 my ($ds, $disk) = @_;
112 my $volid = $disk->{file
};
114 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
115 delete $disk->{size
};
116 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
117 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
118 my ($storeid, $size) = ($2 || $default_storage, $3);
119 die "no storage ID specified (and no default storage)\n" if !$storeid;
120 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
121 my $fmt = $disk->{format
} || $defformat;
122 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
123 $fmt, undef, $size*1024*1024);
124 $disk->{file
} = $volid;
125 $disk->{size
} = $size*1024*1024*1024;
126 push @$vollist, $volid;
127 delete $disk->{format
}; # no longer needed
128 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
131 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
133 my $volid_is_new = 1;
136 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
137 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
142 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
144 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
146 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
148 die "volume $volid does not exists\n" if !$size;
150 $disk->{size
} = $size;
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
157 # free allocated images on error
159 syslog
('err', "VM $vmid creating disks failed");
160 foreach my $volid (@$vollist) {
161 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
167 # modify vm config if everything went well
168 foreach my $ds (keys %$res) {
169 $conf->{$ds} = $res->{$ds};
175 my $check_vm_modify_config_perm = sub {
176 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
178 return 1 if $authuser eq 'root@pam';
180 foreach my $opt (@$key_list) {
181 # disk checks need to be done somewhere else
182 next if PVE
::QemuServer
::valid_drivename
($opt);
184 if ($opt eq 'sockets' || $opt eq 'cores' ||
185 $opt eq 'cpu' || $opt eq 'smp' ||
186 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
188 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
190 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
192 } elsif ($opt eq 'args' || $opt eq 'lock') {
193 die "only root can set '$opt' config\n";
194 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
195 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
197 } elsif ($opt =~ m/^net\d+$/) {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
207 __PACKAGE__-
>register_method({
211 description
=> "Virtual machine index (per node).",
213 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
217 protected
=> 1, # qemu pid files are only readable by root
219 additionalProperties
=> 0,
221 node
=> get_standard_option
('pve-node'),
230 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
235 my $rpcenv = PVE
::RPCEnvironment
::get
();
236 my $authuser = $rpcenv->get_user();
238 my $vmstatus = PVE
::QemuServer
::vmstatus
();
241 foreach my $vmid (keys %$vmstatus) {
242 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
244 my $data = $vmstatus->{$vmid};
245 $data->{vmid
} = $vmid;
254 __PACKAGE__-
>register_method({
258 description
=> "Create or restore a virtual machine.",
260 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
261 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
262 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
263 user
=> 'all', # check inside
268 additionalProperties
=> 0,
269 properties
=> PVE
::QemuServer
::json_config_properties
(
271 node
=> get_standard_option
('pve-node'),
272 vmid
=> get_standard_option
('pve-vmid'),
274 description
=> "The backup file.",
279 storage
=> get_standard_option
('pve-storage-id', {
280 description
=> "Default storage.",
286 description
=> "Allow to overwrite existing VM.",
287 requires
=> 'archive',
292 description
=> "Assign a unique random ethernet address.",
293 requires
=> 'archive',
297 type
=> 'string', format
=> 'pve-poolid',
298 description
=> "Add the VM to the specified pool.",
308 my $rpcenv = PVE
::RPCEnvironment
::get
();
310 my $authuser = $rpcenv->get_user();
312 my $node = extract_param
($param, 'node');
314 my $vmid = extract_param
($param, 'vmid');
316 my $archive = extract_param
($param, 'archive');
318 my $storage = extract_param
($param, 'storage');
320 my $force = extract_param
($param, 'force');
322 my $unique = extract_param
($param, 'unique');
324 my $pool = extract_param
($param, 'pool');
326 my $filename = PVE
::QemuServer
::config_file
($vmid);
328 my $storecfg = PVE
::Storage
::config
();
330 PVE
::Cluster
::check_cfs_quorum
();
332 if (defined($pool)) {
333 $rpcenv->check_pool_exist($pool);
336 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
337 if defined($storage);
339 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
341 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
343 } elsif ($archive && $force && (-f
$filename) &&
344 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
345 # OK: user has VM.Backup permissions, and want to restore an existing VM
351 &$resolve_cdrom_alias($param);
353 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
355 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
357 foreach my $opt (keys %$param) {
358 if (PVE
::QemuServer
::valid_drivename
($opt)) {
359 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
360 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
362 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
363 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
367 PVE
::QemuServer
::add_random_macs
($param);
369 my $keystr = join(' ', keys %$param);
370 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
372 if ($archive eq '-') {
373 die "pipe requires cli environment\n"
374 if $rpcenv->{type
} ne 'cli';
376 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
381 my $restorefn = sub {
383 # fixme: this test does not work if VM exists on other node!
385 die "unable to restore vm $vmid: config file already exists\n"
388 die "unable to restore vm $vmid: vm is running\n"
389 if PVE
::QemuServer
::check_running
($vmid);
393 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
396 unique
=> $unique });
398 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
401 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
407 die "unable to create vm $vmid: config file already exists\n"
418 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
420 # try to be smart about bootdisk
421 my @disks = PVE
::QemuServer
::disknames
();
423 foreach my $ds (reverse @disks) {
424 next if !$conf->{$ds};
425 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
426 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
430 if (!$conf->{bootdisk
} && $firstdisk) {
431 $conf->{bootdisk
} = $firstdisk;
434 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
440 foreach my $volid (@$vollist) {
441 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
444 die "create failed - $err";
447 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
450 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
453 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
456 __PACKAGE__-
>register_method({
461 description
=> "Directory index",
466 additionalProperties
=> 0,
468 node
=> get_standard_option
('pve-node'),
469 vmid
=> get_standard_option
('pve-vmid'),
477 subdir
=> { type
=> 'string' },
480 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
486 { subdir
=> 'config' },
487 { subdir
=> 'status' },
488 { subdir
=> 'unlink' },
489 { subdir
=> 'vncproxy' },
490 { subdir
=> 'migrate' },
491 { subdir
=> 'resize' },
492 { subdir
=> 'move' },
494 { subdir
=> 'rrddata' },
495 { subdir
=> 'monitor' },
496 { subdir
=> 'snapshot' },
497 { subdir
=> 'spiceproxy' },
498 { subdir
=> 'sendkey' },
499 { subdir
=> 'firewall' },
505 __PACKAGE__-
>register_method ({
506 subclass
=> "PVE::API2::Firewall::VM",
507 path
=> '{vmid}/firewall',
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrd',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics (returns PNG)",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The list of datasources you want to display.",
531 type
=> 'string', format
=> 'pve-configid-list',
534 description
=> "The RRD consolidation function",
536 enum
=> [ 'AVERAGE', 'MAX' ],
544 filename
=> { type
=> 'string' },
550 return PVE
::Cluster
::create_rrd_graph
(
551 "pve2-vm/$param->{vmid}", $param->{timeframe
},
552 $param->{ds
}, $param->{cf
});
556 __PACKAGE__-
>register_method({
558 path
=> '{vmid}/rrddata',
560 protected
=> 1, # fixme: can we avoid that?
562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 description
=> "Read VM RRD statistics",
566 additionalProperties
=> 0,
568 node
=> get_standard_option
('pve-node'),
569 vmid
=> get_standard_option
('pve-vmid'),
571 description
=> "Specify the time frame you are interested in.",
573 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
576 description
=> "The RRD consolidation function",
578 enum
=> [ 'AVERAGE', 'MAX' ],
593 return PVE
::Cluster
::create_rrd_data
(
594 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
598 __PACKAGE__-
>register_method({
600 path
=> '{vmid}/config',
603 description
=> "Get virtual machine configuration.",
605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
611 vmid
=> get_standard_option
('pve-vmid'),
619 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
626 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
628 delete $conf->{snapshots
};
633 my $vm_is_volid_owner = sub {
634 my ($storecfg, $vmid, $volid) =@_;
636 if ($volid !~ m
|^/|) {
638 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
639 if ($owner && ($owner == $vmid)) {
647 my $test_deallocate_drive = sub {
648 my ($storecfg, $vmid, $key, $drive, $force) = @_;
650 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
651 my $volid = $drive->{file
};
652 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
653 if ($force || $key =~ m/^unused/) {
654 my $sid = PVE
::Storage
::parse_volume_id
($volid);
663 my $delete_drive = sub {
664 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
666 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
667 my $volid = $drive->{file
};
669 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
670 if ($force || $key =~ m/^unused/) {
672 # check if the disk is really unused
673 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
674 my $path = PVE
::Storage
::path
($storecfg, $volid);
676 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
677 if $used_paths->{$path};
679 PVE
::Storage
::vdisk_free
($storecfg, $volid);
683 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
688 delete $conf->{$key};
691 my $vmconfig_delete_option = sub {
692 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
694 return if !defined($conf->{$opt});
696 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
699 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
701 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
702 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
703 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
707 my $unplugwarning = "";
708 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
709 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
710 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
711 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
712 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
713 $unplugwarning = "<br>verify that your guest support acpi hotplug";
716 if ($opt eq 'tablet') {
717 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
719 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
723 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
724 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
726 delete $conf->{$opt};
729 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
732 my $safe_num_ne = sub {
735 return 0 if !defined($a) && !defined($b);
736 return 1 if !defined($a);
737 return 1 if !defined($b);
742 my $vmconfig_update_disk = sub {
743 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
745 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
747 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
748 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
750 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
755 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
757 my $media = $drive->{media
} || 'disk';
758 my $oldmedia = $old_drive->{media
} || 'disk';
759 die "unable to change media type\n" if $media ne $oldmedia;
761 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
762 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
764 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
765 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
768 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
769 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
770 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
771 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
772 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
773 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
774 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
775 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
776 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
777 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
778 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
779 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
780 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
781 ($drive->{mbps
} || 0)*1024*1024,
782 ($drive->{mbps_rd
} || 0)*1024*1024,
783 ($drive->{mbps_wr
} || 0)*1024*1024,
785 $drive->{iops_rd
} || 0,
786 $drive->{iops_wr
} || 0,
787 ($drive->{mbps_max
} || 0)*1024*1024,
788 ($drive->{mbps_rd_max
} || 0)*1024*1024,
789 ($drive->{mbps_wr_max
} || 0)*1024*1024,
790 $drive->{iops_max
} || 0,
791 $drive->{iops_rd_max
} || 0,
792 $drive->{iops_wr_max
} || 0)
793 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
798 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
799 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
801 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
802 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
804 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
806 if (PVE
::QemuServer
::check_running
($vmid)) {
807 if ($drive->{file
} eq 'none') {
808 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
810 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
811 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
812 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
816 } else { # hotplug new disks
818 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
822 my $vmconfig_update_net = sub {
823 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
825 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
826 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
827 my $newnet = PVE
::QemuServer
::parse_net
($value);
829 if($oldnet->{model
} ne $newnet->{model
}){
830 #if model change, we try to hot-unplug
831 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
834 if($newnet->{bridge
} && $oldnet->{bridge
}){
835 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
837 if($newnet->{rate
} ne $oldnet->{rate
}){
838 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
841 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
842 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
843 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
847 #if bridge/nat mode change, we try to hot-unplug
848 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
853 $conf->{$opt} = $value;
854 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
855 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
857 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
859 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
862 # POST/PUT {vmid}/config implementation
864 # The original API used PUT (idempotent) an we assumed that all operations
865 # are fast. But it turned out that almost any configuration change can
866 # involve hot-plug actions, or disk alloc/free. Such actions can take long
867 # time to complete and have side effects (not idempotent).
869 # The new implementation uses POST and forks a worker process. We added
870 # a new option 'background_delay'. If specified we wait up to
871 # 'background_delay' second for the worker task to complete. It returns null
872 # if the task is finished within that time, else we return the UPID.
874 my $update_vm_api = sub {
875 my ($param, $sync) = @_;
877 my $rpcenv = PVE
::RPCEnvironment
::get
();
879 my $authuser = $rpcenv->get_user();
881 my $node = extract_param
($param, 'node');
883 my $vmid = extract_param
($param, 'vmid');
885 my $digest = extract_param
($param, 'digest');
887 my $background_delay = extract_param
($param, 'background_delay');
889 my @paramarr = (); # used for log message
890 foreach my $key (keys %$param) {
891 push @paramarr, "-$key", $param->{$key};
894 my $skiplock = extract_param
($param, 'skiplock');
895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
896 if $skiplock && $authuser ne 'root@pam';
898 my $delete_str = extract_param
($param, 'delete');
900 my $force = extract_param
($param, 'force');
902 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
904 my $storecfg = PVE
::Storage
::config
();
906 my $defaults = PVE
::QemuServer
::load_defaults
();
908 &$resolve_cdrom_alias($param);
910 # now try to verify all parameters
913 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
914 $opt = 'ide2' if $opt eq 'cdrom';
915 raise_param_exc
({ delete => "you can't use '-$opt' and " .
916 "-delete $opt' at the same time" })
917 if defined($param->{$opt});
919 if (!PVE
::QemuServer
::option_exists
($opt)) {
920 raise_param_exc
({ delete => "unknown option '$opt'" });
926 foreach my $opt (keys %$param) {
927 if (PVE
::QemuServer
::valid_drivename
($opt)) {
929 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
930 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
931 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
932 } elsif ($opt =~ m/^net(\d+)$/) {
934 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
935 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
939 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
941 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
943 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
947 my $conf = PVE
::QemuServer
::load_config
($vmid);
949 die "checksum missmatch (file change by other user?)\n"
950 if $digest && $digest ne $conf->{digest
};
952 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
954 if ($param->{memory
} || defined($param->{balloon
})) {
955 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
956 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
958 die "balloon value too large (must be smaller than assigned memory)\n"
959 if $balloon && $balloon > $maxmem;
962 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
966 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
968 foreach my $opt (@delete) { # delete
969 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
970 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
973 my $running = PVE
::QemuServer
::check_running
($vmid);
975 foreach my $opt (keys %$param) { # add/change
977 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
981 if (PVE
::QemuServer
::valid_drivename
($opt)) {
983 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
984 $opt, $param->{$opt}, $force);
986 } elsif ($opt =~ m/^net(\d+)$/) { #nics
988 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
989 $opt, $param->{$opt});
993 if($opt eq 'tablet' && $param->{$opt} == 1){
994 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
995 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
996 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
999 if($opt eq 'cores' && $conf->{maxcpus
}){
1000 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1003 $conf->{$opt} = $param->{$opt};
1004 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1008 # allow manual ballooning if shares is set to zero
1009 if ($running && defined($param->{balloon
}) &&
1010 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1011 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1012 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1020 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1022 if ($background_delay) {
1024 # Note: It would be better to do that in the Event based HTTPServer
1025 # to avoid blocking call to sleep.
1027 my $end_time = time() + $background_delay;
1029 my $task = PVE
::Tools
::upid_decode
($upid);
1032 while (time() < $end_time) {
1033 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1035 sleep(1); # this gets interrupted when child process ends
1039 my $status = PVE
::Tools
::upid_read_status
($upid);
1040 return undef if $status eq 'OK';
1049 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1052 my $vm_config_perm_list = [
1057 'VM.Config.Network',
1059 'VM.Config.Options',
1062 __PACKAGE__-
>register_method({
1063 name
=> 'update_vm_async',
1064 path
=> '{vmid}/config',
1068 description
=> "Set virtual machine options (asynchrounous API).",
1070 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1073 additionalProperties
=> 0,
1074 properties
=> PVE
::QemuServer
::json_config_properties
(
1076 node
=> get_standard_option
('pve-node'),
1077 vmid
=> get_standard_option
('pve-vmid'),
1078 skiplock
=> get_standard_option
('skiplock'),
1080 type
=> 'string', format
=> 'pve-configid-list',
1081 description
=> "A list of settings you want to delete.",
1086 description
=> $opt_force_description,
1088 requires
=> 'delete',
1092 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1096 background_delay
=> {
1098 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1109 code
=> $update_vm_api,
1112 __PACKAGE__-
>register_method({
1113 name
=> 'update_vm',
1114 path
=> '{vmid}/config',
1118 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1120 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1123 additionalProperties
=> 0,
1124 properties
=> PVE
::QemuServer
::json_config_properties
(
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1128 skiplock
=> get_standard_option
('skiplock'),
1130 type
=> 'string', format
=> 'pve-configid-list',
1131 description
=> "A list of settings you want to delete.",
1136 description
=> $opt_force_description,
1138 requires
=> 'delete',
1142 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1148 returns
=> { type
=> 'null' },
1151 &$update_vm_api($param, 1);
1157 __PACKAGE__-
>register_method({
1158 name
=> 'destroy_vm',
1163 description
=> "Destroy the vm (also delete all used/owned volumes).",
1165 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1168 additionalProperties
=> 0,
1170 node
=> get_standard_option
('pve-node'),
1171 vmid
=> get_standard_option
('pve-vmid'),
1172 skiplock
=> get_standard_option
('skiplock'),
1181 my $rpcenv = PVE
::RPCEnvironment
::get
();
1183 my $authuser = $rpcenv->get_user();
1185 my $vmid = $param->{vmid
};
1187 my $skiplock = $param->{skiplock
};
1188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1189 if $skiplock && $authuser ne 'root@pam';
1192 my $conf = PVE
::QemuServer
::load_config
($vmid);
1194 my $storecfg = PVE
::Storage
::config
();
1196 my $delVMfromPoolFn = sub {
1197 my $usercfg = cfs_read_file
("user.cfg");
1198 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1199 if (my $data = $usercfg->{pools
}->{$pool}) {
1200 delete $data->{vms
}->{$vmid};
1201 delete $usercfg->{vms
}->{$vmid};
1202 cfs_write_file
("user.cfg", $usercfg);
1210 syslog
('info', "destroy VM $vmid: $upid\n");
1212 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1214 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1217 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1220 __PACKAGE__-
>register_method({
1222 path
=> '{vmid}/unlink',
1226 description
=> "Unlink/delete disk images.",
1228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid'),
1236 type
=> 'string', format
=> 'pve-configid-list',
1237 description
=> "A list of disk IDs you want to delete.",
1241 description
=> $opt_force_description,
1246 returns
=> { type
=> 'null'},
1250 $param->{delete} = extract_param
($param, 'idlist');
1252 __PACKAGE__-
>update_vm($param);
1259 __PACKAGE__-
>register_method({
1261 path
=> '{vmid}/vncproxy',
1265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1267 description
=> "Creates a TCP VNC proxy connections.",
1269 additionalProperties
=> 0,
1271 node
=> get_standard_option
('pve-node'),
1272 vmid
=> get_standard_option
('pve-vmid'),
1276 additionalProperties
=> 0,
1278 user
=> { type
=> 'string' },
1279 ticket
=> { type
=> 'string' },
1280 cert
=> { type
=> 'string' },
1281 port
=> { type
=> 'integer' },
1282 upid
=> { type
=> 'string' },
1288 my $rpcenv = PVE
::RPCEnvironment
::get
();
1290 my $authuser = $rpcenv->get_user();
1292 my $vmid = $param->{vmid
};
1293 my $node = $param->{node
};
1295 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1297 my $authpath = "/vms/$vmid";
1299 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1301 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1304 my $port = PVE
::Tools
::next_vnc_port
();
1309 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1310 $remip = PVE
::Cluster
::remote_node_ip
($node);
1311 # NOTE: kvm VNC traffic is already TLS encrypted
1312 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1320 syslog
('info', "starting vnc proxy $upid\n");
1324 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1326 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1327 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1328 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1329 '-timeout', $timeout, '-authpath', $authpath,
1330 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1333 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1335 my $qmstr = join(' ', @$qmcmd);
1337 # also redirect stderr (else we get RFB protocol errors)
1338 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1341 PVE
::Tools
::run_command
($cmd);
1346 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1348 PVE
::Tools
::wait_for_vnc_port
($port);
1359 __PACKAGE__-
>register_method({
1360 name
=> 'spiceproxy',
1361 path
=> '{vmid}/spiceproxy',
1366 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1368 description
=> "Returns a SPICE configuration to connect to the VM.",
1370 additionalProperties
=> 0,
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid'),
1374 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1377 returns
=> get_standard_option
('remote-viewer-config'),
1381 my $rpcenv = PVE
::RPCEnvironment
::get
();
1383 my $authuser = $rpcenv->get_user();
1385 my $vmid = $param->{vmid
};
1386 my $node = $param->{node
};
1387 my $proxy = $param->{proxy
};
1389 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1390 my $title = "VM $vmid - $conf->{'name'}",
1392 my $port = PVE
::QemuServer
::spice_port
($vmid);
1394 my ($ticket, undef, $remote_viewer_config) =
1395 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1397 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1398 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1400 return $remote_viewer_config;
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 # read spice ticket from STDIN
1539 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1540 if (defined(my $line = <>)) {
1542 $spice_ticket = $line;
1546 my $storecfg = PVE
::Storage
::config
();
1548 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1549 $rpcenv->{type
} ne 'ha') {
1554 my $service = "pvevm:$vmid";
1556 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1558 print "Executing HA start for VM $vmid\n";
1560 PVE
::Tools
::run_command
($cmd);
1565 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1572 syslog
('info', "start VM $vmid: $upid\n");
1574 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1575 $machine, $spice_ticket);
1580 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1584 __PACKAGE__-
>register_method({
1586 path
=> '{vmid}/status/stop',
1590 description
=> "Stop virtual machine.",
1592 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1595 additionalProperties
=> 0,
1597 node
=> get_standard_option
('pve-node'),
1598 vmid
=> get_standard_option
('pve-vmid'),
1599 skiplock
=> get_standard_option
('skiplock'),
1600 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1602 description
=> "Wait maximal timeout seconds.",
1608 description
=> "Do not decativate storage volumes.",
1621 my $rpcenv = PVE
::RPCEnvironment
::get
();
1623 my $authuser = $rpcenv->get_user();
1625 my $node = extract_param
($param, 'node');
1627 my $vmid = extract_param
($param, 'vmid');
1629 my $skiplock = extract_param
($param, 'skiplock');
1630 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1631 if $skiplock && $authuser ne 'root@pam';
1633 my $keepActive = extract_param
($param, 'keepActive');
1634 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1635 if $keepActive && $authuser ne 'root@pam';
1637 my $migratedfrom = extract_param
($param, 'migratedfrom');
1638 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1639 if $migratedfrom && $authuser ne 'root@pam';
1642 my $storecfg = PVE
::Storage
::config
();
1644 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1649 my $service = "pvevm:$vmid";
1651 my $cmd = ['clusvcadm', '-d', $service];
1653 print "Executing HA stop for VM $vmid\n";
1655 PVE
::Tools
::run_command
($cmd);
1660 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1666 syslog
('info', "stop VM $vmid: $upid\n");
1668 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1669 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1674 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1678 __PACKAGE__-
>register_method({
1680 path
=> '{vmid}/status/reset',
1684 description
=> "Reset virtual machine.",
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1689 additionalProperties
=> 0,
1691 node
=> get_standard_option
('pve-node'),
1692 vmid
=> get_standard_option
('pve-vmid'),
1693 skiplock
=> get_standard_option
('skiplock'),
1702 my $rpcenv = PVE
::RPCEnvironment
::get
();
1704 my $authuser = $rpcenv->get_user();
1706 my $node = extract_param
($param, 'node');
1708 my $vmid = extract_param
($param, 'vmid');
1710 my $skiplock = extract_param
($param, 'skiplock');
1711 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1712 if $skiplock && $authuser ne 'root@pam';
1714 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1719 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1724 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1727 __PACKAGE__-
>register_method({
1728 name
=> 'vm_shutdown',
1729 path
=> '{vmid}/status/shutdown',
1733 description
=> "Shutdown virtual machine.",
1735 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1738 additionalProperties
=> 0,
1740 node
=> get_standard_option
('pve-node'),
1741 vmid
=> get_standard_option
('pve-vmid'),
1742 skiplock
=> get_standard_option
('skiplock'),
1744 description
=> "Wait maximal timeout seconds.",
1750 description
=> "Make sure the VM stops.",
1756 description
=> "Do not decativate storage volumes.",
1769 my $rpcenv = PVE
::RPCEnvironment
::get
();
1771 my $authuser = $rpcenv->get_user();
1773 my $node = extract_param
($param, 'node');
1775 my $vmid = extract_param
($param, 'vmid');
1777 my $skiplock = extract_param
($param, 'skiplock');
1778 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1779 if $skiplock && $authuser ne 'root@pam';
1781 my $keepActive = extract_param
($param, 'keepActive');
1782 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1783 if $keepActive && $authuser ne 'root@pam';
1785 my $storecfg = PVE
::Storage
::config
();
1790 syslog
('info', "shutdown VM $vmid: $upid\n");
1792 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1793 1, $param->{forceStop
}, $keepActive);
1798 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1801 __PACKAGE__-
>register_method({
1802 name
=> 'vm_suspend',
1803 path
=> '{vmid}/status/suspend',
1807 description
=> "Suspend virtual machine.",
1809 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1812 additionalProperties
=> 0,
1814 node
=> get_standard_option
('pve-node'),
1815 vmid
=> get_standard_option
('pve-vmid'),
1816 skiplock
=> get_standard_option
('skiplock'),
1825 my $rpcenv = PVE
::RPCEnvironment
::get
();
1827 my $authuser = $rpcenv->get_user();
1829 my $node = extract_param
($param, 'node');
1831 my $vmid = extract_param
($param, 'vmid');
1833 my $skiplock = extract_param
($param, 'skiplock');
1834 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1835 if $skiplock && $authuser ne 'root@pam';
1837 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1842 syslog
('info', "suspend VM $vmid: $upid\n");
1844 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1849 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1852 __PACKAGE__-
>register_method({
1853 name
=> 'vm_resume',
1854 path
=> '{vmid}/status/resume',
1858 description
=> "Resume virtual machine.",
1860 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1863 additionalProperties
=> 0,
1865 node
=> get_standard_option
('pve-node'),
1866 vmid
=> get_standard_option
('pve-vmid'),
1867 skiplock
=> get_standard_option
('skiplock'),
1876 my $rpcenv = PVE
::RPCEnvironment
::get
();
1878 my $authuser = $rpcenv->get_user();
1880 my $node = extract_param
($param, 'node');
1882 my $vmid = extract_param
($param, 'vmid');
1884 my $skiplock = extract_param
($param, 'skiplock');
1885 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1886 if $skiplock && $authuser ne 'root@pam';
1888 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1893 syslog
('info', "resume VM $vmid: $upid\n");
1895 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1900 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1903 __PACKAGE__-
>register_method({
1904 name
=> 'vm_sendkey',
1905 path
=> '{vmid}/sendkey',
1909 description
=> "Send key event to virtual machine.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid'),
1918 skiplock
=> get_standard_option
('skiplock'),
1920 description
=> "The key (qemu monitor encoding).",
1925 returns
=> { type
=> 'null'},
1929 my $rpcenv = PVE
::RPCEnvironment
::get
();
1931 my $authuser = $rpcenv->get_user();
1933 my $node = extract_param
($param, 'node');
1935 my $vmid = extract_param
($param, 'vmid');
1937 my $skiplock = extract_param
($param, 'skiplock');
1938 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1939 if $skiplock && $authuser ne 'root@pam';
1941 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1946 __PACKAGE__-
>register_method({
1947 name
=> 'vm_feature',
1948 path
=> '{vmid}/feature',
1952 description
=> "Check if feature for virtual machine is available.",
1954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1957 additionalProperties
=> 0,
1959 node
=> get_standard_option
('pve-node'),
1960 vmid
=> get_standard_option
('pve-vmid'),
1962 description
=> "Feature to check.",
1964 enum
=> [ 'snapshot', 'clone', 'copy' ],
1966 snapname
=> get_standard_option
('pve-snapshot-name', {
1974 hasFeature
=> { type
=> 'boolean' },
1977 items
=> { type
=> 'string' },
1984 my $node = extract_param
($param, 'node');
1986 my $vmid = extract_param
($param, 'vmid');
1988 my $snapname = extract_param
($param, 'snapname');
1990 my $feature = extract_param
($param, 'feature');
1992 my $running = PVE
::QemuServer
::check_running
($vmid);
1994 my $conf = PVE
::QemuServer
::load_config
($vmid);
1997 my $snap = $conf->{snapshots
}->{$snapname};
1998 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2001 my $storecfg = PVE
::Storage
::config
();
2003 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2004 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2007 hasFeature
=> $hasFeature,
2008 nodes
=> [ keys %$nodelist ],
2012 __PACKAGE__-
>register_method({
2014 path
=> '{vmid}/clone',
2018 description
=> "Create a copy of virtual machine/template.",
2020 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2021 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2022 "'Datastore.AllocateSpace' on any used storage.",
2025 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2027 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2028 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2033 additionalProperties
=> 0,
2035 node
=> get_standard_option
('pve-node'),
2036 vmid
=> get_standard_option
('pve-vmid'),
2037 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2040 type
=> 'string', format
=> 'dns-name',
2041 description
=> "Set a name for the new VM.",
2046 description
=> "Description for the new VM.",
2050 type
=> 'string', format
=> 'pve-poolid',
2051 description
=> "Add the new VM to the specified pool.",
2053 snapname
=> get_standard_option
('pve-snapshot-name', {
2057 storage
=> get_standard_option
('pve-storage-id', {
2058 description
=> "Target storage for full clone.",
2063 description
=> "Target format for file storage.",
2067 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2072 description
=> "Create a full copy of all disk. This is always done when " .
2073 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2076 target
=> get_standard_option
('pve-node', {
2077 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2088 my $rpcenv = PVE
::RPCEnvironment
::get
();
2090 my $authuser = $rpcenv->get_user();
2092 my $node = extract_param
($param, 'node');
2094 my $vmid = extract_param
($param, 'vmid');
2096 my $newid = extract_param
($param, 'newid');
2098 my $pool = extract_param
($param, 'pool');
2100 if (defined($pool)) {
2101 $rpcenv->check_pool_exist($pool);
2104 my $snapname = extract_param
($param, 'snapname');
2106 my $storage = extract_param
($param, 'storage');
2108 my $format = extract_param
($param, 'format');
2110 my $target = extract_param
($param, 'target');
2112 my $localnode = PVE
::INotify
::nodename
();
2114 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2116 PVE
::Cluster
::check_node_exists
($target) if $target;
2118 my $storecfg = PVE
::Storage
::config
();
2121 # check if storage is enabled on local node
2122 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2124 # check if storage is available on target node
2125 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2126 # clone only works if target storage is shared
2127 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2128 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2132 PVE
::Cluster
::check_cfs_quorum
();
2134 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2136 # exclusive lock if VM is running - else shared lock is enough;
2137 my $shared_lock = $running ?
0 : 1;
2141 # do all tests after lock
2142 # we also try to do all tests before we fork the worker
2144 my $conf = PVE
::QemuServer
::load_config
($vmid);
2146 PVE
::QemuServer
::check_lock
($conf);
2148 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2150 die "unexpected state change\n" if $verify_running != $running;
2152 die "snapshot '$snapname' does not exist\n"
2153 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2155 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2157 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2159 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2161 my $conffile = PVE
::QemuServer
::config_file
($newid);
2163 die "unable to create VM $newid: config file already exists\n"
2166 my $newconf = { lock => 'clone' };
2170 foreach my $opt (keys %$oldconf) {
2171 my $value = $oldconf->{$opt};
2173 # do not copy snapshot related info
2174 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2175 $opt eq 'vmstate' || $opt eq 'snapstate';
2177 # always change MAC! address
2178 if ($opt =~ m/^net(\d+)$/) {
2179 my $net = PVE
::QemuServer
::parse_net
($value);
2180 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2181 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2182 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2183 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2184 die "unable to parse drive options for '$opt'\n" if !$drive;
2185 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2186 $newconf->{$opt} = $value; # simply copy configuration
2188 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2189 die "Full clone feature is not available"
2190 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2193 $drives->{$opt} = $drive;
2194 push @$vollist, $drive->{file
};
2197 # copy everything else
2198 $newconf->{$opt} = $value;
2202 delete $newconf->{template
};
2204 if ($param->{name
}) {
2205 $newconf->{name
} = $param->{name
};
2207 if ($oldconf->{name
}) {
2208 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2210 $newconf->{name
} = "Copy-of-VM-$vmid";
2214 if ($param->{description
}) {
2215 $newconf->{description
} = $param->{description
};
2218 # create empty/temp config - this fails if VM already exists on other node
2219 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2224 my $newvollist = [];
2227 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2229 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2231 foreach my $opt (keys %$drives) {
2232 my $drive = $drives->{$opt};
2234 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2235 $newid, $storage, $format, $drive->{full
}, $newvollist);
2237 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2239 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2242 delete $newconf->{lock};
2243 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2246 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2247 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2249 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2250 die "Failed to move config to node '$target' - rename failed: $!\n"
2251 if !rename($conffile, $newconffile);
2254 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2259 sleep 1; # some storage like rbd need to wait before release volume - really?
2261 foreach my $volid (@$newvollist) {
2262 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2265 die "clone failed: $err";
2271 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2274 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2275 # Aquire exclusive lock lock for $newid
2276 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2281 __PACKAGE__-
>register_method({
2282 name
=> 'move_vm_disk',
2283 path
=> '{vmid}/move_disk',
2287 description
=> "Move volume to different storage.",
2289 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2290 "and 'Datastore.AllocateSpace' permissions on the storage.",
2293 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2294 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2298 additionalProperties
=> 0,
2300 node
=> get_standard_option
('pve-node'),
2301 vmid
=> get_standard_option
('pve-vmid'),
2304 description
=> "The disk you want to move.",
2305 enum
=> [ PVE
::QemuServer
::disknames
() ],
2307 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2310 description
=> "Target Format.",
2311 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2316 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2322 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2330 description
=> "the task ID.",
2335 my $rpcenv = PVE
::RPCEnvironment
::get
();
2337 my $authuser = $rpcenv->get_user();
2339 my $node = extract_param
($param, 'node');
2341 my $vmid = extract_param
($param, 'vmid');
2343 my $digest = extract_param
($param, 'digest');
2345 my $disk = extract_param
($param, 'disk');
2347 my $storeid = extract_param
($param, 'storage');
2349 my $format = extract_param
($param, 'format');
2351 my $storecfg = PVE
::Storage
::config
();
2353 my $updatefn = sub {
2355 my $conf = PVE
::QemuServer
::load_config
($vmid);
2357 die "checksum missmatch (file change by other user?)\n"
2358 if $digest && $digest ne $conf->{digest
};
2360 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2362 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2364 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2366 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2369 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2370 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2374 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2375 (!$format || !$oldfmt || $oldfmt eq $format);
2377 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2379 my $running = PVE
::QemuServer
::check_running
($vmid);
2381 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2385 my $newvollist = [];
2388 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2390 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2391 $vmid, $storeid, $format, 1, $newvollist);
2393 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2395 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2397 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2400 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2401 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2408 foreach my $volid (@$newvollist) {
2409 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2412 die "storage migration failed: $err";
2415 if ($param->{delete}) {
2416 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2417 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2418 if ($used_paths->{$path}){
2419 warn "volume $old_volid have snapshots. Can't delete it\n";
2420 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2421 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2423 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2429 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2432 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2435 __PACKAGE__-
>register_method({
2436 name
=> 'migrate_vm',
2437 path
=> '{vmid}/migrate',
2441 description
=> "Migrate virtual machine. Creates a new migration task.",
2443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2446 additionalProperties
=> 0,
2448 node
=> get_standard_option
('pve-node'),
2449 vmid
=> get_standard_option
('pve-vmid'),
2450 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2453 description
=> "Use online/live migration.",
2458 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2465 description
=> "the task ID.",
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $target = extract_param
($param, 'target');
2476 my $localnode = PVE
::INotify
::nodename
();
2477 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2479 PVE
::Cluster
::check_cfs_quorum
();
2481 PVE
::Cluster
::check_node_exists
($target);
2483 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2485 my $vmid = extract_param
($param, 'vmid');
2487 raise_param_exc
({ force
=> "Only root may use this option." })
2488 if $param->{force
} && $authuser ne 'root@pam';
2491 my $conf = PVE
::QemuServer
::load_config
($vmid);
2493 # try to detect errors early
2495 PVE
::QemuServer
::check_lock
($conf);
2497 if (PVE
::QemuServer
::check_running
($vmid)) {
2498 die "cant migrate running VM without --online\n"
2499 if !$param->{online
};
2502 my $storecfg = PVE
::Storage
::config
();
2503 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2505 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2510 my $service = "pvevm:$vmid";
2512 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2514 print "Executing HA migrate for VM $vmid to node $target\n";
2516 PVE
::Tools
::run_command
($cmd);
2521 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2528 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2531 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2536 __PACKAGE__-
>register_method({
2538 path
=> '{vmid}/monitor',
2542 description
=> "Execute Qemu monitor commands.",
2544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2547 additionalProperties
=> 0,
2549 node
=> get_standard_option
('pve-node'),
2550 vmid
=> get_standard_option
('pve-vmid'),
2553 description
=> "The monitor command.",
2557 returns
=> { type
=> 'string'},
2561 my $vmid = $param->{vmid
};
2563 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2567 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2569 $res = "ERROR: $@" if $@;
2574 __PACKAGE__-
>register_method({
2575 name
=> 'resize_vm',
2576 path
=> '{vmid}/resize',
2580 description
=> "Extend volume size.",
2582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2585 additionalProperties
=> 0,
2587 node
=> get_standard_option
('pve-node'),
2588 vmid
=> get_standard_option
('pve-vmid'),
2589 skiplock
=> get_standard_option
('skiplock'),
2592 description
=> "The disk you want to resize.",
2593 enum
=> [PVE
::QemuServer
::disknames
()],
2597 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2598 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.",
2602 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2608 returns
=> { type
=> 'null'},
2612 my $rpcenv = PVE
::RPCEnvironment
::get
();
2614 my $authuser = $rpcenv->get_user();
2616 my $node = extract_param
($param, 'node');
2618 my $vmid = extract_param
($param, 'vmid');
2620 my $digest = extract_param
($param, 'digest');
2622 my $disk = extract_param
($param, 'disk');
2624 my $sizestr = extract_param
($param, 'size');
2626 my $skiplock = extract_param
($param, 'skiplock');
2627 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2628 if $skiplock && $authuser ne 'root@pam';
2630 my $storecfg = PVE
::Storage
::config
();
2632 my $updatefn = sub {
2634 my $conf = PVE
::QemuServer
::load_config
($vmid);
2636 die "checksum missmatch (file change by other user?)\n"
2637 if $digest && $digest ne $conf->{digest
};
2638 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2640 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2642 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2644 my $volid = $drive->{file
};
2646 die "disk '$disk' has no associated volume\n" if !$volid;
2648 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2650 die "you can't online resize a virtio windows bootdisk\n"
2651 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2653 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2655 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2657 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2659 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2660 my ($ext, $newsize, $unit) = ($1, $2, $4);
2663 $newsize = $newsize * 1024;
2664 } elsif ($unit eq 'M') {
2665 $newsize = $newsize * 1024 * 1024;
2666 } elsif ($unit eq 'G') {
2667 $newsize = $newsize * 1024 * 1024 * 1024;
2668 } elsif ($unit eq 'T') {
2669 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2672 $newsize += $size if $ext;
2673 $newsize = int($newsize);
2675 die "unable to skrink disk size\n" if $newsize < $size;
2677 return if $size == $newsize;
2679 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2681 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2683 $drive->{size
} = $newsize;
2684 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2686 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2689 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2693 __PACKAGE__-
>register_method({
2694 name
=> 'snapshot_list',
2695 path
=> '{vmid}/snapshot',
2697 description
=> "List all snapshots.",
2699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2702 protected
=> 1, # qemu pid files are only readable by root
2704 additionalProperties
=> 0,
2706 vmid
=> get_standard_option
('pve-vmid'),
2707 node
=> get_standard_option
('pve-node'),
2716 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2721 my $vmid = $param->{vmid
};
2723 my $conf = PVE
::QemuServer
::load_config
($vmid);
2724 my $snaphash = $conf->{snapshots
} || {};
2728 foreach my $name (keys %$snaphash) {
2729 my $d = $snaphash->{$name};
2732 snaptime
=> $d->{snaptime
} || 0,
2733 vmstate
=> $d->{vmstate
} ?
1 : 0,
2734 description
=> $d->{description
} || '',
2736 $item->{parent
} = $d->{parent
} if $d->{parent
};
2737 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2741 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2742 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2743 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2745 push @$res, $current;
2750 __PACKAGE__-
>register_method({
2752 path
=> '{vmid}/snapshot',
2756 description
=> "Snapshot a VM.",
2758 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2761 additionalProperties
=> 0,
2763 node
=> get_standard_option
('pve-node'),
2764 vmid
=> get_standard_option
('pve-vmid'),
2765 snapname
=> get_standard_option
('pve-snapshot-name'),
2769 description
=> "Save the vmstate",
2774 description
=> "Freeze the filesystem",
2779 description
=> "A textual description or comment.",
2785 description
=> "the task ID.",
2790 my $rpcenv = PVE
::RPCEnvironment
::get
();
2792 my $authuser = $rpcenv->get_user();
2794 my $node = extract_param
($param, 'node');
2796 my $vmid = extract_param
($param, 'vmid');
2798 my $snapname = extract_param
($param, 'snapname');
2800 die "unable to use snapshot name 'current' (reserved name)\n"
2801 if $snapname eq 'current';
2804 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2805 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2806 $param->{freezefs
}, $param->{description
});
2809 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2812 __PACKAGE__-
>register_method({
2813 name
=> 'snapshot_cmd_idx',
2814 path
=> '{vmid}/snapshot/{snapname}',
2821 additionalProperties
=> 0,
2823 vmid
=> get_standard_option
('pve-vmid'),
2824 node
=> get_standard_option
('pve-node'),
2825 snapname
=> get_standard_option
('pve-snapshot-name'),
2834 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2841 push @$res, { cmd
=> 'rollback' };
2842 push @$res, { cmd
=> 'config' };
2847 __PACKAGE__-
>register_method({
2848 name
=> 'update_snapshot_config',
2849 path
=> '{vmid}/snapshot/{snapname}/config',
2853 description
=> "Update snapshot metadata.",
2855 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2858 additionalProperties
=> 0,
2860 node
=> get_standard_option
('pve-node'),
2861 vmid
=> get_standard_option
('pve-vmid'),
2862 snapname
=> get_standard_option
('pve-snapshot-name'),
2866 description
=> "A textual description or comment.",
2870 returns
=> { type
=> 'null' },
2874 my $rpcenv = PVE
::RPCEnvironment
::get
();
2876 my $authuser = $rpcenv->get_user();
2878 my $vmid = extract_param
($param, 'vmid');
2880 my $snapname = extract_param
($param, 'snapname');
2882 return undef if !defined($param->{description
});
2884 my $updatefn = sub {
2886 my $conf = PVE
::QemuServer
::load_config
($vmid);
2888 PVE
::QemuServer
::check_lock
($conf);
2890 my $snap = $conf->{snapshots
}->{$snapname};
2892 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2894 $snap->{description
} = $param->{description
} if defined($param->{description
});
2896 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2899 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2904 __PACKAGE__-
>register_method({
2905 name
=> 'get_snapshot_config',
2906 path
=> '{vmid}/snapshot/{snapname}/config',
2909 description
=> "Get snapshot configuration",
2911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2914 additionalProperties
=> 0,
2916 node
=> get_standard_option
('pve-node'),
2917 vmid
=> get_standard_option
('pve-vmid'),
2918 snapname
=> get_standard_option
('pve-snapshot-name'),
2921 returns
=> { type
=> "object" },
2925 my $rpcenv = PVE
::RPCEnvironment
::get
();
2927 my $authuser = $rpcenv->get_user();
2929 my $vmid = extract_param
($param, 'vmid');
2931 my $snapname = extract_param
($param, 'snapname');
2933 my $conf = PVE
::QemuServer
::load_config
($vmid);
2935 my $snap = $conf->{snapshots
}->{$snapname};
2937 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2942 __PACKAGE__-
>register_method({
2944 path
=> '{vmid}/snapshot/{snapname}/rollback',
2948 description
=> "Rollback VM state to specified snapshot.",
2950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2953 additionalProperties
=> 0,
2955 node
=> get_standard_option
('pve-node'),
2956 vmid
=> get_standard_option
('pve-vmid'),
2957 snapname
=> get_standard_option
('pve-snapshot-name'),
2962 description
=> "the task ID.",
2967 my $rpcenv = PVE
::RPCEnvironment
::get
();
2969 my $authuser = $rpcenv->get_user();
2971 my $node = extract_param
($param, 'node');
2973 my $vmid = extract_param
($param, 'vmid');
2975 my $snapname = extract_param
($param, 'snapname');
2978 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2979 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2982 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2985 __PACKAGE__-
>register_method({
2986 name
=> 'delsnapshot',
2987 path
=> '{vmid}/snapshot/{snapname}',
2991 description
=> "Delete a VM snapshot.",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid'),
3000 snapname
=> get_standard_option
('pve-snapshot-name'),
3004 description
=> "For removal from config file, even if removing disk snapshots fails.",
3010 description
=> "the task ID.",
3015 my $rpcenv = PVE
::RPCEnvironment
::get
();
3017 my $authuser = $rpcenv->get_user();
3019 my $node = extract_param
($param, 'node');
3021 my $vmid = extract_param
($param, 'vmid');
3023 my $snapname = extract_param
($param, 'snapname');
3026 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3027 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3030 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3033 __PACKAGE__-
>register_method({
3035 path
=> '{vmid}/template',
3039 description
=> "Create a Template.",
3041 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3042 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3045 additionalProperties
=> 0,
3047 node
=> get_standard_option
('pve-node'),
3048 vmid
=> get_standard_option
('pve-vmid'),
3052 description
=> "If you want to convert only 1 disk to base image.",
3053 enum
=> [PVE
::QemuServer
::disknames
()],
3058 returns
=> { type
=> 'null'},
3062 my $rpcenv = PVE
::RPCEnvironment
::get
();
3064 my $authuser = $rpcenv->get_user();
3066 my $node = extract_param
($param, 'node');
3068 my $vmid = extract_param
($param, 'vmid');
3070 my $disk = extract_param
($param, 'disk');
3072 my $updatefn = sub {
3074 my $conf = PVE
::QemuServer
::load_config
($vmid);
3076 PVE
::QemuServer
::check_lock
($conf);
3078 die "unable to create template, because VM contains snapshots\n"
3079 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3081 die "you can't convert a template to a template\n"
3082 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3084 die "you can't convert a VM to template if VM is running\n"
3085 if PVE
::QemuServer
::check_running
($vmid);
3088 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3091 $conf->{template
} = 1;
3092 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3094 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3097 PVE
::QemuServer
::lock_config
($vmid, $updatefn);