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 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1353 PVE
::Tools
::run_command
($cmd);
1358 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1360 PVE
::Tools
::wait_for_vnc_port
($port);
1371 __PACKAGE__-
>register_method({
1372 name
=> 'vncwebsocket',
1373 path
=> '{vmid}/vncwebsocket',
1376 description
=> "You also need to pass a valid ticket (vncticket).",
1377 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1379 description
=> "Opens a weksocket for VNC traffic.",
1381 additionalProperties
=> 0,
1383 node
=> get_standard_option
('pve-node'),
1384 vmid
=> get_standard_option
('pve-vmid'),
1386 description
=> "Ticket from previous call to vncproxy.",
1391 description
=> "Port number returned by previous vncproxy call.",
1401 port
=> { type
=> 'string' },
1407 my $rpcenv = PVE
::RPCEnvironment
::get
();
1409 my $authuser = $rpcenv->get_user();
1411 my $vmid = $param->{vmid
};
1412 my $node = $param->{node
};
1414 my $authpath = "/vms/$vmid";
1416 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1418 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1420 # Note: VNC ports are acessible from outside, so we do not gain any
1421 # security if we verify that $param->{port} belongs to VM $vmid. This
1422 # check is done by verifying the VNC ticket (inside VNC protocol).
1424 my $port = $param->{port
};
1426 return { port
=> $port };
1429 __PACKAGE__-
>register_method({
1430 name
=> 'spiceproxy',
1431 path
=> '{vmid}/spiceproxy',
1436 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1438 description
=> "Returns a SPICE configuration to connect to the VM.",
1440 additionalProperties
=> 0,
1442 node
=> get_standard_option
('pve-node'),
1443 vmid
=> get_standard_option
('pve-vmid'),
1444 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1447 returns
=> get_standard_option
('remote-viewer-config'),
1451 my $rpcenv = PVE
::RPCEnvironment
::get
();
1453 my $authuser = $rpcenv->get_user();
1455 my $vmid = $param->{vmid
};
1456 my $node = $param->{node
};
1457 my $proxy = $param->{proxy
};
1459 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1460 my $title = "VM $vmid - $conf->{'name'}",
1462 my $port = PVE
::QemuServer
::spice_port
($vmid);
1464 my ($ticket, undef, $remote_viewer_config) =
1465 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1467 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1468 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1470 return $remote_viewer_config;
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/status',
1478 description
=> "Directory index",
1483 additionalProperties
=> 0,
1485 node
=> get_standard_option
('pve-node'),
1486 vmid
=> get_standard_option
('pve-vmid'),
1494 subdir
=> { type
=> 'string' },
1497 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1503 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1506 { subdir
=> 'current' },
1507 { subdir
=> 'start' },
1508 { subdir
=> 'stop' },
1514 my $vm_is_ha_managed = sub {
1517 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1518 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1524 __PACKAGE__-
>register_method({
1525 name
=> 'vm_status',
1526 path
=> '{vmid}/status/current',
1529 protected
=> 1, # qemu pid files are only readable by root
1530 description
=> "Get virtual machine status.",
1532 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1535 additionalProperties
=> 0,
1537 node
=> get_standard_option
('pve-node'),
1538 vmid
=> get_standard_option
('pve-vmid'),
1541 returns
=> { type
=> 'object' },
1546 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1548 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1549 my $status = $vmstatus->{$param->{vmid
}};
1551 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1553 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1558 __PACKAGE__-
>register_method({
1560 path
=> '{vmid}/status/start',
1564 description
=> "Start virtual machine.",
1566 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1573 skiplock
=> get_standard_option
('skiplock'),
1574 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1575 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1576 machine
=> get_standard_option
('pve-qm-machine'),
1585 my $rpcenv = PVE
::RPCEnvironment
::get
();
1587 my $authuser = $rpcenv->get_user();
1589 my $node = extract_param
($param, 'node');
1591 my $vmid = extract_param
($param, 'vmid');
1593 my $machine = extract_param
($param, 'machine');
1595 my $stateuri = extract_param
($param, 'stateuri');
1596 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1597 if $stateuri && $authuser ne 'root@pam';
1599 my $skiplock = extract_param
($param, 'skiplock');
1600 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1601 if $skiplock && $authuser ne 'root@pam';
1603 my $migratedfrom = extract_param
($param, 'migratedfrom');
1604 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1605 if $migratedfrom && $authuser ne 'root@pam';
1607 # read spice ticket from STDIN
1609 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1610 if (defined(my $line = <>)) {
1612 $spice_ticket = $line;
1616 my $storecfg = PVE
::Storage
::config
();
1618 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1619 $rpcenv->{type
} ne 'ha') {
1624 my $service = "pvevm:$vmid";
1626 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1628 print "Executing HA start for VM $vmid\n";
1630 PVE
::Tools
::run_command
($cmd);
1635 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1642 syslog
('info', "start VM $vmid: $upid\n");
1644 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1645 $machine, $spice_ticket);
1650 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1654 __PACKAGE__-
>register_method({
1656 path
=> '{vmid}/status/stop',
1660 description
=> "Stop virtual machine.",
1662 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1665 additionalProperties
=> 0,
1667 node
=> get_standard_option
('pve-node'),
1668 vmid
=> get_standard_option
('pve-vmid'),
1669 skiplock
=> get_standard_option
('skiplock'),
1670 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1672 description
=> "Wait maximal timeout seconds.",
1678 description
=> "Do not decativate storage volumes.",
1691 my $rpcenv = PVE
::RPCEnvironment
::get
();
1693 my $authuser = $rpcenv->get_user();
1695 my $node = extract_param
($param, 'node');
1697 my $vmid = extract_param
($param, 'vmid');
1699 my $skiplock = extract_param
($param, 'skiplock');
1700 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1701 if $skiplock && $authuser ne 'root@pam';
1703 my $keepActive = extract_param
($param, 'keepActive');
1704 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1705 if $keepActive && $authuser ne 'root@pam';
1707 my $migratedfrom = extract_param
($param, 'migratedfrom');
1708 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1709 if $migratedfrom && $authuser ne 'root@pam';
1712 my $storecfg = PVE
::Storage
::config
();
1714 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1719 my $service = "pvevm:$vmid";
1721 my $cmd = ['clusvcadm', '-d', $service];
1723 print "Executing HA stop for VM $vmid\n";
1725 PVE
::Tools
::run_command
($cmd);
1730 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1736 syslog
('info', "stop VM $vmid: $upid\n");
1738 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1739 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1744 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1748 __PACKAGE__-
>register_method({
1750 path
=> '{vmid}/status/reset',
1754 description
=> "Reset virtual machine.",
1756 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1759 additionalProperties
=> 0,
1761 node
=> get_standard_option
('pve-node'),
1762 vmid
=> get_standard_option
('pve-vmid'),
1763 skiplock
=> get_standard_option
('skiplock'),
1772 my $rpcenv = PVE
::RPCEnvironment
::get
();
1774 my $authuser = $rpcenv->get_user();
1776 my $node = extract_param
($param, 'node');
1778 my $vmid = extract_param
($param, 'vmid');
1780 my $skiplock = extract_param
($param, 'skiplock');
1781 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1782 if $skiplock && $authuser ne 'root@pam';
1784 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1789 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1794 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1797 __PACKAGE__-
>register_method({
1798 name
=> 'vm_shutdown',
1799 path
=> '{vmid}/status/shutdown',
1803 description
=> "Shutdown virtual machine.",
1805 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1808 additionalProperties
=> 0,
1810 node
=> get_standard_option
('pve-node'),
1811 vmid
=> get_standard_option
('pve-vmid'),
1812 skiplock
=> get_standard_option
('skiplock'),
1814 description
=> "Wait maximal timeout seconds.",
1820 description
=> "Make sure the VM stops.",
1826 description
=> "Do not decativate storage volumes.",
1839 my $rpcenv = PVE
::RPCEnvironment
::get
();
1841 my $authuser = $rpcenv->get_user();
1843 my $node = extract_param
($param, 'node');
1845 my $vmid = extract_param
($param, 'vmid');
1847 my $skiplock = extract_param
($param, 'skiplock');
1848 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1849 if $skiplock && $authuser ne 'root@pam';
1851 my $keepActive = extract_param
($param, 'keepActive');
1852 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1853 if $keepActive && $authuser ne 'root@pam';
1855 my $storecfg = PVE
::Storage
::config
();
1860 syslog
('info', "shutdown VM $vmid: $upid\n");
1862 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1863 1, $param->{forceStop
}, $keepActive);
1868 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1871 __PACKAGE__-
>register_method({
1872 name
=> 'vm_suspend',
1873 path
=> '{vmid}/status/suspend',
1877 description
=> "Suspend virtual machine.",
1879 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1882 additionalProperties
=> 0,
1884 node
=> get_standard_option
('pve-node'),
1885 vmid
=> get_standard_option
('pve-vmid'),
1886 skiplock
=> get_standard_option
('skiplock'),
1895 my $rpcenv = PVE
::RPCEnvironment
::get
();
1897 my $authuser = $rpcenv->get_user();
1899 my $node = extract_param
($param, 'node');
1901 my $vmid = extract_param
($param, 'vmid');
1903 my $skiplock = extract_param
($param, 'skiplock');
1904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1905 if $skiplock && $authuser ne 'root@pam';
1907 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1912 syslog
('info', "suspend VM $vmid: $upid\n");
1914 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1919 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1922 __PACKAGE__-
>register_method({
1923 name
=> 'vm_resume',
1924 path
=> '{vmid}/status/resume',
1928 description
=> "Resume virtual machine.",
1930 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1933 additionalProperties
=> 0,
1935 node
=> get_standard_option
('pve-node'),
1936 vmid
=> get_standard_option
('pve-vmid'),
1937 skiplock
=> get_standard_option
('skiplock'),
1946 my $rpcenv = PVE
::RPCEnvironment
::get
();
1948 my $authuser = $rpcenv->get_user();
1950 my $node = extract_param
($param, 'node');
1952 my $vmid = extract_param
($param, 'vmid');
1954 my $skiplock = extract_param
($param, 'skiplock');
1955 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1956 if $skiplock && $authuser ne 'root@pam';
1958 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1963 syslog
('info', "resume VM $vmid: $upid\n");
1965 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1970 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1973 __PACKAGE__-
>register_method({
1974 name
=> 'vm_sendkey',
1975 path
=> '{vmid}/sendkey',
1979 description
=> "Send key event to virtual machine.",
1981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1984 additionalProperties
=> 0,
1986 node
=> get_standard_option
('pve-node'),
1987 vmid
=> get_standard_option
('pve-vmid'),
1988 skiplock
=> get_standard_option
('skiplock'),
1990 description
=> "The key (qemu monitor encoding).",
1995 returns
=> { type
=> 'null'},
1999 my $rpcenv = PVE
::RPCEnvironment
::get
();
2001 my $authuser = $rpcenv->get_user();
2003 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $skiplock = extract_param
($param, 'skiplock');
2008 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2009 if $skiplock && $authuser ne 'root@pam';
2011 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2016 __PACKAGE__-
>register_method({
2017 name
=> 'vm_feature',
2018 path
=> '{vmid}/feature',
2022 description
=> "Check if feature for virtual machine is available.",
2024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2027 additionalProperties
=> 0,
2029 node
=> get_standard_option
('pve-node'),
2030 vmid
=> get_standard_option
('pve-vmid'),
2032 description
=> "Feature to check.",
2034 enum
=> [ 'snapshot', 'clone', 'copy' ],
2036 snapname
=> get_standard_option
('pve-snapshot-name', {
2044 hasFeature
=> { type
=> 'boolean' },
2047 items
=> { type
=> 'string' },
2054 my $node = extract_param
($param, 'node');
2056 my $vmid = extract_param
($param, 'vmid');
2058 my $snapname = extract_param
($param, 'snapname');
2060 my $feature = extract_param
($param, 'feature');
2062 my $running = PVE
::QemuServer
::check_running
($vmid);
2064 my $conf = PVE
::QemuServer
::load_config
($vmid);
2067 my $snap = $conf->{snapshots
}->{$snapname};
2068 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2071 my $storecfg = PVE
::Storage
::config
();
2073 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2074 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2077 hasFeature
=> $hasFeature,
2078 nodes
=> [ keys %$nodelist ],
2082 __PACKAGE__-
>register_method({
2084 path
=> '{vmid}/clone',
2088 description
=> "Create a copy of virtual machine/template.",
2090 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2091 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2092 "'Datastore.AllocateSpace' on any used storage.",
2095 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2097 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2098 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2103 additionalProperties
=> 0,
2105 node
=> get_standard_option
('pve-node'),
2106 vmid
=> get_standard_option
('pve-vmid'),
2107 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2110 type
=> 'string', format
=> 'dns-name',
2111 description
=> "Set a name for the new VM.",
2116 description
=> "Description for the new VM.",
2120 type
=> 'string', format
=> 'pve-poolid',
2121 description
=> "Add the new VM to the specified pool.",
2123 snapname
=> get_standard_option
('pve-snapshot-name', {
2127 storage
=> get_standard_option
('pve-storage-id', {
2128 description
=> "Target storage for full clone.",
2133 description
=> "Target format for file storage.",
2137 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2142 description
=> "Create a full copy of all disk. This is always done when " .
2143 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2146 target
=> get_standard_option
('pve-node', {
2147 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2158 my $rpcenv = PVE
::RPCEnvironment
::get
();
2160 my $authuser = $rpcenv->get_user();
2162 my $node = extract_param
($param, 'node');
2164 my $vmid = extract_param
($param, 'vmid');
2166 my $newid = extract_param
($param, 'newid');
2168 my $pool = extract_param
($param, 'pool');
2170 if (defined($pool)) {
2171 $rpcenv->check_pool_exist($pool);
2174 my $snapname = extract_param
($param, 'snapname');
2176 my $storage = extract_param
($param, 'storage');
2178 my $format = extract_param
($param, 'format');
2180 my $target = extract_param
($param, 'target');
2182 my $localnode = PVE
::INotify
::nodename
();
2184 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2186 PVE
::Cluster
::check_node_exists
($target) if $target;
2188 my $storecfg = PVE
::Storage
::config
();
2191 # check if storage is enabled on local node
2192 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2194 # check if storage is available on target node
2195 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2196 # clone only works if target storage is shared
2197 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2198 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2202 PVE
::Cluster
::check_cfs_quorum
();
2204 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2206 # exclusive lock if VM is running - else shared lock is enough;
2207 my $shared_lock = $running ?
0 : 1;
2211 # do all tests after lock
2212 # we also try to do all tests before we fork the worker
2214 my $conf = PVE
::QemuServer
::load_config
($vmid);
2216 PVE
::QemuServer
::check_lock
($conf);
2218 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2220 die "unexpected state change\n" if $verify_running != $running;
2222 die "snapshot '$snapname' does not exist\n"
2223 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2225 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2227 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2229 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2231 my $conffile = PVE
::QemuServer
::config_file
($newid);
2233 die "unable to create VM $newid: config file already exists\n"
2236 my $newconf = { lock => 'clone' };
2240 foreach my $opt (keys %$oldconf) {
2241 my $value = $oldconf->{$opt};
2243 # do not copy snapshot related info
2244 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2245 $opt eq 'vmstate' || $opt eq 'snapstate';
2247 # always change MAC! address
2248 if ($opt =~ m/^net(\d+)$/) {
2249 my $net = PVE
::QemuServer
::parse_net
($value);
2250 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2251 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2252 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2253 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2254 die "unable to parse drive options for '$opt'\n" if !$drive;
2255 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2256 $newconf->{$opt} = $value; # simply copy configuration
2258 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2259 die "Full clone feature is not available"
2260 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2263 $drives->{$opt} = $drive;
2264 push @$vollist, $drive->{file
};
2267 # copy everything else
2268 $newconf->{$opt} = $value;
2272 delete $newconf->{template
};
2274 if ($param->{name
}) {
2275 $newconf->{name
} = $param->{name
};
2277 if ($oldconf->{name
}) {
2278 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2280 $newconf->{name
} = "Copy-of-VM-$vmid";
2284 if ($param->{description
}) {
2285 $newconf->{description
} = $param->{description
};
2288 # create empty/temp config - this fails if VM already exists on other node
2289 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2294 my $newvollist = [];
2297 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2299 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2301 foreach my $opt (keys %$drives) {
2302 my $drive = $drives->{$opt};
2304 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2305 $newid, $storage, $format, $drive->{full
}, $newvollist);
2307 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2309 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2312 delete $newconf->{lock};
2313 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2316 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2317 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2319 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2320 die "Failed to move config to node '$target' - rename failed: $!\n"
2321 if !rename($conffile, $newconffile);
2324 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2329 sleep 1; # some storage like rbd need to wait before release volume - really?
2331 foreach my $volid (@$newvollist) {
2332 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2335 die "clone failed: $err";
2341 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2344 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2345 # Aquire exclusive lock lock for $newid
2346 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2351 __PACKAGE__-
>register_method({
2352 name
=> 'move_vm_disk',
2353 path
=> '{vmid}/move_disk',
2357 description
=> "Move volume to different storage.",
2359 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2360 "and 'Datastore.AllocateSpace' permissions on the storage.",
2363 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2364 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2368 additionalProperties
=> 0,
2370 node
=> get_standard_option
('pve-node'),
2371 vmid
=> get_standard_option
('pve-vmid'),
2374 description
=> "The disk you want to move.",
2375 enum
=> [ PVE
::QemuServer
::disknames
() ],
2377 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2380 description
=> "Target Format.",
2381 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2386 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2392 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2400 description
=> "the task ID.",
2405 my $rpcenv = PVE
::RPCEnvironment
::get
();
2407 my $authuser = $rpcenv->get_user();
2409 my $node = extract_param
($param, 'node');
2411 my $vmid = extract_param
($param, 'vmid');
2413 my $digest = extract_param
($param, 'digest');
2415 my $disk = extract_param
($param, 'disk');
2417 my $storeid = extract_param
($param, 'storage');
2419 my $format = extract_param
($param, 'format');
2421 my $storecfg = PVE
::Storage
::config
();
2423 my $updatefn = sub {
2425 my $conf = PVE
::QemuServer
::load_config
($vmid);
2427 die "checksum missmatch (file change by other user?)\n"
2428 if $digest && $digest ne $conf->{digest
};
2430 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2432 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2434 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2436 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2439 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2440 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2444 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2445 (!$format || !$oldfmt || $oldfmt eq $format);
2447 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2449 my $running = PVE
::QemuServer
::check_running
($vmid);
2451 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2455 my $newvollist = [];
2458 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2460 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2461 $vmid, $storeid, $format, 1, $newvollist);
2463 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2465 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2467 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2470 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2471 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2478 foreach my $volid (@$newvollist) {
2479 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2482 die "storage migration failed: $err";
2485 if ($param->{delete}) {
2486 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2487 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2488 if ($used_paths->{$path}){
2489 warn "volume $old_volid have snapshots. Can't delete it\n";
2490 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2491 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2493 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2499 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2502 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2505 __PACKAGE__-
>register_method({
2506 name
=> 'migrate_vm',
2507 path
=> '{vmid}/migrate',
2511 description
=> "Migrate virtual machine. Creates a new migration task.",
2513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2516 additionalProperties
=> 0,
2518 node
=> get_standard_option
('pve-node'),
2519 vmid
=> get_standard_option
('pve-vmid'),
2520 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2523 description
=> "Use online/live migration.",
2528 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2535 description
=> "the task ID.",
2540 my $rpcenv = PVE
::RPCEnvironment
::get
();
2542 my $authuser = $rpcenv->get_user();
2544 my $target = extract_param
($param, 'target');
2546 my $localnode = PVE
::INotify
::nodename
();
2547 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2549 PVE
::Cluster
::check_cfs_quorum
();
2551 PVE
::Cluster
::check_node_exists
($target);
2553 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2555 my $vmid = extract_param
($param, 'vmid');
2557 raise_param_exc
({ force
=> "Only root may use this option." })
2558 if $param->{force
} && $authuser ne 'root@pam';
2561 my $conf = PVE
::QemuServer
::load_config
($vmid);
2563 # try to detect errors early
2565 PVE
::QemuServer
::check_lock
($conf);
2567 if (PVE
::QemuServer
::check_running
($vmid)) {
2568 die "cant migrate running VM without --online\n"
2569 if !$param->{online
};
2572 my $storecfg = PVE
::Storage
::config
();
2573 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2575 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2580 my $service = "pvevm:$vmid";
2582 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2584 print "Executing HA migrate for VM $vmid to node $target\n";
2586 PVE
::Tools
::run_command
($cmd);
2591 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2598 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2601 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2606 __PACKAGE__-
>register_method({
2608 path
=> '{vmid}/monitor',
2612 description
=> "Execute Qemu monitor commands.",
2614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2617 additionalProperties
=> 0,
2619 node
=> get_standard_option
('pve-node'),
2620 vmid
=> get_standard_option
('pve-vmid'),
2623 description
=> "The monitor command.",
2627 returns
=> { type
=> 'string'},
2631 my $vmid = $param->{vmid
};
2633 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2637 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2639 $res = "ERROR: $@" if $@;
2644 __PACKAGE__-
>register_method({
2645 name
=> 'resize_vm',
2646 path
=> '{vmid}/resize',
2650 description
=> "Extend volume size.",
2652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2655 additionalProperties
=> 0,
2657 node
=> get_standard_option
('pve-node'),
2658 vmid
=> get_standard_option
('pve-vmid'),
2659 skiplock
=> get_standard_option
('skiplock'),
2662 description
=> "The disk you want to resize.",
2663 enum
=> [PVE
::QemuServer
::disknames
()],
2667 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2668 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.",
2672 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2678 returns
=> { type
=> 'null'},
2682 my $rpcenv = PVE
::RPCEnvironment
::get
();
2684 my $authuser = $rpcenv->get_user();
2686 my $node = extract_param
($param, 'node');
2688 my $vmid = extract_param
($param, 'vmid');
2690 my $digest = extract_param
($param, 'digest');
2692 my $disk = extract_param
($param, 'disk');
2694 my $sizestr = extract_param
($param, 'size');
2696 my $skiplock = extract_param
($param, 'skiplock');
2697 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2698 if $skiplock && $authuser ne 'root@pam';
2700 my $storecfg = PVE
::Storage
::config
();
2702 my $updatefn = sub {
2704 my $conf = PVE
::QemuServer
::load_config
($vmid);
2706 die "checksum missmatch (file change by other user?)\n"
2707 if $digest && $digest ne $conf->{digest
};
2708 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2710 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2712 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2714 my $volid = $drive->{file
};
2716 die "disk '$disk' has no associated volume\n" if !$volid;
2718 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2720 die "you can't online resize a virtio windows bootdisk\n"
2721 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2723 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2725 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2727 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2729 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2730 my ($ext, $newsize, $unit) = ($1, $2, $4);
2733 $newsize = $newsize * 1024;
2734 } elsif ($unit eq 'M') {
2735 $newsize = $newsize * 1024 * 1024;
2736 } elsif ($unit eq 'G') {
2737 $newsize = $newsize * 1024 * 1024 * 1024;
2738 } elsif ($unit eq 'T') {
2739 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2742 $newsize += $size if $ext;
2743 $newsize = int($newsize);
2745 die "unable to skrink disk size\n" if $newsize < $size;
2747 return if $size == $newsize;
2749 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2751 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2753 $drive->{size
} = $newsize;
2754 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2756 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2759 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2763 __PACKAGE__-
>register_method({
2764 name
=> 'snapshot_list',
2765 path
=> '{vmid}/snapshot',
2767 description
=> "List all snapshots.",
2769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2772 protected
=> 1, # qemu pid files are only readable by root
2774 additionalProperties
=> 0,
2776 vmid
=> get_standard_option
('pve-vmid'),
2777 node
=> get_standard_option
('pve-node'),
2786 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2791 my $vmid = $param->{vmid
};
2793 my $conf = PVE
::QemuServer
::load_config
($vmid);
2794 my $snaphash = $conf->{snapshots
} || {};
2798 foreach my $name (keys %$snaphash) {
2799 my $d = $snaphash->{$name};
2802 snaptime
=> $d->{snaptime
} || 0,
2803 vmstate
=> $d->{vmstate
} ?
1 : 0,
2804 description
=> $d->{description
} || '',
2806 $item->{parent
} = $d->{parent
} if $d->{parent
};
2807 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2811 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2812 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2813 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2815 push @$res, $current;
2820 __PACKAGE__-
>register_method({
2822 path
=> '{vmid}/snapshot',
2826 description
=> "Snapshot a VM.",
2828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2831 additionalProperties
=> 0,
2833 node
=> get_standard_option
('pve-node'),
2834 vmid
=> get_standard_option
('pve-vmid'),
2835 snapname
=> get_standard_option
('pve-snapshot-name'),
2839 description
=> "Save the vmstate",
2844 description
=> "Freeze the filesystem",
2849 description
=> "A textual description or comment.",
2855 description
=> "the task ID.",
2860 my $rpcenv = PVE
::RPCEnvironment
::get
();
2862 my $authuser = $rpcenv->get_user();
2864 my $node = extract_param
($param, 'node');
2866 my $vmid = extract_param
($param, 'vmid');
2868 my $snapname = extract_param
($param, 'snapname');
2870 die "unable to use snapshot name 'current' (reserved name)\n"
2871 if $snapname eq 'current';
2874 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2875 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2876 $param->{freezefs
}, $param->{description
});
2879 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2882 __PACKAGE__-
>register_method({
2883 name
=> 'snapshot_cmd_idx',
2884 path
=> '{vmid}/snapshot/{snapname}',
2891 additionalProperties
=> 0,
2893 vmid
=> get_standard_option
('pve-vmid'),
2894 node
=> get_standard_option
('pve-node'),
2895 snapname
=> get_standard_option
('pve-snapshot-name'),
2904 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2911 push @$res, { cmd
=> 'rollback' };
2912 push @$res, { cmd
=> 'config' };
2917 __PACKAGE__-
>register_method({
2918 name
=> 'update_snapshot_config',
2919 path
=> '{vmid}/snapshot/{snapname}/config',
2923 description
=> "Update snapshot metadata.",
2925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2928 additionalProperties
=> 0,
2930 node
=> get_standard_option
('pve-node'),
2931 vmid
=> get_standard_option
('pve-vmid'),
2932 snapname
=> get_standard_option
('pve-snapshot-name'),
2936 description
=> "A textual description or comment.",
2940 returns
=> { type
=> 'null' },
2944 my $rpcenv = PVE
::RPCEnvironment
::get
();
2946 my $authuser = $rpcenv->get_user();
2948 my $vmid = extract_param
($param, 'vmid');
2950 my $snapname = extract_param
($param, 'snapname');
2952 return undef if !defined($param->{description
});
2954 my $updatefn = sub {
2956 my $conf = PVE
::QemuServer
::load_config
($vmid);
2958 PVE
::QemuServer
::check_lock
($conf);
2960 my $snap = $conf->{snapshots
}->{$snapname};
2962 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2964 $snap->{description
} = $param->{description
} if defined($param->{description
});
2966 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2969 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2974 __PACKAGE__-
>register_method({
2975 name
=> 'get_snapshot_config',
2976 path
=> '{vmid}/snapshot/{snapname}/config',
2979 description
=> "Get snapshot configuration",
2981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2984 additionalProperties
=> 0,
2986 node
=> get_standard_option
('pve-node'),
2987 vmid
=> get_standard_option
('pve-vmid'),
2988 snapname
=> get_standard_option
('pve-snapshot-name'),
2991 returns
=> { type
=> "object" },
2995 my $rpcenv = PVE
::RPCEnvironment
::get
();
2997 my $authuser = $rpcenv->get_user();
2999 my $vmid = extract_param
($param, 'vmid');
3001 my $snapname = extract_param
($param, 'snapname');
3003 my $conf = PVE
::QemuServer
::load_config
($vmid);
3005 my $snap = $conf->{snapshots
}->{$snapname};
3007 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3012 __PACKAGE__-
>register_method({
3014 path
=> '{vmid}/snapshot/{snapname}/rollback',
3018 description
=> "Rollback VM state to specified snapshot.",
3020 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3023 additionalProperties
=> 0,
3025 node
=> get_standard_option
('pve-node'),
3026 vmid
=> get_standard_option
('pve-vmid'),
3027 snapname
=> get_standard_option
('pve-snapshot-name'),
3032 description
=> "the task ID.",
3037 my $rpcenv = PVE
::RPCEnvironment
::get
();
3039 my $authuser = $rpcenv->get_user();
3041 my $node = extract_param
($param, 'node');
3043 my $vmid = extract_param
($param, 'vmid');
3045 my $snapname = extract_param
($param, 'snapname');
3048 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3049 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3052 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3055 __PACKAGE__-
>register_method({
3056 name
=> 'delsnapshot',
3057 path
=> '{vmid}/snapshot/{snapname}',
3061 description
=> "Delete a VM snapshot.",
3063 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3066 additionalProperties
=> 0,
3068 node
=> get_standard_option
('pve-node'),
3069 vmid
=> get_standard_option
('pve-vmid'),
3070 snapname
=> get_standard_option
('pve-snapshot-name'),
3074 description
=> "For removal from config file, even if removing disk snapshots fails.",
3080 description
=> "the task ID.",
3085 my $rpcenv = PVE
::RPCEnvironment
::get
();
3087 my $authuser = $rpcenv->get_user();
3089 my $node = extract_param
($param, 'node');
3091 my $vmid = extract_param
($param, 'vmid');
3093 my $snapname = extract_param
($param, 'snapname');
3096 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3097 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3100 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3103 __PACKAGE__-
>register_method({
3105 path
=> '{vmid}/template',
3109 description
=> "Create a Template.",
3111 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3112 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3115 additionalProperties
=> 0,
3117 node
=> get_standard_option
('pve-node'),
3118 vmid
=> get_standard_option
('pve-vmid'),
3122 description
=> "If you want to convert only 1 disk to base image.",
3123 enum
=> [PVE
::QemuServer
::disknames
()],
3128 returns
=> { type
=> 'null'},
3132 my $rpcenv = PVE
::RPCEnvironment
::get
();
3134 my $authuser = $rpcenv->get_user();
3136 my $node = extract_param
($param, 'node');
3138 my $vmid = extract_param
($param, 'vmid');
3140 my $disk = extract_param
($param, 'disk');
3142 my $updatefn = sub {
3144 my $conf = PVE
::QemuServer
::load_config
($vmid);
3146 PVE
::QemuServer
::check_lock
($conf);
3148 die "unable to create template, because VM contains snapshots\n"
3149 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3151 die "you can't convert a template to a template\n"
3152 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3154 die "you can't convert a VM to template if VM is running\n"
3155 if PVE
::QemuServer
::check_running
($vmid);
3158 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3161 $conf->{template
} = 1;
3162 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3164 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3167 PVE
::QemuServer
::lock_config
($vmid, $updatefn);