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
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
842 PVE
::Network
::tap_unplug
($iface);
843 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
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 description
=> "starts websockify instead of vncproxy",
1281 additionalProperties
=> 0,
1283 user
=> { type
=> 'string' },
1284 ticket
=> { type
=> 'string' },
1285 cert
=> { type
=> 'string' },
1286 port
=> { type
=> 'integer' },
1287 upid
=> { type
=> 'string' },
1293 my $rpcenv = PVE
::RPCEnvironment
::get
();
1295 my $authuser = $rpcenv->get_user();
1297 my $vmid = $param->{vmid
};
1298 my $node = $param->{node
};
1299 my $websocket = $param->{websocket
};
1301 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1303 my $authpath = "/vms/$vmid";
1305 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1307 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1310 my $port = PVE
::Tools
::next_vnc_port
();
1315 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1316 $remip = PVE
::Cluster
::remote_node_ip
($node);
1317 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1318 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1326 syslog
('info', "starting vnc proxy $upid\n");
1330 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1332 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1334 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1335 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1336 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1337 '-timeout', $timeout, '-authpath', $authpath,
1338 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1341 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1343 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1345 my $qmstr = join(' ', @$qmcmd);
1347 # also redirect stderr (else we get RFB protocol errors)
1348 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1351 PVE
::Tools
::run_command
($cmd);
1356 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1358 PVE
::Tools
::wait_for_vnc_port
($port);
1369 __PACKAGE__-
>register_method({
1370 name
=> 'vncwebsocket',
1371 path
=> '{vmid}/vncwebsocket',
1374 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1376 description
=> "Opens a weksocket for VNV traffic.",
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid'),
1383 description
=> "Port number returned by previous vncproxy call.",
1393 port
=> { type
=> 'string' },
1399 my $rpcenv = PVE
::RPCEnvironment
::get
();
1401 my $authuser = $rpcenv->get_user();
1403 my $vmid = $param->{vmid
};
1404 my $node = $param->{node
};
1406 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1408 # Note: VNC ports are acessible from outside, so we do not gain any
1409 # security if we verify that $param->{port} belongs to VM $vmid. This
1410 # check is done by verifying the VNC ticket (inside VNC protocol).
1412 my $port = $param->{port
};
1414 return { port
=> $port };
1417 __PACKAGE__-
>register_method({
1418 name
=> 'spiceproxy',
1419 path
=> '{vmid}/spiceproxy',
1424 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1426 description
=> "Returns a SPICE configuration to connect to the VM.",
1428 additionalProperties
=> 0,
1430 node
=> get_standard_option
('pve-node'),
1431 vmid
=> get_standard_option
('pve-vmid'),
1432 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1435 returns
=> get_standard_option
('remote-viewer-config'),
1439 my $rpcenv = PVE
::RPCEnvironment
::get
();
1441 my $authuser = $rpcenv->get_user();
1443 my $vmid = $param->{vmid
};
1444 my $node = $param->{node
};
1445 my $proxy = $param->{proxy
};
1447 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1448 my $title = "VM $vmid - $conf->{'name'}",
1450 my $port = PVE
::QemuServer
::spice_port
($vmid);
1452 my ($ticket, undef, $remote_viewer_config) =
1453 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1455 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1456 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1458 return $remote_viewer_config;
1461 __PACKAGE__-
>register_method({
1463 path
=> '{vmid}/status',
1466 description
=> "Directory index",
1471 additionalProperties
=> 0,
1473 node
=> get_standard_option
('pve-node'),
1474 vmid
=> get_standard_option
('pve-vmid'),
1482 subdir
=> { type
=> 'string' },
1485 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1491 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1494 { subdir
=> 'current' },
1495 { subdir
=> 'start' },
1496 { subdir
=> 'stop' },
1502 my $vm_is_ha_managed = sub {
1505 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1506 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1512 __PACKAGE__-
>register_method({
1513 name
=> 'vm_status',
1514 path
=> '{vmid}/status/current',
1517 protected
=> 1, # qemu pid files are only readable by root
1518 description
=> "Get virtual machine status.",
1520 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1523 additionalProperties
=> 0,
1525 node
=> get_standard_option
('pve-node'),
1526 vmid
=> get_standard_option
('pve-vmid'),
1529 returns
=> { type
=> 'object' },
1534 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1536 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1537 my $status = $vmstatus->{$param->{vmid
}};
1539 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1541 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1546 __PACKAGE__-
>register_method({
1548 path
=> '{vmid}/status/start',
1552 description
=> "Start virtual machine.",
1554 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid'),
1561 skiplock
=> get_standard_option
('skiplock'),
1562 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1563 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1564 machine
=> get_standard_option
('pve-qm-machine'),
1573 my $rpcenv = PVE
::RPCEnvironment
::get
();
1575 my $authuser = $rpcenv->get_user();
1577 my $node = extract_param
($param, 'node');
1579 my $vmid = extract_param
($param, 'vmid');
1581 my $machine = extract_param
($param, 'machine');
1583 my $stateuri = extract_param
($param, 'stateuri');
1584 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1585 if $stateuri && $authuser ne 'root@pam';
1587 my $skiplock = extract_param
($param, 'skiplock');
1588 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1589 if $skiplock && $authuser ne 'root@pam';
1591 my $migratedfrom = extract_param
($param, 'migratedfrom');
1592 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1593 if $migratedfrom && $authuser ne 'root@pam';
1595 # read spice ticket from STDIN
1597 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1598 if (defined(my $line = <>)) {
1600 $spice_ticket = $line;
1604 my $storecfg = PVE
::Storage
::config
();
1606 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1607 $rpcenv->{type
} ne 'ha') {
1612 my $service = "pvevm:$vmid";
1614 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1616 print "Executing HA start for VM $vmid\n";
1618 PVE
::Tools
::run_command
($cmd);
1623 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1630 syslog
('info', "start VM $vmid: $upid\n");
1632 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1633 $machine, $spice_ticket);
1638 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1642 __PACKAGE__-
>register_method({
1644 path
=> '{vmid}/status/stop',
1648 description
=> "Stop virtual machine.",
1650 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1653 additionalProperties
=> 0,
1655 node
=> get_standard_option
('pve-node'),
1656 vmid
=> get_standard_option
('pve-vmid'),
1657 skiplock
=> get_standard_option
('skiplock'),
1658 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1660 description
=> "Wait maximal timeout seconds.",
1666 description
=> "Do not decativate storage volumes.",
1679 my $rpcenv = PVE
::RPCEnvironment
::get
();
1681 my $authuser = $rpcenv->get_user();
1683 my $node = extract_param
($param, 'node');
1685 my $vmid = extract_param
($param, 'vmid');
1687 my $skiplock = extract_param
($param, 'skiplock');
1688 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1689 if $skiplock && $authuser ne 'root@pam';
1691 my $keepActive = extract_param
($param, 'keepActive');
1692 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1693 if $keepActive && $authuser ne 'root@pam';
1695 my $migratedfrom = extract_param
($param, 'migratedfrom');
1696 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1697 if $migratedfrom && $authuser ne 'root@pam';
1700 my $storecfg = PVE
::Storage
::config
();
1702 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1707 my $service = "pvevm:$vmid";
1709 my $cmd = ['clusvcadm', '-d', $service];
1711 print "Executing HA stop for VM $vmid\n";
1713 PVE
::Tools
::run_command
($cmd);
1718 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1724 syslog
('info', "stop VM $vmid: $upid\n");
1726 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1727 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1732 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1736 __PACKAGE__-
>register_method({
1738 path
=> '{vmid}/status/reset',
1742 description
=> "Reset virtual machine.",
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1751 skiplock
=> get_standard_option
('skiplock'),
1760 my $rpcenv = PVE
::RPCEnvironment
::get
();
1762 my $authuser = $rpcenv->get_user();
1764 my $node = extract_param
($param, 'node');
1766 my $vmid = extract_param
($param, 'vmid');
1768 my $skiplock = extract_param
($param, 'skiplock');
1769 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1770 if $skiplock && $authuser ne 'root@pam';
1772 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1777 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1782 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1785 __PACKAGE__-
>register_method({
1786 name
=> 'vm_shutdown',
1787 path
=> '{vmid}/status/shutdown',
1791 description
=> "Shutdown virtual machine.",
1793 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1796 additionalProperties
=> 0,
1798 node
=> get_standard_option
('pve-node'),
1799 vmid
=> get_standard_option
('pve-vmid'),
1800 skiplock
=> get_standard_option
('skiplock'),
1802 description
=> "Wait maximal timeout seconds.",
1808 description
=> "Make sure the VM stops.",
1814 description
=> "Do not decativate storage volumes.",
1827 my $rpcenv = PVE
::RPCEnvironment
::get
();
1829 my $authuser = $rpcenv->get_user();
1831 my $node = extract_param
($param, 'node');
1833 my $vmid = extract_param
($param, 'vmid');
1835 my $skiplock = extract_param
($param, 'skiplock');
1836 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1837 if $skiplock && $authuser ne 'root@pam';
1839 my $keepActive = extract_param
($param, 'keepActive');
1840 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1841 if $keepActive && $authuser ne 'root@pam';
1843 my $storecfg = PVE
::Storage
::config
();
1848 syslog
('info', "shutdown VM $vmid: $upid\n");
1850 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1851 1, $param->{forceStop
}, $keepActive);
1856 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1859 __PACKAGE__-
>register_method({
1860 name
=> 'vm_suspend',
1861 path
=> '{vmid}/status/suspend',
1865 description
=> "Suspend virtual machine.",
1867 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1870 additionalProperties
=> 0,
1872 node
=> get_standard_option
('pve-node'),
1873 vmid
=> get_standard_option
('pve-vmid'),
1874 skiplock
=> get_standard_option
('skiplock'),
1883 my $rpcenv = PVE
::RPCEnvironment
::get
();
1885 my $authuser = $rpcenv->get_user();
1887 my $node = extract_param
($param, 'node');
1889 my $vmid = extract_param
($param, 'vmid');
1891 my $skiplock = extract_param
($param, 'skiplock');
1892 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1893 if $skiplock && $authuser ne 'root@pam';
1895 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1900 syslog
('info', "suspend VM $vmid: $upid\n");
1902 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1907 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1910 __PACKAGE__-
>register_method({
1911 name
=> 'vm_resume',
1912 path
=> '{vmid}/status/resume',
1916 description
=> "Resume virtual machine.",
1918 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1921 additionalProperties
=> 0,
1923 node
=> get_standard_option
('pve-node'),
1924 vmid
=> get_standard_option
('pve-vmid'),
1925 skiplock
=> get_standard_option
('skiplock'),
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $skiplock = extract_param
($param, 'skiplock');
1943 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1944 if $skiplock && $authuser ne 'root@pam';
1946 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1951 syslog
('info', "resume VM $vmid: $upid\n");
1953 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1958 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1961 __PACKAGE__-
>register_method({
1962 name
=> 'vm_sendkey',
1963 path
=> '{vmid}/sendkey',
1967 description
=> "Send key event to virtual machine.",
1969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1972 additionalProperties
=> 0,
1974 node
=> get_standard_option
('pve-node'),
1975 vmid
=> get_standard_option
('pve-vmid'),
1976 skiplock
=> get_standard_option
('skiplock'),
1978 description
=> "The key (qemu monitor encoding).",
1983 returns
=> { type
=> 'null'},
1987 my $rpcenv = PVE
::RPCEnvironment
::get
();
1989 my $authuser = $rpcenv->get_user();
1991 my $node = extract_param
($param, 'node');
1993 my $vmid = extract_param
($param, 'vmid');
1995 my $skiplock = extract_param
($param, 'skiplock');
1996 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1997 if $skiplock && $authuser ne 'root@pam';
1999 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2004 __PACKAGE__-
>register_method({
2005 name
=> 'vm_feature',
2006 path
=> '{vmid}/feature',
2010 description
=> "Check if feature for virtual machine is available.",
2012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2015 additionalProperties
=> 0,
2017 node
=> get_standard_option
('pve-node'),
2018 vmid
=> get_standard_option
('pve-vmid'),
2020 description
=> "Feature to check.",
2022 enum
=> [ 'snapshot', 'clone', 'copy' ],
2024 snapname
=> get_standard_option
('pve-snapshot-name', {
2032 hasFeature
=> { type
=> 'boolean' },
2035 items
=> { type
=> 'string' },
2042 my $node = extract_param
($param, 'node');
2044 my $vmid = extract_param
($param, 'vmid');
2046 my $snapname = extract_param
($param, 'snapname');
2048 my $feature = extract_param
($param, 'feature');
2050 my $running = PVE
::QemuServer
::check_running
($vmid);
2052 my $conf = PVE
::QemuServer
::load_config
($vmid);
2055 my $snap = $conf->{snapshots
}->{$snapname};
2056 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2059 my $storecfg = PVE
::Storage
::config
();
2061 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2062 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2065 hasFeature
=> $hasFeature,
2066 nodes
=> [ keys %$nodelist ],
2070 __PACKAGE__-
>register_method({
2072 path
=> '{vmid}/clone',
2076 description
=> "Create a copy of virtual machine/template.",
2078 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2079 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2080 "'Datastore.AllocateSpace' on any used storage.",
2083 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2085 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2086 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2091 additionalProperties
=> 0,
2093 node
=> get_standard_option
('pve-node'),
2094 vmid
=> get_standard_option
('pve-vmid'),
2095 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2098 type
=> 'string', format
=> 'dns-name',
2099 description
=> "Set a name for the new VM.",
2104 description
=> "Description for the new VM.",
2108 type
=> 'string', format
=> 'pve-poolid',
2109 description
=> "Add the new VM to the specified pool.",
2111 snapname
=> get_standard_option
('pve-snapshot-name', {
2115 storage
=> get_standard_option
('pve-storage-id', {
2116 description
=> "Target storage for full clone.",
2121 description
=> "Target format for file storage.",
2125 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2130 description
=> "Create a full copy of all disk. This is always done when " .
2131 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2134 target
=> get_standard_option
('pve-node', {
2135 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2148 my $authuser = $rpcenv->get_user();
2150 my $node = extract_param
($param, 'node');
2152 my $vmid = extract_param
($param, 'vmid');
2154 my $newid = extract_param
($param, 'newid');
2156 my $pool = extract_param
($param, 'pool');
2158 if (defined($pool)) {
2159 $rpcenv->check_pool_exist($pool);
2162 my $snapname = extract_param
($param, 'snapname');
2164 my $storage = extract_param
($param, 'storage');
2166 my $format = extract_param
($param, 'format');
2168 my $target = extract_param
($param, 'target');
2170 my $localnode = PVE
::INotify
::nodename
();
2172 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2174 PVE
::Cluster
::check_node_exists
($target) if $target;
2176 my $storecfg = PVE
::Storage
::config
();
2179 # check if storage is enabled on local node
2180 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2182 # check if storage is available on target node
2183 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2184 # clone only works if target storage is shared
2185 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2186 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2190 PVE
::Cluster
::check_cfs_quorum
();
2192 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2194 # exclusive lock if VM is running - else shared lock is enough;
2195 my $shared_lock = $running ?
0 : 1;
2199 # do all tests after lock
2200 # we also try to do all tests before we fork the worker
2202 my $conf = PVE
::QemuServer
::load_config
($vmid);
2204 PVE
::QemuServer
::check_lock
($conf);
2206 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2208 die "unexpected state change\n" if $verify_running != $running;
2210 die "snapshot '$snapname' does not exist\n"
2211 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2213 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2215 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2217 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2219 my $conffile = PVE
::QemuServer
::config_file
($newid);
2221 die "unable to create VM $newid: config file already exists\n"
2224 my $newconf = { lock => 'clone' };
2228 foreach my $opt (keys %$oldconf) {
2229 my $value = $oldconf->{$opt};
2231 # do not copy snapshot related info
2232 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2233 $opt eq 'vmstate' || $opt eq 'snapstate';
2235 # always change MAC! address
2236 if ($opt =~ m/^net(\d+)$/) {
2237 my $net = PVE
::QemuServer
::parse_net
($value);
2238 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2239 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2240 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2241 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2242 die "unable to parse drive options for '$opt'\n" if !$drive;
2243 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2244 $newconf->{$opt} = $value; # simply copy configuration
2246 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2247 die "Full clone feature is not available"
2248 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2251 $drives->{$opt} = $drive;
2252 push @$vollist, $drive->{file
};
2255 # copy everything else
2256 $newconf->{$opt} = $value;
2260 delete $newconf->{template
};
2262 if ($param->{name
}) {
2263 $newconf->{name
} = $param->{name
};
2265 if ($oldconf->{name
}) {
2266 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2268 $newconf->{name
} = "Copy-of-VM-$vmid";
2272 if ($param->{description
}) {
2273 $newconf->{description
} = $param->{description
};
2276 # create empty/temp config - this fails if VM already exists on other node
2277 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2282 my $newvollist = [];
2285 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2287 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2289 foreach my $opt (keys %$drives) {
2290 my $drive = $drives->{$opt};
2292 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2293 $newid, $storage, $format, $drive->{full
}, $newvollist);
2295 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2297 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2300 delete $newconf->{lock};
2301 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2304 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2305 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2307 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2308 die "Failed to move config to node '$target' - rename failed: $!\n"
2309 if !rename($conffile, $newconffile);
2312 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2317 sleep 1; # some storage like rbd need to wait before release volume - really?
2319 foreach my $volid (@$newvollist) {
2320 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2323 die "clone failed: $err";
2329 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2332 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2333 # Aquire exclusive lock lock for $newid
2334 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2339 __PACKAGE__-
>register_method({
2340 name
=> 'move_vm_disk',
2341 path
=> '{vmid}/move_disk',
2345 description
=> "Move volume to different storage.",
2347 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2348 "and 'Datastore.AllocateSpace' permissions on the storage.",
2351 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2352 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2356 additionalProperties
=> 0,
2358 node
=> get_standard_option
('pve-node'),
2359 vmid
=> get_standard_option
('pve-vmid'),
2362 description
=> "The disk you want to move.",
2363 enum
=> [ PVE
::QemuServer
::disknames
() ],
2365 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2368 description
=> "Target Format.",
2369 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2374 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2380 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2388 description
=> "the task ID.",
2393 my $rpcenv = PVE
::RPCEnvironment
::get
();
2395 my $authuser = $rpcenv->get_user();
2397 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $digest = extract_param
($param, 'digest');
2403 my $disk = extract_param
($param, 'disk');
2405 my $storeid = extract_param
($param, 'storage');
2407 my $format = extract_param
($param, 'format');
2409 my $storecfg = PVE
::Storage
::config
();
2411 my $updatefn = sub {
2413 my $conf = PVE
::QemuServer
::load_config
($vmid);
2415 die "checksum missmatch (file change by other user?)\n"
2416 if $digest && $digest ne $conf->{digest
};
2418 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2420 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2422 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2424 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2427 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2428 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2432 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2433 (!$format || !$oldfmt || $oldfmt eq $format);
2435 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2437 my $running = PVE
::QemuServer
::check_running
($vmid);
2439 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2443 my $newvollist = [];
2446 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2448 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2449 $vmid, $storeid, $format, 1, $newvollist);
2451 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2453 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2455 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2458 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2459 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2466 foreach my $volid (@$newvollist) {
2467 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2470 die "storage migration failed: $err";
2473 if ($param->{delete}) {
2474 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2475 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2476 if ($used_paths->{$path}){
2477 warn "volume $old_volid have snapshots. Can't delete it\n";
2478 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2479 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2481 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2487 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2490 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2493 __PACKAGE__-
>register_method({
2494 name
=> 'migrate_vm',
2495 path
=> '{vmid}/migrate',
2499 description
=> "Migrate virtual machine. Creates a new migration task.",
2501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2504 additionalProperties
=> 0,
2506 node
=> get_standard_option
('pve-node'),
2507 vmid
=> get_standard_option
('pve-vmid'),
2508 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2511 description
=> "Use online/live migration.",
2516 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2523 description
=> "the task ID.",
2528 my $rpcenv = PVE
::RPCEnvironment
::get
();
2530 my $authuser = $rpcenv->get_user();
2532 my $target = extract_param
($param, 'target');
2534 my $localnode = PVE
::INotify
::nodename
();
2535 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2537 PVE
::Cluster
::check_cfs_quorum
();
2539 PVE
::Cluster
::check_node_exists
($target);
2541 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2543 my $vmid = extract_param
($param, 'vmid');
2545 raise_param_exc
({ force
=> "Only root may use this option." })
2546 if $param->{force
} && $authuser ne 'root@pam';
2549 my $conf = PVE
::QemuServer
::load_config
($vmid);
2551 # try to detect errors early
2553 PVE
::QemuServer
::check_lock
($conf);
2555 if (PVE
::QemuServer
::check_running
($vmid)) {
2556 die "cant migrate running VM without --online\n"
2557 if !$param->{online
};
2560 my $storecfg = PVE
::Storage
::config
();
2561 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2563 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2568 my $service = "pvevm:$vmid";
2570 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2572 print "Executing HA migrate for VM $vmid to node $target\n";
2574 PVE
::Tools
::run_command
($cmd);
2579 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2586 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2589 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2594 __PACKAGE__-
>register_method({
2596 path
=> '{vmid}/monitor',
2600 description
=> "Execute Qemu monitor commands.",
2602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2605 additionalProperties
=> 0,
2607 node
=> get_standard_option
('pve-node'),
2608 vmid
=> get_standard_option
('pve-vmid'),
2611 description
=> "The monitor command.",
2615 returns
=> { type
=> 'string'},
2619 my $vmid = $param->{vmid
};
2621 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2625 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2627 $res = "ERROR: $@" if $@;
2632 __PACKAGE__-
>register_method({
2633 name
=> 'resize_vm',
2634 path
=> '{vmid}/resize',
2638 description
=> "Extend volume size.",
2640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2643 additionalProperties
=> 0,
2645 node
=> get_standard_option
('pve-node'),
2646 vmid
=> get_standard_option
('pve-vmid'),
2647 skiplock
=> get_standard_option
('skiplock'),
2650 description
=> "The disk you want to resize.",
2651 enum
=> [PVE
::QemuServer
::disknames
()],
2655 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2656 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.",
2660 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2666 returns
=> { type
=> 'null'},
2670 my $rpcenv = PVE
::RPCEnvironment
::get
();
2672 my $authuser = $rpcenv->get_user();
2674 my $node = extract_param
($param, 'node');
2676 my $vmid = extract_param
($param, 'vmid');
2678 my $digest = extract_param
($param, 'digest');
2680 my $disk = extract_param
($param, 'disk');
2682 my $sizestr = extract_param
($param, 'size');
2684 my $skiplock = extract_param
($param, 'skiplock');
2685 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2686 if $skiplock && $authuser ne 'root@pam';
2688 my $storecfg = PVE
::Storage
::config
();
2690 my $updatefn = sub {
2692 my $conf = PVE
::QemuServer
::load_config
($vmid);
2694 die "checksum missmatch (file change by other user?)\n"
2695 if $digest && $digest ne $conf->{digest
};
2696 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2698 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2700 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2702 my $volid = $drive->{file
};
2704 die "disk '$disk' has no associated volume\n" if !$volid;
2706 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2708 die "you can't online resize a virtio windows bootdisk\n"
2709 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2711 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2713 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2715 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2717 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2718 my ($ext, $newsize, $unit) = ($1, $2, $4);
2721 $newsize = $newsize * 1024;
2722 } elsif ($unit eq 'M') {
2723 $newsize = $newsize * 1024 * 1024;
2724 } elsif ($unit eq 'G') {
2725 $newsize = $newsize * 1024 * 1024 * 1024;
2726 } elsif ($unit eq 'T') {
2727 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2730 $newsize += $size if $ext;
2731 $newsize = int($newsize);
2733 die "unable to skrink disk size\n" if $newsize < $size;
2735 return if $size == $newsize;
2737 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2739 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2741 $drive->{size
} = $newsize;
2742 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2744 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2747 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2751 __PACKAGE__-
>register_method({
2752 name
=> 'snapshot_list',
2753 path
=> '{vmid}/snapshot',
2755 description
=> "List all snapshots.",
2757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2760 protected
=> 1, # qemu pid files are only readable by root
2762 additionalProperties
=> 0,
2764 vmid
=> get_standard_option
('pve-vmid'),
2765 node
=> get_standard_option
('pve-node'),
2774 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2779 my $vmid = $param->{vmid
};
2781 my $conf = PVE
::QemuServer
::load_config
($vmid);
2782 my $snaphash = $conf->{snapshots
} || {};
2786 foreach my $name (keys %$snaphash) {
2787 my $d = $snaphash->{$name};
2790 snaptime
=> $d->{snaptime
} || 0,
2791 vmstate
=> $d->{vmstate
} ?
1 : 0,
2792 description
=> $d->{description
} || '',
2794 $item->{parent
} = $d->{parent
} if $d->{parent
};
2795 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2799 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2800 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2801 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2803 push @$res, $current;
2808 __PACKAGE__-
>register_method({
2810 path
=> '{vmid}/snapshot',
2814 description
=> "Snapshot a VM.",
2816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2819 additionalProperties
=> 0,
2821 node
=> get_standard_option
('pve-node'),
2822 vmid
=> get_standard_option
('pve-vmid'),
2823 snapname
=> get_standard_option
('pve-snapshot-name'),
2827 description
=> "Save the vmstate",
2832 description
=> "Freeze the filesystem",
2837 description
=> "A textual description or comment.",
2843 description
=> "the task ID.",
2848 my $rpcenv = PVE
::RPCEnvironment
::get
();
2850 my $authuser = $rpcenv->get_user();
2852 my $node = extract_param
($param, 'node');
2854 my $vmid = extract_param
($param, 'vmid');
2856 my $snapname = extract_param
($param, 'snapname');
2858 die "unable to use snapshot name 'current' (reserved name)\n"
2859 if $snapname eq 'current';
2862 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2863 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2864 $param->{freezefs
}, $param->{description
});
2867 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2870 __PACKAGE__-
>register_method({
2871 name
=> 'snapshot_cmd_idx',
2872 path
=> '{vmid}/snapshot/{snapname}',
2879 additionalProperties
=> 0,
2881 vmid
=> get_standard_option
('pve-vmid'),
2882 node
=> get_standard_option
('pve-node'),
2883 snapname
=> get_standard_option
('pve-snapshot-name'),
2892 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2899 push @$res, { cmd
=> 'rollback' };
2900 push @$res, { cmd
=> 'config' };
2905 __PACKAGE__-
>register_method({
2906 name
=> 'update_snapshot_config',
2907 path
=> '{vmid}/snapshot/{snapname}/config',
2911 description
=> "Update snapshot metadata.",
2913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2916 additionalProperties
=> 0,
2918 node
=> get_standard_option
('pve-node'),
2919 vmid
=> get_standard_option
('pve-vmid'),
2920 snapname
=> get_standard_option
('pve-snapshot-name'),
2924 description
=> "A textual description or comment.",
2928 returns
=> { type
=> 'null' },
2932 my $rpcenv = PVE
::RPCEnvironment
::get
();
2934 my $authuser = $rpcenv->get_user();
2936 my $vmid = extract_param
($param, 'vmid');
2938 my $snapname = extract_param
($param, 'snapname');
2940 return undef if !defined($param->{description
});
2942 my $updatefn = sub {
2944 my $conf = PVE
::QemuServer
::load_config
($vmid);
2946 PVE
::QemuServer
::check_lock
($conf);
2948 my $snap = $conf->{snapshots
}->{$snapname};
2950 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2952 $snap->{description
} = $param->{description
} if defined($param->{description
});
2954 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2957 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2962 __PACKAGE__-
>register_method({
2963 name
=> 'get_snapshot_config',
2964 path
=> '{vmid}/snapshot/{snapname}/config',
2967 description
=> "Get snapshot configuration",
2969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2972 additionalProperties
=> 0,
2974 node
=> get_standard_option
('pve-node'),
2975 vmid
=> get_standard_option
('pve-vmid'),
2976 snapname
=> get_standard_option
('pve-snapshot-name'),
2979 returns
=> { type
=> "object" },
2983 my $rpcenv = PVE
::RPCEnvironment
::get
();
2985 my $authuser = $rpcenv->get_user();
2987 my $vmid = extract_param
($param, 'vmid');
2989 my $snapname = extract_param
($param, 'snapname');
2991 my $conf = PVE
::QemuServer
::load_config
($vmid);
2993 my $snap = $conf->{snapshots
}->{$snapname};
2995 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3000 __PACKAGE__-
>register_method({
3002 path
=> '{vmid}/snapshot/{snapname}/rollback',
3006 description
=> "Rollback VM state to specified snapshot.",
3008 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3011 additionalProperties
=> 0,
3013 node
=> get_standard_option
('pve-node'),
3014 vmid
=> get_standard_option
('pve-vmid'),
3015 snapname
=> get_standard_option
('pve-snapshot-name'),
3020 description
=> "the task ID.",
3025 my $rpcenv = PVE
::RPCEnvironment
::get
();
3027 my $authuser = $rpcenv->get_user();
3029 my $node = extract_param
($param, 'node');
3031 my $vmid = extract_param
($param, 'vmid');
3033 my $snapname = extract_param
($param, 'snapname');
3036 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3037 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3040 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3043 __PACKAGE__-
>register_method({
3044 name
=> 'delsnapshot',
3045 path
=> '{vmid}/snapshot/{snapname}',
3049 description
=> "Delete a VM snapshot.",
3051 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3054 additionalProperties
=> 0,
3056 node
=> get_standard_option
('pve-node'),
3057 vmid
=> get_standard_option
('pve-vmid'),
3058 snapname
=> get_standard_option
('pve-snapshot-name'),
3062 description
=> "For removal from config file, even if removing disk snapshots fails.",
3068 description
=> "the task ID.",
3073 my $rpcenv = PVE
::RPCEnvironment
::get
();
3075 my $authuser = $rpcenv->get_user();
3077 my $node = extract_param
($param, 'node');
3079 my $vmid = extract_param
($param, 'vmid');
3081 my $snapname = extract_param
($param, 'snapname');
3084 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3085 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3088 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3091 __PACKAGE__-
>register_method({
3093 path
=> '{vmid}/template',
3097 description
=> "Create a Template.",
3099 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3100 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3103 additionalProperties
=> 0,
3105 node
=> get_standard_option
('pve-node'),
3106 vmid
=> get_standard_option
('pve-vmid'),
3110 description
=> "If you want to convert only 1 disk to base image.",
3111 enum
=> [PVE
::QemuServer
::disknames
()],
3116 returns
=> { type
=> 'null'},
3120 my $rpcenv = PVE
::RPCEnvironment
::get
();
3122 my $authuser = $rpcenv->get_user();
3124 my $node = extract_param
($param, 'node');
3126 my $vmid = extract_param
($param, 'vmid');
3128 my $disk = extract_param
($param, 'disk');
3130 my $updatefn = sub {
3132 my $conf = PVE
::QemuServer
::load_config
($vmid);
3134 PVE
::QemuServer
::check_lock
($conf);
3136 die "unable to create template, because VM contains snapshots\n"
3137 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3139 die "you can't convert a template to a template\n"
3140 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3142 die "you can't convert a VM to template if VM is running\n"
3143 if PVE
::QemuServer
::check_running
($vmid);
3146 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3149 $conf->{template
} = 1;
3150 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3152 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3155 PVE
::QemuServer
::lock_config
($vmid, $updatefn);