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',
1375 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1377 description
=> "Opens a weksocket for VNV traffic.",
1379 additionalProperties
=> 0,
1381 node
=> get_standard_option
('pve-node'),
1382 vmid
=> get_standard_option
('pve-vmid'),
1384 description
=> "Port number returned by previous vncproxy call.",
1394 port
=> { type
=> 'string' },
1400 my $rpcenv = PVE
::RPCEnvironment
::get
();
1402 my $authuser = $rpcenv->get_user();
1404 my $vmid = $param->{vmid
};
1405 my $node = $param->{node
};
1407 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1409 # Note: VNC ports are acessible from outside, so we do not gain any
1410 # security if we verify that $param->{port} belongs to VM $vmid. This
1411 # check is done by verifying the VNC ticket (inside VNC protocol).
1413 my $port = $param->{port
};
1415 return { port
=> $port };
1418 __PACKAGE__-
>register_method({
1419 name
=> 'spiceproxy',
1420 path
=> '{vmid}/spiceproxy',
1425 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1427 description
=> "Returns a SPICE configuration to connect to the VM.",
1429 additionalProperties
=> 0,
1431 node
=> get_standard_option
('pve-node'),
1432 vmid
=> get_standard_option
('pve-vmid'),
1433 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1436 returns
=> get_standard_option
('remote-viewer-config'),
1440 my $rpcenv = PVE
::RPCEnvironment
::get
();
1442 my $authuser = $rpcenv->get_user();
1444 my $vmid = $param->{vmid
};
1445 my $node = $param->{node
};
1446 my $proxy = $param->{proxy
};
1448 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1449 my $title = "VM $vmid - $conf->{'name'}",
1451 my $port = PVE
::QemuServer
::spice_port
($vmid);
1453 my ($ticket, undef, $remote_viewer_config) =
1454 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1456 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1457 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1459 return $remote_viewer_config;
1462 __PACKAGE__-
>register_method({
1464 path
=> '{vmid}/status',
1467 description
=> "Directory index",
1472 additionalProperties
=> 0,
1474 node
=> get_standard_option
('pve-node'),
1475 vmid
=> get_standard_option
('pve-vmid'),
1483 subdir
=> { type
=> 'string' },
1486 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1492 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1495 { subdir
=> 'current' },
1496 { subdir
=> 'start' },
1497 { subdir
=> 'stop' },
1503 my $vm_is_ha_managed = sub {
1506 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1507 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1513 __PACKAGE__-
>register_method({
1514 name
=> 'vm_status',
1515 path
=> '{vmid}/status/current',
1518 protected
=> 1, # qemu pid files are only readable by root
1519 description
=> "Get virtual machine status.",
1521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1524 additionalProperties
=> 0,
1526 node
=> get_standard_option
('pve-node'),
1527 vmid
=> get_standard_option
('pve-vmid'),
1530 returns
=> { type
=> 'object' },
1535 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1537 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1538 my $status = $vmstatus->{$param->{vmid
}};
1540 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1542 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1547 __PACKAGE__-
>register_method({
1549 path
=> '{vmid}/status/start',
1553 description
=> "Start virtual machine.",
1555 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1558 additionalProperties
=> 0,
1560 node
=> get_standard_option
('pve-node'),
1561 vmid
=> get_standard_option
('pve-vmid'),
1562 skiplock
=> get_standard_option
('skiplock'),
1563 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1564 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1565 machine
=> get_standard_option
('pve-qm-machine'),
1574 my $rpcenv = PVE
::RPCEnvironment
::get
();
1576 my $authuser = $rpcenv->get_user();
1578 my $node = extract_param
($param, 'node');
1580 my $vmid = extract_param
($param, 'vmid');
1582 my $machine = extract_param
($param, 'machine');
1584 my $stateuri = extract_param
($param, 'stateuri');
1585 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1586 if $stateuri && $authuser ne 'root@pam';
1588 my $skiplock = extract_param
($param, 'skiplock');
1589 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1590 if $skiplock && $authuser ne 'root@pam';
1592 my $migratedfrom = extract_param
($param, 'migratedfrom');
1593 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1594 if $migratedfrom && $authuser ne 'root@pam';
1596 # read spice ticket from STDIN
1598 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1599 if (defined(my $line = <>)) {
1601 $spice_ticket = $line;
1605 my $storecfg = PVE
::Storage
::config
();
1607 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1608 $rpcenv->{type
} ne 'ha') {
1613 my $service = "pvevm:$vmid";
1615 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1617 print "Executing HA start for VM $vmid\n";
1619 PVE
::Tools
::run_command
($cmd);
1624 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1631 syslog
('info', "start VM $vmid: $upid\n");
1633 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1634 $machine, $spice_ticket);
1639 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1643 __PACKAGE__-
>register_method({
1645 path
=> '{vmid}/status/stop',
1649 description
=> "Stop virtual machine.",
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid'),
1658 skiplock
=> get_standard_option
('skiplock'),
1659 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1661 description
=> "Wait maximal timeout seconds.",
1667 description
=> "Do not decativate storage volumes.",
1680 my $rpcenv = PVE
::RPCEnvironment
::get
();
1682 my $authuser = $rpcenv->get_user();
1684 my $node = extract_param
($param, 'node');
1686 my $vmid = extract_param
($param, 'vmid');
1688 my $skiplock = extract_param
($param, 'skiplock');
1689 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1690 if $skiplock && $authuser ne 'root@pam';
1692 my $keepActive = extract_param
($param, 'keepActive');
1693 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1694 if $keepActive && $authuser ne 'root@pam';
1696 my $migratedfrom = extract_param
($param, 'migratedfrom');
1697 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1698 if $migratedfrom && $authuser ne 'root@pam';
1701 my $storecfg = PVE
::Storage
::config
();
1703 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1708 my $service = "pvevm:$vmid";
1710 my $cmd = ['clusvcadm', '-d', $service];
1712 print "Executing HA stop for VM $vmid\n";
1714 PVE
::Tools
::run_command
($cmd);
1719 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1725 syslog
('info', "stop VM $vmid: $upid\n");
1727 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1728 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1733 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1737 __PACKAGE__-
>register_method({
1739 path
=> '{vmid}/status/reset',
1743 description
=> "Reset virtual machine.",
1745 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1748 additionalProperties
=> 0,
1750 node
=> get_standard_option
('pve-node'),
1751 vmid
=> get_standard_option
('pve-vmid'),
1752 skiplock
=> get_standard_option
('skiplock'),
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $node = extract_param
($param, 'node');
1767 my $vmid = extract_param
($param, 'vmid');
1769 my $skiplock = extract_param
($param, 'skiplock');
1770 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1771 if $skiplock && $authuser ne 'root@pam';
1773 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1778 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1783 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1786 __PACKAGE__-
>register_method({
1787 name
=> 'vm_shutdown',
1788 path
=> '{vmid}/status/shutdown',
1792 description
=> "Shutdown virtual machine.",
1794 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1797 additionalProperties
=> 0,
1799 node
=> get_standard_option
('pve-node'),
1800 vmid
=> get_standard_option
('pve-vmid'),
1801 skiplock
=> get_standard_option
('skiplock'),
1803 description
=> "Wait maximal timeout seconds.",
1809 description
=> "Make sure the VM stops.",
1815 description
=> "Do not decativate storage volumes.",
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $node = extract_param
($param, 'node');
1834 my $vmid = extract_param
($param, 'vmid');
1836 my $skiplock = extract_param
($param, 'skiplock');
1837 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1838 if $skiplock && $authuser ne 'root@pam';
1840 my $keepActive = extract_param
($param, 'keepActive');
1841 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1842 if $keepActive && $authuser ne 'root@pam';
1844 my $storecfg = PVE
::Storage
::config
();
1849 syslog
('info', "shutdown VM $vmid: $upid\n");
1851 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1852 1, $param->{forceStop
}, $keepActive);
1857 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1860 __PACKAGE__-
>register_method({
1861 name
=> 'vm_suspend',
1862 path
=> '{vmid}/status/suspend',
1866 description
=> "Suspend virtual machine.",
1868 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1871 additionalProperties
=> 0,
1873 node
=> get_standard_option
('pve-node'),
1874 vmid
=> get_standard_option
('pve-vmid'),
1875 skiplock
=> get_standard_option
('skiplock'),
1884 my $rpcenv = PVE
::RPCEnvironment
::get
();
1886 my $authuser = $rpcenv->get_user();
1888 my $node = extract_param
($param, 'node');
1890 my $vmid = extract_param
($param, 'vmid');
1892 my $skiplock = extract_param
($param, 'skiplock');
1893 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1894 if $skiplock && $authuser ne 'root@pam';
1896 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1901 syslog
('info', "suspend VM $vmid: $upid\n");
1903 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1908 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1911 __PACKAGE__-
>register_method({
1912 name
=> 'vm_resume',
1913 path
=> '{vmid}/status/resume',
1917 description
=> "Resume virtual machine.",
1919 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1922 additionalProperties
=> 0,
1924 node
=> get_standard_option
('pve-node'),
1925 vmid
=> get_standard_option
('pve-vmid'),
1926 skiplock
=> get_standard_option
('skiplock'),
1935 my $rpcenv = PVE
::RPCEnvironment
::get
();
1937 my $authuser = $rpcenv->get_user();
1939 my $node = extract_param
($param, 'node');
1941 my $vmid = extract_param
($param, 'vmid');
1943 my $skiplock = extract_param
($param, 'skiplock');
1944 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1945 if $skiplock && $authuser ne 'root@pam';
1947 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1952 syslog
('info', "resume VM $vmid: $upid\n");
1954 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1959 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1962 __PACKAGE__-
>register_method({
1963 name
=> 'vm_sendkey',
1964 path
=> '{vmid}/sendkey',
1968 description
=> "Send key event to virtual machine.",
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1973 additionalProperties
=> 0,
1975 node
=> get_standard_option
('pve-node'),
1976 vmid
=> get_standard_option
('pve-vmid'),
1977 skiplock
=> get_standard_option
('skiplock'),
1979 description
=> "The key (qemu monitor encoding).",
1984 returns
=> { type
=> 'null'},
1988 my $rpcenv = PVE
::RPCEnvironment
::get
();
1990 my $authuser = $rpcenv->get_user();
1992 my $node = extract_param
($param, 'node');
1994 my $vmid = extract_param
($param, 'vmid');
1996 my $skiplock = extract_param
($param, 'skiplock');
1997 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1998 if $skiplock && $authuser ne 'root@pam';
2000 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2005 __PACKAGE__-
>register_method({
2006 name
=> 'vm_feature',
2007 path
=> '{vmid}/feature',
2011 description
=> "Check if feature for virtual machine is available.",
2013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2016 additionalProperties
=> 0,
2018 node
=> get_standard_option
('pve-node'),
2019 vmid
=> get_standard_option
('pve-vmid'),
2021 description
=> "Feature to check.",
2023 enum
=> [ 'snapshot', 'clone', 'copy' ],
2025 snapname
=> get_standard_option
('pve-snapshot-name', {
2033 hasFeature
=> { type
=> 'boolean' },
2036 items
=> { type
=> 'string' },
2043 my $node = extract_param
($param, 'node');
2045 my $vmid = extract_param
($param, 'vmid');
2047 my $snapname = extract_param
($param, 'snapname');
2049 my $feature = extract_param
($param, 'feature');
2051 my $running = PVE
::QemuServer
::check_running
($vmid);
2053 my $conf = PVE
::QemuServer
::load_config
($vmid);
2056 my $snap = $conf->{snapshots
}->{$snapname};
2057 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2060 my $storecfg = PVE
::Storage
::config
();
2062 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2063 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2066 hasFeature
=> $hasFeature,
2067 nodes
=> [ keys %$nodelist ],
2071 __PACKAGE__-
>register_method({
2073 path
=> '{vmid}/clone',
2077 description
=> "Create a copy of virtual machine/template.",
2079 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2080 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2081 "'Datastore.AllocateSpace' on any used storage.",
2084 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2086 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2087 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2092 additionalProperties
=> 0,
2094 node
=> get_standard_option
('pve-node'),
2095 vmid
=> get_standard_option
('pve-vmid'),
2096 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2099 type
=> 'string', format
=> 'dns-name',
2100 description
=> "Set a name for the new VM.",
2105 description
=> "Description for the new VM.",
2109 type
=> 'string', format
=> 'pve-poolid',
2110 description
=> "Add the new VM to the specified pool.",
2112 snapname
=> get_standard_option
('pve-snapshot-name', {
2116 storage
=> get_standard_option
('pve-storage-id', {
2117 description
=> "Target storage for full clone.",
2122 description
=> "Target format for file storage.",
2126 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2131 description
=> "Create a full copy of all disk. This is always done when " .
2132 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2135 target
=> get_standard_option
('pve-node', {
2136 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2147 my $rpcenv = PVE
::RPCEnvironment
::get
();
2149 my $authuser = $rpcenv->get_user();
2151 my $node = extract_param
($param, 'node');
2153 my $vmid = extract_param
($param, 'vmid');
2155 my $newid = extract_param
($param, 'newid');
2157 my $pool = extract_param
($param, 'pool');
2159 if (defined($pool)) {
2160 $rpcenv->check_pool_exist($pool);
2163 my $snapname = extract_param
($param, 'snapname');
2165 my $storage = extract_param
($param, 'storage');
2167 my $format = extract_param
($param, 'format');
2169 my $target = extract_param
($param, 'target');
2171 my $localnode = PVE
::INotify
::nodename
();
2173 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2175 PVE
::Cluster
::check_node_exists
($target) if $target;
2177 my $storecfg = PVE
::Storage
::config
();
2180 # check if storage is enabled on local node
2181 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2183 # check if storage is available on target node
2184 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2185 # clone only works if target storage is shared
2186 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2187 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2191 PVE
::Cluster
::check_cfs_quorum
();
2193 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2195 # exclusive lock if VM is running - else shared lock is enough;
2196 my $shared_lock = $running ?
0 : 1;
2200 # do all tests after lock
2201 # we also try to do all tests before we fork the worker
2203 my $conf = PVE
::QemuServer
::load_config
($vmid);
2205 PVE
::QemuServer
::check_lock
($conf);
2207 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2209 die "unexpected state change\n" if $verify_running != $running;
2211 die "snapshot '$snapname' does not exist\n"
2212 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2214 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2216 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2218 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2220 my $conffile = PVE
::QemuServer
::config_file
($newid);
2222 die "unable to create VM $newid: config file already exists\n"
2225 my $newconf = { lock => 'clone' };
2229 foreach my $opt (keys %$oldconf) {
2230 my $value = $oldconf->{$opt};
2232 # do not copy snapshot related info
2233 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2234 $opt eq 'vmstate' || $opt eq 'snapstate';
2236 # always change MAC! address
2237 if ($opt =~ m/^net(\d+)$/) {
2238 my $net = PVE
::QemuServer
::parse_net
($value);
2239 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2240 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2241 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2242 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2243 die "unable to parse drive options for '$opt'\n" if !$drive;
2244 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2245 $newconf->{$opt} = $value; # simply copy configuration
2247 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2248 die "Full clone feature is not available"
2249 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2252 $drives->{$opt} = $drive;
2253 push @$vollist, $drive->{file
};
2256 # copy everything else
2257 $newconf->{$opt} = $value;
2261 delete $newconf->{template
};
2263 if ($param->{name
}) {
2264 $newconf->{name
} = $param->{name
};
2266 if ($oldconf->{name
}) {
2267 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2269 $newconf->{name
} = "Copy-of-VM-$vmid";
2273 if ($param->{description
}) {
2274 $newconf->{description
} = $param->{description
};
2277 # create empty/temp config - this fails if VM already exists on other node
2278 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2283 my $newvollist = [];
2286 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2288 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2290 foreach my $opt (keys %$drives) {
2291 my $drive = $drives->{$opt};
2293 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2294 $newid, $storage, $format, $drive->{full
}, $newvollist);
2296 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2298 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2301 delete $newconf->{lock};
2302 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2305 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2306 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2308 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2309 die "Failed to move config to node '$target' - rename failed: $!\n"
2310 if !rename($conffile, $newconffile);
2313 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2318 sleep 1; # some storage like rbd need to wait before release volume - really?
2320 foreach my $volid (@$newvollist) {
2321 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2324 die "clone failed: $err";
2330 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2333 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2334 # Aquire exclusive lock lock for $newid
2335 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2340 __PACKAGE__-
>register_method({
2341 name
=> 'move_vm_disk',
2342 path
=> '{vmid}/move_disk',
2346 description
=> "Move volume to different storage.",
2348 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2349 "and 'Datastore.AllocateSpace' permissions on the storage.",
2352 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2353 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2357 additionalProperties
=> 0,
2359 node
=> get_standard_option
('pve-node'),
2360 vmid
=> get_standard_option
('pve-vmid'),
2363 description
=> "The disk you want to move.",
2364 enum
=> [ PVE
::QemuServer
::disknames
() ],
2366 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2369 description
=> "Target Format.",
2370 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2375 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2381 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2389 description
=> "the task ID.",
2394 my $rpcenv = PVE
::RPCEnvironment
::get
();
2396 my $authuser = $rpcenv->get_user();
2398 my $node = extract_param
($param, 'node');
2400 my $vmid = extract_param
($param, 'vmid');
2402 my $digest = extract_param
($param, 'digest');
2404 my $disk = extract_param
($param, 'disk');
2406 my $storeid = extract_param
($param, 'storage');
2408 my $format = extract_param
($param, 'format');
2410 my $storecfg = PVE
::Storage
::config
();
2412 my $updatefn = sub {
2414 my $conf = PVE
::QemuServer
::load_config
($vmid);
2416 die "checksum missmatch (file change by other user?)\n"
2417 if $digest && $digest ne $conf->{digest
};
2419 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2421 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2423 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2425 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2428 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2429 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2433 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2434 (!$format || !$oldfmt || $oldfmt eq $format);
2436 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2438 my $running = PVE
::QemuServer
::check_running
($vmid);
2440 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2444 my $newvollist = [];
2447 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2449 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2450 $vmid, $storeid, $format, 1, $newvollist);
2452 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2454 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2456 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2459 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2460 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2467 foreach my $volid (@$newvollist) {
2468 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2471 die "storage migration failed: $err";
2474 if ($param->{delete}) {
2475 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2476 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2477 if ($used_paths->{$path}){
2478 warn "volume $old_volid have snapshots. Can't delete it\n";
2479 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2480 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2482 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2488 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2491 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2494 __PACKAGE__-
>register_method({
2495 name
=> 'migrate_vm',
2496 path
=> '{vmid}/migrate',
2500 description
=> "Migrate virtual machine. Creates a new migration task.",
2502 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2505 additionalProperties
=> 0,
2507 node
=> get_standard_option
('pve-node'),
2508 vmid
=> get_standard_option
('pve-vmid'),
2509 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2512 description
=> "Use online/live migration.",
2517 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2524 description
=> "the task ID.",
2529 my $rpcenv = PVE
::RPCEnvironment
::get
();
2531 my $authuser = $rpcenv->get_user();
2533 my $target = extract_param
($param, 'target');
2535 my $localnode = PVE
::INotify
::nodename
();
2536 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2538 PVE
::Cluster
::check_cfs_quorum
();
2540 PVE
::Cluster
::check_node_exists
($target);
2542 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2544 my $vmid = extract_param
($param, 'vmid');
2546 raise_param_exc
({ force
=> "Only root may use this option." })
2547 if $param->{force
} && $authuser ne 'root@pam';
2550 my $conf = PVE
::QemuServer
::load_config
($vmid);
2552 # try to detect errors early
2554 PVE
::QemuServer
::check_lock
($conf);
2556 if (PVE
::QemuServer
::check_running
($vmid)) {
2557 die "cant migrate running VM without --online\n"
2558 if !$param->{online
};
2561 my $storecfg = PVE
::Storage
::config
();
2562 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2564 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2569 my $service = "pvevm:$vmid";
2571 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2573 print "Executing HA migrate for VM $vmid to node $target\n";
2575 PVE
::Tools
::run_command
($cmd);
2580 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2587 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2590 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2595 __PACKAGE__-
>register_method({
2597 path
=> '{vmid}/monitor',
2601 description
=> "Execute Qemu monitor commands.",
2603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid'),
2612 description
=> "The monitor command.",
2616 returns
=> { type
=> 'string'},
2620 my $vmid = $param->{vmid
};
2622 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2626 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2628 $res = "ERROR: $@" if $@;
2633 __PACKAGE__-
>register_method({
2634 name
=> 'resize_vm',
2635 path
=> '{vmid}/resize',
2639 description
=> "Extend volume size.",
2641 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2644 additionalProperties
=> 0,
2646 node
=> get_standard_option
('pve-node'),
2647 vmid
=> get_standard_option
('pve-vmid'),
2648 skiplock
=> get_standard_option
('skiplock'),
2651 description
=> "The disk you want to resize.",
2652 enum
=> [PVE
::QemuServer
::disknames
()],
2656 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2657 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.",
2661 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2667 returns
=> { type
=> 'null'},
2671 my $rpcenv = PVE
::RPCEnvironment
::get
();
2673 my $authuser = $rpcenv->get_user();
2675 my $node = extract_param
($param, 'node');
2677 my $vmid = extract_param
($param, 'vmid');
2679 my $digest = extract_param
($param, 'digest');
2681 my $disk = extract_param
($param, 'disk');
2683 my $sizestr = extract_param
($param, 'size');
2685 my $skiplock = extract_param
($param, 'skiplock');
2686 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2687 if $skiplock && $authuser ne 'root@pam';
2689 my $storecfg = PVE
::Storage
::config
();
2691 my $updatefn = sub {
2693 my $conf = PVE
::QemuServer
::load_config
($vmid);
2695 die "checksum missmatch (file change by other user?)\n"
2696 if $digest && $digest ne $conf->{digest
};
2697 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2699 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2701 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2703 my $volid = $drive->{file
};
2705 die "disk '$disk' has no associated volume\n" if !$volid;
2707 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2709 die "you can't online resize a virtio windows bootdisk\n"
2710 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2712 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2714 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2716 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2718 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2719 my ($ext, $newsize, $unit) = ($1, $2, $4);
2722 $newsize = $newsize * 1024;
2723 } elsif ($unit eq 'M') {
2724 $newsize = $newsize * 1024 * 1024;
2725 } elsif ($unit eq 'G') {
2726 $newsize = $newsize * 1024 * 1024 * 1024;
2727 } elsif ($unit eq 'T') {
2728 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2731 $newsize += $size if $ext;
2732 $newsize = int($newsize);
2734 die "unable to skrink disk size\n" if $newsize < $size;
2736 return if $size == $newsize;
2738 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2740 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2742 $drive->{size
} = $newsize;
2743 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2745 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2748 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2752 __PACKAGE__-
>register_method({
2753 name
=> 'snapshot_list',
2754 path
=> '{vmid}/snapshot',
2756 description
=> "List all snapshots.",
2758 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2761 protected
=> 1, # qemu pid files are only readable by root
2763 additionalProperties
=> 0,
2765 vmid
=> get_standard_option
('pve-vmid'),
2766 node
=> get_standard_option
('pve-node'),
2775 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2780 my $vmid = $param->{vmid
};
2782 my $conf = PVE
::QemuServer
::load_config
($vmid);
2783 my $snaphash = $conf->{snapshots
} || {};
2787 foreach my $name (keys %$snaphash) {
2788 my $d = $snaphash->{$name};
2791 snaptime
=> $d->{snaptime
} || 0,
2792 vmstate
=> $d->{vmstate
} ?
1 : 0,
2793 description
=> $d->{description
} || '',
2795 $item->{parent
} = $d->{parent
} if $d->{parent
};
2796 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2800 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2801 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2802 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2804 push @$res, $current;
2809 __PACKAGE__-
>register_method({
2811 path
=> '{vmid}/snapshot',
2815 description
=> "Snapshot a VM.",
2817 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2820 additionalProperties
=> 0,
2822 node
=> get_standard_option
('pve-node'),
2823 vmid
=> get_standard_option
('pve-vmid'),
2824 snapname
=> get_standard_option
('pve-snapshot-name'),
2828 description
=> "Save the vmstate",
2833 description
=> "Freeze the filesystem",
2838 description
=> "A textual description or comment.",
2844 description
=> "the task ID.",
2849 my $rpcenv = PVE
::RPCEnvironment
::get
();
2851 my $authuser = $rpcenv->get_user();
2853 my $node = extract_param
($param, 'node');
2855 my $vmid = extract_param
($param, 'vmid');
2857 my $snapname = extract_param
($param, 'snapname');
2859 die "unable to use snapshot name 'current' (reserved name)\n"
2860 if $snapname eq 'current';
2863 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2864 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2865 $param->{freezefs
}, $param->{description
});
2868 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2871 __PACKAGE__-
>register_method({
2872 name
=> 'snapshot_cmd_idx',
2873 path
=> '{vmid}/snapshot/{snapname}',
2880 additionalProperties
=> 0,
2882 vmid
=> get_standard_option
('pve-vmid'),
2883 node
=> get_standard_option
('pve-node'),
2884 snapname
=> get_standard_option
('pve-snapshot-name'),
2893 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2900 push @$res, { cmd
=> 'rollback' };
2901 push @$res, { cmd
=> 'config' };
2906 __PACKAGE__-
>register_method({
2907 name
=> 'update_snapshot_config',
2908 path
=> '{vmid}/snapshot/{snapname}/config',
2912 description
=> "Update snapshot metadata.",
2914 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2917 additionalProperties
=> 0,
2919 node
=> get_standard_option
('pve-node'),
2920 vmid
=> get_standard_option
('pve-vmid'),
2921 snapname
=> get_standard_option
('pve-snapshot-name'),
2925 description
=> "A textual description or comment.",
2929 returns
=> { type
=> 'null' },
2933 my $rpcenv = PVE
::RPCEnvironment
::get
();
2935 my $authuser = $rpcenv->get_user();
2937 my $vmid = extract_param
($param, 'vmid');
2939 my $snapname = extract_param
($param, 'snapname');
2941 return undef if !defined($param->{description
});
2943 my $updatefn = sub {
2945 my $conf = PVE
::QemuServer
::load_config
($vmid);
2947 PVE
::QemuServer
::check_lock
($conf);
2949 my $snap = $conf->{snapshots
}->{$snapname};
2951 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2953 $snap->{description
} = $param->{description
} if defined($param->{description
});
2955 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2958 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2963 __PACKAGE__-
>register_method({
2964 name
=> 'get_snapshot_config',
2965 path
=> '{vmid}/snapshot/{snapname}/config',
2968 description
=> "Get snapshot configuration",
2970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2973 additionalProperties
=> 0,
2975 node
=> get_standard_option
('pve-node'),
2976 vmid
=> get_standard_option
('pve-vmid'),
2977 snapname
=> get_standard_option
('pve-snapshot-name'),
2980 returns
=> { type
=> "object" },
2984 my $rpcenv = PVE
::RPCEnvironment
::get
();
2986 my $authuser = $rpcenv->get_user();
2988 my $vmid = extract_param
($param, 'vmid');
2990 my $snapname = extract_param
($param, 'snapname');
2992 my $conf = PVE
::QemuServer
::load_config
($vmid);
2994 my $snap = $conf->{snapshots
}->{$snapname};
2996 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3001 __PACKAGE__-
>register_method({
3003 path
=> '{vmid}/snapshot/{snapname}/rollback',
3007 description
=> "Rollback VM state to specified snapshot.",
3009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3012 additionalProperties
=> 0,
3014 node
=> get_standard_option
('pve-node'),
3015 vmid
=> get_standard_option
('pve-vmid'),
3016 snapname
=> get_standard_option
('pve-snapshot-name'),
3021 description
=> "the task ID.",
3026 my $rpcenv = PVE
::RPCEnvironment
::get
();
3028 my $authuser = $rpcenv->get_user();
3030 my $node = extract_param
($param, 'node');
3032 my $vmid = extract_param
($param, 'vmid');
3034 my $snapname = extract_param
($param, 'snapname');
3037 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3038 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3041 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3044 __PACKAGE__-
>register_method({
3045 name
=> 'delsnapshot',
3046 path
=> '{vmid}/snapshot/{snapname}',
3050 description
=> "Delete a VM snapshot.",
3052 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3055 additionalProperties
=> 0,
3057 node
=> get_standard_option
('pve-node'),
3058 vmid
=> get_standard_option
('pve-vmid'),
3059 snapname
=> get_standard_option
('pve-snapshot-name'),
3063 description
=> "For removal from config file, even if removing disk snapshots fails.",
3069 description
=> "the task ID.",
3074 my $rpcenv = PVE
::RPCEnvironment
::get
();
3076 my $authuser = $rpcenv->get_user();
3078 my $node = extract_param
($param, 'node');
3080 my $vmid = extract_param
($param, 'vmid');
3082 my $snapname = extract_param
($param, 'snapname');
3085 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3086 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3089 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3092 __PACKAGE__-
>register_method({
3094 path
=> '{vmid}/template',
3098 description
=> "Create a Template.",
3100 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3101 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3104 additionalProperties
=> 0,
3106 node
=> get_standard_option
('pve-node'),
3107 vmid
=> get_standard_option
('pve-vmid'),
3111 description
=> "If you want to convert only 1 disk to base image.",
3112 enum
=> [PVE
::QemuServer
::disknames
()],
3117 returns
=> { type
=> 'null'},
3121 my $rpcenv = PVE
::RPCEnvironment
::get
();
3123 my $authuser = $rpcenv->get_user();
3125 my $node = extract_param
($param, 'node');
3127 my $vmid = extract_param
($param, 'vmid');
3129 my $disk = extract_param
($param, 'disk');
3131 my $updatefn = sub {
3133 my $conf = PVE
::QemuServer
::load_config
($vmid);
3135 PVE
::QemuServer
::check_lock
($conf);
3137 die "unable to create template, because VM contains snapshots\n"
3138 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3140 die "you can't convert a template to a template\n"
3141 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3143 die "you can't convert a VM to template if VM is running\n"
3144 if PVE
::QemuServer
::check_running
($vmid);
3147 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3150 $conf->{template
} = 1;
3151 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3153 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3156 PVE
::QemuServer
::lock_config
($vmid, $updatefn);