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' || $opt eq 'smbios1') {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
197 } elsif ($opt =~ m/^net\d+$/) {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
207 __PACKAGE__-
>register_method({
211 description
=> "Virtual machine index (per node).",
213 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
217 protected
=> 1, # qemu pid files are only readable by root
219 additionalProperties
=> 0,
221 node
=> get_standard_option
('pve-node'),
230 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
235 my $rpcenv = PVE
::RPCEnvironment
::get
();
236 my $authuser = $rpcenv->get_user();
238 my $vmstatus = PVE
::QemuServer
::vmstatus
();
241 foreach my $vmid (keys %$vmstatus) {
242 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
244 my $data = $vmstatus->{$vmid};
245 $data->{vmid
} = $vmid;
254 __PACKAGE__-
>register_method({
258 description
=> "Create or restore a virtual machine.",
260 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
261 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
262 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
263 user
=> 'all', # check inside
268 additionalProperties
=> 0,
269 properties
=> PVE
::QemuServer
::json_config_properties
(
271 node
=> get_standard_option
('pve-node'),
272 vmid
=> get_standard_option
('pve-vmid'),
274 description
=> "The backup file.",
279 storage
=> get_standard_option
('pve-storage-id', {
280 description
=> "Default storage.",
286 description
=> "Allow to overwrite existing VM.",
287 requires
=> 'archive',
292 description
=> "Assign a unique random ethernet address.",
293 requires
=> 'archive',
297 type
=> 'string', format
=> 'pve-poolid',
298 description
=> "Add the VM to the specified pool.",
308 my $rpcenv = PVE
::RPCEnvironment
::get
();
310 my $authuser = $rpcenv->get_user();
312 my $node = extract_param
($param, 'node');
314 my $vmid = extract_param
($param, 'vmid');
316 my $archive = extract_param
($param, 'archive');
318 my $storage = extract_param
($param, 'storage');
320 my $force = extract_param
($param, 'force');
322 my $unique = extract_param
($param, 'unique');
324 my $pool = extract_param
($param, 'pool');
326 my $filename = PVE
::QemuServer
::config_file
($vmid);
328 my $storecfg = PVE
::Storage
::config
();
330 PVE
::Cluster
::check_cfs_quorum
();
332 if (defined($pool)) {
333 $rpcenv->check_pool_exist($pool);
336 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
337 if defined($storage);
339 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
341 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
343 } elsif ($archive && $force && (-f
$filename) &&
344 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
345 # OK: user has VM.Backup permissions, and want to restore an existing VM
351 &$resolve_cdrom_alias($param);
353 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
355 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
357 foreach my $opt (keys %$param) {
358 if (PVE
::QemuServer
::valid_drivename
($opt)) {
359 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
360 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
362 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
363 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
367 PVE
::QemuServer
::add_random_macs
($param);
369 my $keystr = join(' ', keys %$param);
370 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
372 if ($archive eq '-') {
373 die "pipe requires cli environment\n"
374 if $rpcenv->{type
} ne 'cli';
376 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
381 my $restorefn = sub {
383 # fixme: this test does not work if VM exists on other node!
385 die "unable to restore vm $vmid: config file already exists\n"
388 die "unable to restore vm $vmid: vm is running\n"
389 if PVE
::QemuServer
::check_running
($vmid);
393 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
396 unique
=> $unique });
398 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
401 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
407 die "unable to create vm $vmid: config file already exists\n"
418 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
420 # try to be smart about bootdisk
421 my @disks = PVE
::QemuServer
::disknames
();
423 foreach my $ds (reverse @disks) {
424 next if !$conf->{$ds};
425 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
426 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
430 if (!$conf->{bootdisk
} && $firstdisk) {
431 $conf->{bootdisk
} = $firstdisk;
434 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
440 foreach my $volid (@$vollist) {
441 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
444 die "create failed - $err";
447 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
450 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
453 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
456 __PACKAGE__-
>register_method({
461 description
=> "Directory index",
466 additionalProperties
=> 0,
468 node
=> get_standard_option
('pve-node'),
469 vmid
=> get_standard_option
('pve-vmid'),
477 subdir
=> { type
=> 'string' },
480 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
486 { subdir
=> 'config' },
487 { subdir
=> 'status' },
488 { subdir
=> 'unlink' },
489 { subdir
=> 'vncproxy' },
490 { subdir
=> 'migrate' },
491 { subdir
=> 'resize' },
492 { subdir
=> 'move' },
494 { subdir
=> 'rrddata' },
495 { subdir
=> 'monitor' },
496 { subdir
=> 'snapshot' },
497 { subdir
=> 'spiceproxy' },
498 { subdir
=> 'sendkey' },
499 { subdir
=> 'firewall' },
505 __PACKAGE__-
>register_method ({
506 subclass
=> "PVE::API2::Firewall::VM",
507 path
=> '{vmid}/firewall',
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrd',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics (returns PNG)",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The list of datasources you want to display.",
531 type
=> 'string', format
=> 'pve-configid-list',
534 description
=> "The RRD consolidation function",
536 enum
=> [ 'AVERAGE', 'MAX' ],
544 filename
=> { type
=> 'string' },
550 return PVE
::Cluster
::create_rrd_graph
(
551 "pve2-vm/$param->{vmid}", $param->{timeframe
},
552 $param->{ds
}, $param->{cf
});
556 __PACKAGE__-
>register_method({
558 path
=> '{vmid}/rrddata',
560 protected
=> 1, # fixme: can we avoid that?
562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 description
=> "Read VM RRD statistics",
566 additionalProperties
=> 0,
568 node
=> get_standard_option
('pve-node'),
569 vmid
=> get_standard_option
('pve-vmid'),
571 description
=> "Specify the time frame you are interested in.",
573 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
576 description
=> "The RRD consolidation function",
578 enum
=> [ 'AVERAGE', 'MAX' ],
593 return PVE
::Cluster
::create_rrd_data
(
594 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
598 __PACKAGE__-
>register_method({
600 path
=> '{vmid}/config',
603 description
=> "Get virtual machine configuration.",
605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
611 vmid
=> get_standard_option
('pve-vmid'),
619 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
626 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
628 delete $conf->{snapshots
};
633 my $vm_is_volid_owner = sub {
634 my ($storecfg, $vmid, $volid) =@_;
636 if ($volid !~ m
|^/|) {
638 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
639 if ($owner && ($owner == $vmid)) {
647 my $test_deallocate_drive = sub {
648 my ($storecfg, $vmid, $key, $drive, $force) = @_;
650 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
651 my $volid = $drive->{file
};
652 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
653 if ($force || $key =~ m/^unused/) {
654 my $sid = PVE
::Storage
::parse_volume_id
($volid);
663 my $delete_drive = sub {
664 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
666 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
667 my $volid = $drive->{file
};
669 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
670 if ($force || $key =~ m/^unused/) {
672 # check if the disk is really unused
673 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
674 my $path = PVE
::Storage
::path
($storecfg, $volid);
676 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
677 if $used_paths->{$path};
679 PVE
::Storage
::vdisk_free
($storecfg, $volid);
683 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
688 delete $conf->{$key};
691 my $vmconfig_delete_option = sub {
692 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
694 return if !defined($conf->{$opt});
696 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
699 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
701 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
702 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
703 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
707 my $unplugwarning = "";
708 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
709 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
710 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
711 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
712 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
713 $unplugwarning = "<br>verify that your guest support acpi hotplug";
716 if ($opt eq 'tablet') {
717 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
719 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
723 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
724 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
726 delete $conf->{$opt};
729 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
732 my $safe_num_ne = sub {
735 return 0 if !defined($a) && !defined($b);
736 return 1 if !defined($a);
737 return 1 if !defined($b);
742 my $vmconfig_update_disk = sub {
743 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
745 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
747 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
748 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
750 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
755 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
757 my $media = $drive->{media
} || 'disk';
758 my $oldmedia = $old_drive->{media
} || 'disk';
759 die "unable to change media type\n" if $media ne $oldmedia;
761 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
762 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
764 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
765 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
768 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
769 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
770 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
771 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
772 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
773 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
774 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
775 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
776 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
777 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
778 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
779 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
780 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
781 ($drive->{mbps
} || 0)*1024*1024,
782 ($drive->{mbps_rd
} || 0)*1024*1024,
783 ($drive->{mbps_wr
} || 0)*1024*1024,
785 $drive->{iops_rd
} || 0,
786 $drive->{iops_wr
} || 0,
787 ($drive->{mbps_max
} || 0)*1024*1024,
788 ($drive->{mbps_rd_max
} || 0)*1024*1024,
789 ($drive->{mbps_wr_max
} || 0)*1024*1024,
790 $drive->{iops_max
} || 0,
791 $drive->{iops_rd_max
} || 0,
792 $drive->{iops_wr_max
} || 0)
793 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
798 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
799 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
801 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
802 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
804 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
806 if (PVE
::QemuServer
::check_running
($vmid)) {
807 if ($drive->{file
} eq 'none') {
808 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
810 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
811 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
812 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
816 } else { # hotplug new disks
818 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
822 my $vmconfig_update_net = sub {
823 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
825 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
826 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
827 my $newnet = PVE
::QemuServer
::parse_net
($value);
829 if($oldnet->{model
} ne $newnet->{model
}){
830 #if model change, we try to hot-unplug
831 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
834 if($newnet->{bridge
} && $oldnet->{bridge
}){
835 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
837 if($newnet->{rate
} ne $oldnet->{rate
}){
838 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
841 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
}) || ($newnet->{firewall
} ne $oldnet->{firewall
})){
842 PVE
::Network
::tap_unplug
($iface);
843 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
847 #if bridge/nat mode change, we try to hot-unplug
848 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
853 $conf->{$opt} = $value;
854 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
855 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
857 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
859 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
862 # POST/PUT {vmid}/config implementation
864 # The original API used PUT (idempotent) an we assumed that all operations
865 # are fast. But it turned out that almost any configuration change can
866 # involve hot-plug actions, or disk alloc/free. Such actions can take long
867 # time to complete and have side effects (not idempotent).
869 # The new implementation uses POST and forks a worker process. We added
870 # a new option 'background_delay'. If specified we wait up to
871 # 'background_delay' second for the worker task to complete. It returns null
872 # if the task is finished within that time, else we return the UPID.
874 my $update_vm_api = sub {
875 my ($param, $sync) = @_;
877 my $rpcenv = PVE
::RPCEnvironment
::get
();
879 my $authuser = $rpcenv->get_user();
881 my $node = extract_param
($param, 'node');
883 my $vmid = extract_param
($param, 'vmid');
885 my $digest = extract_param
($param, 'digest');
887 my $background_delay = extract_param
($param, 'background_delay');
889 my @paramarr = (); # used for log message
890 foreach my $key (keys %$param) {
891 push @paramarr, "-$key", $param->{$key};
894 my $skiplock = extract_param
($param, 'skiplock');
895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
896 if $skiplock && $authuser ne 'root@pam';
898 my $delete_str = extract_param
($param, 'delete');
900 my $force = extract_param
($param, 'force');
902 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
904 my $storecfg = PVE
::Storage
::config
();
906 my $defaults = PVE
::QemuServer
::load_defaults
();
908 &$resolve_cdrom_alias($param);
910 # now try to verify all parameters
913 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
914 $opt = 'ide2' if $opt eq 'cdrom';
915 raise_param_exc
({ delete => "you can't use '-$opt' and " .
916 "-delete $opt' at the same time" })
917 if defined($param->{$opt});
919 if (!PVE
::QemuServer
::option_exists
($opt)) {
920 raise_param_exc
({ delete => "unknown option '$opt'" });
926 foreach my $opt (keys %$param) {
927 if (PVE
::QemuServer
::valid_drivename
($opt)) {
929 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
930 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
931 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
932 } elsif ($opt =~ m/^net(\d+)$/) {
934 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
935 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
939 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
941 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
943 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
947 my $conf = PVE
::QemuServer
::load_config
($vmid);
949 die "checksum missmatch (file change by other user?)\n"
950 if $digest && $digest ne $conf->{digest
};
952 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
954 if ($param->{memory
} || defined($param->{balloon
})) {
955 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
956 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
958 die "balloon value too large (must be smaller than assigned memory)\n"
959 if $balloon && $balloon > $maxmem;
962 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
966 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
968 foreach my $opt (@delete) { # delete
969 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
970 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
973 my $running = PVE
::QemuServer
::check_running
($vmid);
975 foreach my $opt (keys %$param) { # add/change
977 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
981 if (PVE
::QemuServer
::valid_drivename
($opt)) {
983 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
984 $opt, $param->{$opt}, $force);
986 } elsif ($opt =~ m/^net(\d+)$/) { #nics
988 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
989 $opt, $param->{$opt});
993 if($opt eq 'tablet' && $param->{$opt} == 1){
994 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
995 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
996 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
999 if($opt eq 'cores' && $conf->{maxcpus
}){
1000 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1003 $conf->{$opt} = $param->{$opt};
1004 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1008 # allow manual ballooning if shares is set to zero
1009 if ($running && defined($param->{balloon
}) &&
1010 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1011 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1012 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1020 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1022 if ($background_delay) {
1024 # Note: It would be better to do that in the Event based HTTPServer
1025 # to avoid blocking call to sleep.
1027 my $end_time = time() + $background_delay;
1029 my $task = PVE
::Tools
::upid_decode
($upid);
1032 while (time() < $end_time) {
1033 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1035 sleep(1); # this gets interrupted when child process ends
1039 my $status = PVE
::Tools
::upid_read_status
($upid);
1040 return undef if $status eq 'OK';
1049 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1052 my $vm_config_perm_list = [
1057 'VM.Config.Network',
1059 'VM.Config.Options',
1062 __PACKAGE__-
>register_method({
1063 name
=> 'update_vm_async',
1064 path
=> '{vmid}/config',
1068 description
=> "Set virtual machine options (asynchrounous API).",
1070 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1073 additionalProperties
=> 0,
1074 properties
=> PVE
::QemuServer
::json_config_properties
(
1076 node
=> get_standard_option
('pve-node'),
1077 vmid
=> get_standard_option
('pve-vmid'),
1078 skiplock
=> get_standard_option
('skiplock'),
1080 type
=> 'string', format
=> 'pve-configid-list',
1081 description
=> "A list of settings you want to delete.",
1086 description
=> $opt_force_description,
1088 requires
=> 'delete',
1092 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1096 background_delay
=> {
1098 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1109 code
=> $update_vm_api,
1112 __PACKAGE__-
>register_method({
1113 name
=> 'update_vm',
1114 path
=> '{vmid}/config',
1118 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1120 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1123 additionalProperties
=> 0,
1124 properties
=> PVE
::QemuServer
::json_config_properties
(
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1128 skiplock
=> get_standard_option
('skiplock'),
1130 type
=> 'string', format
=> 'pve-configid-list',
1131 description
=> "A list of settings you want to delete.",
1136 description
=> $opt_force_description,
1138 requires
=> 'delete',
1142 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1148 returns
=> { type
=> 'null' },
1151 &$update_vm_api($param, 1);
1157 __PACKAGE__-
>register_method({
1158 name
=> 'destroy_vm',
1163 description
=> "Destroy the vm (also delete all used/owned volumes).",
1165 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1168 additionalProperties
=> 0,
1170 node
=> get_standard_option
('pve-node'),
1171 vmid
=> get_standard_option
('pve-vmid'),
1172 skiplock
=> get_standard_option
('skiplock'),
1181 my $rpcenv = PVE
::RPCEnvironment
::get
();
1183 my $authuser = $rpcenv->get_user();
1185 my $vmid = $param->{vmid
};
1187 my $skiplock = $param->{skiplock
};
1188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1189 if $skiplock && $authuser ne 'root@pam';
1192 my $conf = PVE
::QemuServer
::load_config
($vmid);
1194 my $storecfg = PVE
::Storage
::config
();
1196 my $delVMfromPoolFn = sub {
1197 my $usercfg = cfs_read_file
("user.cfg");
1198 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1199 if (my $data = $usercfg->{pools
}->{$pool}) {
1200 delete $data->{vms
}->{$vmid};
1201 delete $usercfg->{vms
}->{$vmid};
1202 cfs_write_file
("user.cfg", $usercfg);
1210 syslog
('info', "destroy VM $vmid: $upid\n");
1212 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1214 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1217 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1220 __PACKAGE__-
>register_method({
1222 path
=> '{vmid}/unlink',
1226 description
=> "Unlink/delete disk images.",
1228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid'),
1236 type
=> 'string', format
=> 'pve-configid-list',
1237 description
=> "A list of disk IDs you want to delete.",
1241 description
=> $opt_force_description,
1246 returns
=> { type
=> 'null'},
1250 $param->{delete} = extract_param
($param, 'idlist');
1252 __PACKAGE__-
>update_vm($param);
1259 __PACKAGE__-
>register_method({
1261 path
=> '{vmid}/vncproxy',
1265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1267 description
=> "Creates a TCP VNC proxy connections.",
1269 additionalProperties
=> 0,
1271 node
=> get_standard_option
('pve-node'),
1272 vmid
=> get_standard_option
('pve-vmid'),
1276 description
=> "starts websockify instead of vncproxy",
1281 additionalProperties
=> 0,
1283 user
=> { type
=> 'string' },
1284 ticket
=> { type
=> 'string' },
1285 cert
=> { type
=> 'string' },
1286 port
=> { type
=> 'integer' },
1287 upid
=> { type
=> 'string' },
1293 my $rpcenv = PVE
::RPCEnvironment
::get
();
1295 my $authuser = $rpcenv->get_user();
1297 my $vmid = $param->{vmid
};
1298 my $node = $param->{node
};
1299 my $websocket = $param->{websocket
};
1301 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1303 my $authpath = "/vms/$vmid";
1305 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1307 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1310 my $port = PVE
::Tools
::next_vnc_port
();
1315 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1316 $remip = PVE
::Cluster
::remote_node_ip
($node);
1317 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1318 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1326 syslog
('info', "starting vnc proxy $upid\n");
1330 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1332 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1334 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1335 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1336 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1337 '-timeout', $timeout, '-authpath', $authpath,
1338 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1341 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1343 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1345 my $qmstr = join(' ', @$qmcmd);
1347 # also redirect stderr (else we get RFB protocol errors)
1348 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1351 PVE
::Tools
::run_command
($cmd);
1356 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1358 PVE
::Tools
::wait_for_vnc_port
($port);
1369 __PACKAGE__-
>register_method({
1370 name
=> 'vncwebsocket',
1371 path
=> '{vmid}/vncwebsocket',
1374 description
=> "You also need to pass a valid ticket (vncticket).",
1375 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1377 description
=> "Opens a weksocket for VNC traffic.",
1379 additionalProperties
=> 0,
1381 node
=> get_standard_option
('pve-node'),
1382 vmid
=> get_standard_option
('pve-vmid'),
1384 description
=> "Ticket from previous call to vncproxy.",
1389 description
=> "Port number returned by previous vncproxy call.",
1399 port
=> { type
=> 'string' },
1405 my $rpcenv = PVE
::RPCEnvironment
::get
();
1407 my $authuser = $rpcenv->get_user();
1409 my $vmid = $param->{vmid
};
1410 my $node = $param->{node
};
1412 my $authpath = "/vms/$vmid";
1414 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1416 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1418 # Note: VNC ports are acessible from outside, so we do not gain any
1419 # security if we verify that $param->{port} belongs to VM $vmid. This
1420 # check is done by verifying the VNC ticket (inside VNC protocol).
1422 my $port = $param->{port
};
1424 return { port
=> $port };
1427 __PACKAGE__-
>register_method({
1428 name
=> 'spiceproxy',
1429 path
=> '{vmid}/spiceproxy',
1434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1436 description
=> "Returns a SPICE configuration to connect to the VM.",
1438 additionalProperties
=> 0,
1440 node
=> get_standard_option
('pve-node'),
1441 vmid
=> get_standard_option
('pve-vmid'),
1442 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1445 returns
=> get_standard_option
('remote-viewer-config'),
1449 my $rpcenv = PVE
::RPCEnvironment
::get
();
1451 my $authuser = $rpcenv->get_user();
1453 my $vmid = $param->{vmid
};
1454 my $node = $param->{node
};
1455 my $proxy = $param->{proxy
};
1457 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1458 my $title = "VM $vmid - $conf->{'name'}",
1460 my $port = PVE
::QemuServer
::spice_port
($vmid);
1462 my ($ticket, undef, $remote_viewer_config) =
1463 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1465 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1466 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1468 return $remote_viewer_config;
1471 __PACKAGE__-
>register_method({
1473 path
=> '{vmid}/status',
1476 description
=> "Directory index",
1481 additionalProperties
=> 0,
1483 node
=> get_standard_option
('pve-node'),
1484 vmid
=> get_standard_option
('pve-vmid'),
1492 subdir
=> { type
=> 'string' },
1495 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1501 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1504 { subdir
=> 'current' },
1505 { subdir
=> 'start' },
1506 { subdir
=> 'stop' },
1512 my $vm_is_ha_managed = sub {
1515 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1516 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1522 __PACKAGE__-
>register_method({
1523 name
=> 'vm_status',
1524 path
=> '{vmid}/status/current',
1527 protected
=> 1, # qemu pid files are only readable by root
1528 description
=> "Get virtual machine status.",
1530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1533 additionalProperties
=> 0,
1535 node
=> get_standard_option
('pve-node'),
1536 vmid
=> get_standard_option
('pve-vmid'),
1539 returns
=> { type
=> 'object' },
1544 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1546 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1547 my $status = $vmstatus->{$param->{vmid
}};
1549 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1551 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1556 __PACKAGE__-
>register_method({
1558 path
=> '{vmid}/status/start',
1562 description
=> "Start virtual machine.",
1564 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1567 additionalProperties
=> 0,
1569 node
=> get_standard_option
('pve-node'),
1570 vmid
=> get_standard_option
('pve-vmid'),
1571 skiplock
=> get_standard_option
('skiplock'),
1572 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1573 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1574 machine
=> get_standard_option
('pve-qm-machine'),
1583 my $rpcenv = PVE
::RPCEnvironment
::get
();
1585 my $authuser = $rpcenv->get_user();
1587 my $node = extract_param
($param, 'node');
1589 my $vmid = extract_param
($param, 'vmid');
1591 my $machine = extract_param
($param, 'machine');
1593 my $stateuri = extract_param
($param, 'stateuri');
1594 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1595 if $stateuri && $authuser ne 'root@pam';
1597 my $skiplock = extract_param
($param, 'skiplock');
1598 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1599 if $skiplock && $authuser ne 'root@pam';
1601 my $migratedfrom = extract_param
($param, 'migratedfrom');
1602 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1603 if $migratedfrom && $authuser ne 'root@pam';
1605 # read spice ticket from STDIN
1607 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1608 if (defined(my $line = <>)) {
1610 $spice_ticket = $line;
1614 my $storecfg = PVE
::Storage
::config
();
1616 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1617 $rpcenv->{type
} ne 'ha') {
1622 my $service = "pvevm:$vmid";
1624 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1626 print "Executing HA start for VM $vmid\n";
1628 PVE
::Tools
::run_command
($cmd);
1633 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1640 syslog
('info', "start VM $vmid: $upid\n");
1642 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1643 $machine, $spice_ticket);
1648 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1652 __PACKAGE__-
>register_method({
1654 path
=> '{vmid}/status/stop',
1658 description
=> "Stop virtual machine.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1667 skiplock
=> get_standard_option
('skiplock'),
1668 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1670 description
=> "Wait maximal timeout seconds.",
1676 description
=> "Do not decativate storage volumes.",
1689 my $rpcenv = PVE
::RPCEnvironment
::get
();
1691 my $authuser = $rpcenv->get_user();
1693 my $node = extract_param
($param, 'node');
1695 my $vmid = extract_param
($param, 'vmid');
1697 my $skiplock = extract_param
($param, 'skiplock');
1698 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1699 if $skiplock && $authuser ne 'root@pam';
1701 my $keepActive = extract_param
($param, 'keepActive');
1702 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1703 if $keepActive && $authuser ne 'root@pam';
1705 my $migratedfrom = extract_param
($param, 'migratedfrom');
1706 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1707 if $migratedfrom && $authuser ne 'root@pam';
1710 my $storecfg = PVE
::Storage
::config
();
1712 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1717 my $service = "pvevm:$vmid";
1719 my $cmd = ['clusvcadm', '-d', $service];
1721 print "Executing HA stop for VM $vmid\n";
1723 PVE
::Tools
::run_command
($cmd);
1728 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1734 syslog
('info', "stop VM $vmid: $upid\n");
1736 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1737 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1742 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1746 __PACKAGE__-
>register_method({
1748 path
=> '{vmid}/status/reset',
1752 description
=> "Reset virtual machine.",
1754 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1757 additionalProperties
=> 0,
1759 node
=> get_standard_option
('pve-node'),
1760 vmid
=> get_standard_option
('pve-vmid'),
1761 skiplock
=> get_standard_option
('skiplock'),
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $node = extract_param
($param, 'node');
1776 my $vmid = extract_param
($param, 'vmid');
1778 my $skiplock = extract_param
($param, 'skiplock');
1779 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1780 if $skiplock && $authuser ne 'root@pam';
1782 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1787 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1792 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1795 __PACKAGE__-
>register_method({
1796 name
=> 'vm_shutdown',
1797 path
=> '{vmid}/status/shutdown',
1801 description
=> "Shutdown virtual machine.",
1803 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1806 additionalProperties
=> 0,
1808 node
=> get_standard_option
('pve-node'),
1809 vmid
=> get_standard_option
('pve-vmid'),
1810 skiplock
=> get_standard_option
('skiplock'),
1812 description
=> "Wait maximal timeout seconds.",
1818 description
=> "Make sure the VM stops.",
1824 description
=> "Do not decativate storage volumes.",
1837 my $rpcenv = PVE
::RPCEnvironment
::get
();
1839 my $authuser = $rpcenv->get_user();
1841 my $node = extract_param
($param, 'node');
1843 my $vmid = extract_param
($param, 'vmid');
1845 my $skiplock = extract_param
($param, 'skiplock');
1846 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1847 if $skiplock && $authuser ne 'root@pam';
1849 my $keepActive = extract_param
($param, 'keepActive');
1850 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1851 if $keepActive && $authuser ne 'root@pam';
1853 my $storecfg = PVE
::Storage
::config
();
1858 syslog
('info', "shutdown VM $vmid: $upid\n");
1860 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1861 1, $param->{forceStop
}, $keepActive);
1866 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1869 __PACKAGE__-
>register_method({
1870 name
=> 'vm_suspend',
1871 path
=> '{vmid}/status/suspend',
1875 description
=> "Suspend virtual machine.",
1877 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1880 additionalProperties
=> 0,
1882 node
=> get_standard_option
('pve-node'),
1883 vmid
=> get_standard_option
('pve-vmid'),
1884 skiplock
=> get_standard_option
('skiplock'),
1893 my $rpcenv = PVE
::RPCEnvironment
::get
();
1895 my $authuser = $rpcenv->get_user();
1897 my $node = extract_param
($param, 'node');
1899 my $vmid = extract_param
($param, 'vmid');
1901 my $skiplock = extract_param
($param, 'skiplock');
1902 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1903 if $skiplock && $authuser ne 'root@pam';
1905 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1910 syslog
('info', "suspend VM $vmid: $upid\n");
1912 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1917 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1920 __PACKAGE__-
>register_method({
1921 name
=> 'vm_resume',
1922 path
=> '{vmid}/status/resume',
1926 description
=> "Resume virtual machine.",
1928 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1931 additionalProperties
=> 0,
1933 node
=> get_standard_option
('pve-node'),
1934 vmid
=> get_standard_option
('pve-vmid'),
1935 skiplock
=> get_standard_option
('skiplock'),
1944 my $rpcenv = PVE
::RPCEnvironment
::get
();
1946 my $authuser = $rpcenv->get_user();
1948 my $node = extract_param
($param, 'node');
1950 my $vmid = extract_param
($param, 'vmid');
1952 my $skiplock = extract_param
($param, 'skiplock');
1953 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1954 if $skiplock && $authuser ne 'root@pam';
1956 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1961 syslog
('info', "resume VM $vmid: $upid\n");
1963 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1968 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1971 __PACKAGE__-
>register_method({
1972 name
=> 'vm_sendkey',
1973 path
=> '{vmid}/sendkey',
1977 description
=> "Send key event to virtual machine.",
1979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1982 additionalProperties
=> 0,
1984 node
=> get_standard_option
('pve-node'),
1985 vmid
=> get_standard_option
('pve-vmid'),
1986 skiplock
=> get_standard_option
('skiplock'),
1988 description
=> "The key (qemu monitor encoding).",
1993 returns
=> { type
=> 'null'},
1997 my $rpcenv = PVE
::RPCEnvironment
::get
();
1999 my $authuser = $rpcenv->get_user();
2001 my $node = extract_param
($param, 'node');
2003 my $vmid = extract_param
($param, 'vmid');
2005 my $skiplock = extract_param
($param, 'skiplock');
2006 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2007 if $skiplock && $authuser ne 'root@pam';
2009 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2014 __PACKAGE__-
>register_method({
2015 name
=> 'vm_feature',
2016 path
=> '{vmid}/feature',
2020 description
=> "Check if feature for virtual machine is available.",
2022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2025 additionalProperties
=> 0,
2027 node
=> get_standard_option
('pve-node'),
2028 vmid
=> get_standard_option
('pve-vmid'),
2030 description
=> "Feature to check.",
2032 enum
=> [ 'snapshot', 'clone', 'copy' ],
2034 snapname
=> get_standard_option
('pve-snapshot-name', {
2042 hasFeature
=> { type
=> 'boolean' },
2045 items
=> { type
=> 'string' },
2052 my $node = extract_param
($param, 'node');
2054 my $vmid = extract_param
($param, 'vmid');
2056 my $snapname = extract_param
($param, 'snapname');
2058 my $feature = extract_param
($param, 'feature');
2060 my $running = PVE
::QemuServer
::check_running
($vmid);
2062 my $conf = PVE
::QemuServer
::load_config
($vmid);
2065 my $snap = $conf->{snapshots
}->{$snapname};
2066 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2069 my $storecfg = PVE
::Storage
::config
();
2071 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2072 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2075 hasFeature
=> $hasFeature,
2076 nodes
=> [ keys %$nodelist ],
2080 __PACKAGE__-
>register_method({
2082 path
=> '{vmid}/clone',
2086 description
=> "Create a copy of virtual machine/template.",
2088 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2089 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2090 "'Datastore.AllocateSpace' on any used storage.",
2093 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2095 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2096 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2101 additionalProperties
=> 0,
2103 node
=> get_standard_option
('pve-node'),
2104 vmid
=> get_standard_option
('pve-vmid'),
2105 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2108 type
=> 'string', format
=> 'dns-name',
2109 description
=> "Set a name for the new VM.",
2114 description
=> "Description for the new VM.",
2118 type
=> 'string', format
=> 'pve-poolid',
2119 description
=> "Add the new VM to the specified pool.",
2121 snapname
=> get_standard_option
('pve-snapshot-name', {
2125 storage
=> get_standard_option
('pve-storage-id', {
2126 description
=> "Target storage for full clone.",
2131 description
=> "Target format for file storage.",
2135 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2140 description
=> "Create a full copy of all disk. This is always done when " .
2141 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2144 target
=> get_standard_option
('pve-node', {
2145 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2156 my $rpcenv = PVE
::RPCEnvironment
::get
();
2158 my $authuser = $rpcenv->get_user();
2160 my $node = extract_param
($param, 'node');
2162 my $vmid = extract_param
($param, 'vmid');
2164 my $newid = extract_param
($param, 'newid');
2166 my $pool = extract_param
($param, 'pool');
2168 if (defined($pool)) {
2169 $rpcenv->check_pool_exist($pool);
2172 my $snapname = extract_param
($param, 'snapname');
2174 my $storage = extract_param
($param, 'storage');
2176 my $format = extract_param
($param, 'format');
2178 my $target = extract_param
($param, 'target');
2180 my $localnode = PVE
::INotify
::nodename
();
2182 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2184 PVE
::Cluster
::check_node_exists
($target) if $target;
2186 my $storecfg = PVE
::Storage
::config
();
2189 # check if storage is enabled on local node
2190 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2192 # check if storage is available on target node
2193 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2194 # clone only works if target storage is shared
2195 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2196 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2200 PVE
::Cluster
::check_cfs_quorum
();
2202 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2204 # exclusive lock if VM is running - else shared lock is enough;
2205 my $shared_lock = $running ?
0 : 1;
2209 # do all tests after lock
2210 # we also try to do all tests before we fork the worker
2212 my $conf = PVE
::QemuServer
::load_config
($vmid);
2214 PVE
::QemuServer
::check_lock
($conf);
2216 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2218 die "unexpected state change\n" if $verify_running != $running;
2220 die "snapshot '$snapname' does not exist\n"
2221 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2223 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2225 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2227 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2229 my $conffile = PVE
::QemuServer
::config_file
($newid);
2231 die "unable to create VM $newid: config file already exists\n"
2234 my $newconf = { lock => 'clone' };
2238 foreach my $opt (keys %$oldconf) {
2239 my $value = $oldconf->{$opt};
2241 # do not copy snapshot related info
2242 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2243 $opt eq 'vmstate' || $opt eq 'snapstate';
2245 # always change MAC! address
2246 if ($opt =~ m/^net(\d+)$/) {
2247 my $net = PVE
::QemuServer
::parse_net
($value);
2248 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2249 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2250 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2251 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2252 die "unable to parse drive options for '$opt'\n" if !$drive;
2253 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2254 $newconf->{$opt} = $value; # simply copy configuration
2256 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2257 die "Full clone feature is not available"
2258 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2261 $drives->{$opt} = $drive;
2262 push @$vollist, $drive->{file
};
2265 # copy everything else
2266 $newconf->{$opt} = $value;
2270 delete $newconf->{template
};
2272 if ($param->{name
}) {
2273 $newconf->{name
} = $param->{name
};
2275 if ($oldconf->{name
}) {
2276 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2278 $newconf->{name
} = "Copy-of-VM-$vmid";
2282 if ($param->{description
}) {
2283 $newconf->{description
} = $param->{description
};
2286 # create empty/temp config - this fails if VM already exists on other node
2287 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2292 my $newvollist = [];
2295 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2297 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2299 foreach my $opt (keys %$drives) {
2300 my $drive = $drives->{$opt};
2302 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2303 $newid, $storage, $format, $drive->{full
}, $newvollist);
2305 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2307 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2310 delete $newconf->{lock};
2311 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2314 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2315 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2317 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2318 die "Failed to move config to node '$target' - rename failed: $!\n"
2319 if !rename($conffile, $newconffile);
2322 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2327 sleep 1; # some storage like rbd need to wait before release volume - really?
2329 foreach my $volid (@$newvollist) {
2330 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2333 die "clone failed: $err";
2339 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2342 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2343 # Aquire exclusive lock lock for $newid
2344 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2349 __PACKAGE__-
>register_method({
2350 name
=> 'move_vm_disk',
2351 path
=> '{vmid}/move_disk',
2355 description
=> "Move volume to different storage.",
2357 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2358 "and 'Datastore.AllocateSpace' permissions on the storage.",
2361 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2362 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2366 additionalProperties
=> 0,
2368 node
=> get_standard_option
('pve-node'),
2369 vmid
=> get_standard_option
('pve-vmid'),
2372 description
=> "The disk you want to move.",
2373 enum
=> [ PVE
::QemuServer
::disknames
() ],
2375 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2378 description
=> "Target Format.",
2379 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2384 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2390 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2398 description
=> "the task ID.",
2403 my $rpcenv = PVE
::RPCEnvironment
::get
();
2405 my $authuser = $rpcenv->get_user();
2407 my $node = extract_param
($param, 'node');
2409 my $vmid = extract_param
($param, 'vmid');
2411 my $digest = extract_param
($param, 'digest');
2413 my $disk = extract_param
($param, 'disk');
2415 my $storeid = extract_param
($param, 'storage');
2417 my $format = extract_param
($param, 'format');
2419 my $storecfg = PVE
::Storage
::config
();
2421 my $updatefn = sub {
2423 my $conf = PVE
::QemuServer
::load_config
($vmid);
2425 die "checksum missmatch (file change by other user?)\n"
2426 if $digest && $digest ne $conf->{digest
};
2428 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2430 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2432 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2434 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2437 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2438 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2442 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2443 (!$format || !$oldfmt || $oldfmt eq $format);
2445 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2447 my $running = PVE
::QemuServer
::check_running
($vmid);
2449 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2453 my $newvollist = [];
2456 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2458 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2459 $vmid, $storeid, $format, 1, $newvollist);
2461 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2463 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2465 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2468 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2469 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2476 foreach my $volid (@$newvollist) {
2477 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2480 die "storage migration failed: $err";
2483 if ($param->{delete}) {
2484 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2485 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2486 if ($used_paths->{$path}){
2487 warn "volume $old_volid have snapshots. Can't delete it\n";
2488 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2489 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2491 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2497 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2500 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2503 __PACKAGE__-
>register_method({
2504 name
=> 'migrate_vm',
2505 path
=> '{vmid}/migrate',
2509 description
=> "Migrate virtual machine. Creates a new migration task.",
2511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2514 additionalProperties
=> 0,
2516 node
=> get_standard_option
('pve-node'),
2517 vmid
=> get_standard_option
('pve-vmid'),
2518 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2521 description
=> "Use online/live migration.",
2526 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2533 description
=> "the task ID.",
2538 my $rpcenv = PVE
::RPCEnvironment
::get
();
2540 my $authuser = $rpcenv->get_user();
2542 my $target = extract_param
($param, 'target');
2544 my $localnode = PVE
::INotify
::nodename
();
2545 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2547 PVE
::Cluster
::check_cfs_quorum
();
2549 PVE
::Cluster
::check_node_exists
($target);
2551 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2553 my $vmid = extract_param
($param, 'vmid');
2555 raise_param_exc
({ force
=> "Only root may use this option." })
2556 if $param->{force
} && $authuser ne 'root@pam';
2559 my $conf = PVE
::QemuServer
::load_config
($vmid);
2561 # try to detect errors early
2563 PVE
::QemuServer
::check_lock
($conf);
2565 if (PVE
::QemuServer
::check_running
($vmid)) {
2566 die "cant migrate running VM without --online\n"
2567 if !$param->{online
};
2570 my $storecfg = PVE
::Storage
::config
();
2571 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2573 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2578 my $service = "pvevm:$vmid";
2580 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2582 print "Executing HA migrate for VM $vmid to node $target\n";
2584 PVE
::Tools
::run_command
($cmd);
2589 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2596 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2599 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2604 __PACKAGE__-
>register_method({
2606 path
=> '{vmid}/monitor',
2610 description
=> "Execute Qemu monitor commands.",
2612 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2615 additionalProperties
=> 0,
2617 node
=> get_standard_option
('pve-node'),
2618 vmid
=> get_standard_option
('pve-vmid'),
2621 description
=> "The monitor command.",
2625 returns
=> { type
=> 'string'},
2629 my $vmid = $param->{vmid
};
2631 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2635 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2637 $res = "ERROR: $@" if $@;
2642 __PACKAGE__-
>register_method({
2643 name
=> 'resize_vm',
2644 path
=> '{vmid}/resize',
2648 description
=> "Extend volume size.",
2650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2653 additionalProperties
=> 0,
2655 node
=> get_standard_option
('pve-node'),
2656 vmid
=> get_standard_option
('pve-vmid'),
2657 skiplock
=> get_standard_option
('skiplock'),
2660 description
=> "The disk you want to resize.",
2661 enum
=> [PVE
::QemuServer
::disknames
()],
2665 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2666 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.",
2670 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2676 returns
=> { type
=> 'null'},
2680 my $rpcenv = PVE
::RPCEnvironment
::get
();
2682 my $authuser = $rpcenv->get_user();
2684 my $node = extract_param
($param, 'node');
2686 my $vmid = extract_param
($param, 'vmid');
2688 my $digest = extract_param
($param, 'digest');
2690 my $disk = extract_param
($param, 'disk');
2692 my $sizestr = extract_param
($param, 'size');
2694 my $skiplock = extract_param
($param, 'skiplock');
2695 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2696 if $skiplock && $authuser ne 'root@pam';
2698 my $storecfg = PVE
::Storage
::config
();
2700 my $updatefn = sub {
2702 my $conf = PVE
::QemuServer
::load_config
($vmid);
2704 die "checksum missmatch (file change by other user?)\n"
2705 if $digest && $digest ne $conf->{digest
};
2706 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2708 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2710 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2712 my $volid = $drive->{file
};
2714 die "disk '$disk' has no associated volume\n" if !$volid;
2716 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2718 die "you can't online resize a virtio windows bootdisk\n"
2719 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2721 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2723 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2725 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2727 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2728 my ($ext, $newsize, $unit) = ($1, $2, $4);
2731 $newsize = $newsize * 1024;
2732 } elsif ($unit eq 'M') {
2733 $newsize = $newsize * 1024 * 1024;
2734 } elsif ($unit eq 'G') {
2735 $newsize = $newsize * 1024 * 1024 * 1024;
2736 } elsif ($unit eq 'T') {
2737 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2740 $newsize += $size if $ext;
2741 $newsize = int($newsize);
2743 die "unable to skrink disk size\n" if $newsize < $size;
2745 return if $size == $newsize;
2747 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2749 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2751 $drive->{size
} = $newsize;
2752 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2754 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2757 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2761 __PACKAGE__-
>register_method({
2762 name
=> 'snapshot_list',
2763 path
=> '{vmid}/snapshot',
2765 description
=> "List all snapshots.",
2767 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2770 protected
=> 1, # qemu pid files are only readable by root
2772 additionalProperties
=> 0,
2774 vmid
=> get_standard_option
('pve-vmid'),
2775 node
=> get_standard_option
('pve-node'),
2784 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2789 my $vmid = $param->{vmid
};
2791 my $conf = PVE
::QemuServer
::load_config
($vmid);
2792 my $snaphash = $conf->{snapshots
} || {};
2796 foreach my $name (keys %$snaphash) {
2797 my $d = $snaphash->{$name};
2800 snaptime
=> $d->{snaptime
} || 0,
2801 vmstate
=> $d->{vmstate
} ?
1 : 0,
2802 description
=> $d->{description
} || '',
2804 $item->{parent
} = $d->{parent
} if $d->{parent
};
2805 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2809 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2810 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2811 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2813 push @$res, $current;
2818 __PACKAGE__-
>register_method({
2820 path
=> '{vmid}/snapshot',
2824 description
=> "Snapshot a VM.",
2826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2829 additionalProperties
=> 0,
2831 node
=> get_standard_option
('pve-node'),
2832 vmid
=> get_standard_option
('pve-vmid'),
2833 snapname
=> get_standard_option
('pve-snapshot-name'),
2837 description
=> "Save the vmstate",
2842 description
=> "Freeze the filesystem",
2847 description
=> "A textual description or comment.",
2853 description
=> "the task ID.",
2858 my $rpcenv = PVE
::RPCEnvironment
::get
();
2860 my $authuser = $rpcenv->get_user();
2862 my $node = extract_param
($param, 'node');
2864 my $vmid = extract_param
($param, 'vmid');
2866 my $snapname = extract_param
($param, 'snapname');
2868 die "unable to use snapshot name 'current' (reserved name)\n"
2869 if $snapname eq 'current';
2872 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2873 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2874 $param->{freezefs
}, $param->{description
});
2877 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2880 __PACKAGE__-
>register_method({
2881 name
=> 'snapshot_cmd_idx',
2882 path
=> '{vmid}/snapshot/{snapname}',
2889 additionalProperties
=> 0,
2891 vmid
=> get_standard_option
('pve-vmid'),
2892 node
=> get_standard_option
('pve-node'),
2893 snapname
=> get_standard_option
('pve-snapshot-name'),
2902 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2909 push @$res, { cmd
=> 'rollback' };
2910 push @$res, { cmd
=> 'config' };
2915 __PACKAGE__-
>register_method({
2916 name
=> 'update_snapshot_config',
2917 path
=> '{vmid}/snapshot/{snapname}/config',
2921 description
=> "Update snapshot metadata.",
2923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2926 additionalProperties
=> 0,
2928 node
=> get_standard_option
('pve-node'),
2929 vmid
=> get_standard_option
('pve-vmid'),
2930 snapname
=> get_standard_option
('pve-snapshot-name'),
2934 description
=> "A textual description or comment.",
2938 returns
=> { type
=> 'null' },
2942 my $rpcenv = PVE
::RPCEnvironment
::get
();
2944 my $authuser = $rpcenv->get_user();
2946 my $vmid = extract_param
($param, 'vmid');
2948 my $snapname = extract_param
($param, 'snapname');
2950 return undef if !defined($param->{description
});
2952 my $updatefn = sub {
2954 my $conf = PVE
::QemuServer
::load_config
($vmid);
2956 PVE
::QemuServer
::check_lock
($conf);
2958 my $snap = $conf->{snapshots
}->{$snapname};
2960 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2962 $snap->{description
} = $param->{description
} if defined($param->{description
});
2964 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2967 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2972 __PACKAGE__-
>register_method({
2973 name
=> 'get_snapshot_config',
2974 path
=> '{vmid}/snapshot/{snapname}/config',
2977 description
=> "Get snapshot configuration",
2979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2982 additionalProperties
=> 0,
2984 node
=> get_standard_option
('pve-node'),
2985 vmid
=> get_standard_option
('pve-vmid'),
2986 snapname
=> get_standard_option
('pve-snapshot-name'),
2989 returns
=> { type
=> "object" },
2993 my $rpcenv = PVE
::RPCEnvironment
::get
();
2995 my $authuser = $rpcenv->get_user();
2997 my $vmid = extract_param
($param, 'vmid');
2999 my $snapname = extract_param
($param, 'snapname');
3001 my $conf = PVE
::QemuServer
::load_config
($vmid);
3003 my $snap = $conf->{snapshots
}->{$snapname};
3005 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3010 __PACKAGE__-
>register_method({
3012 path
=> '{vmid}/snapshot/{snapname}/rollback',
3016 description
=> "Rollback VM state to specified snapshot.",
3018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3021 additionalProperties
=> 0,
3023 node
=> get_standard_option
('pve-node'),
3024 vmid
=> get_standard_option
('pve-vmid'),
3025 snapname
=> get_standard_option
('pve-snapshot-name'),
3030 description
=> "the task ID.",
3035 my $rpcenv = PVE
::RPCEnvironment
::get
();
3037 my $authuser = $rpcenv->get_user();
3039 my $node = extract_param
($param, 'node');
3041 my $vmid = extract_param
($param, 'vmid');
3043 my $snapname = extract_param
($param, 'snapname');
3046 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3047 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3050 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3053 __PACKAGE__-
>register_method({
3054 name
=> 'delsnapshot',
3055 path
=> '{vmid}/snapshot/{snapname}',
3059 description
=> "Delete a VM snapshot.",
3061 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3064 additionalProperties
=> 0,
3066 node
=> get_standard_option
('pve-node'),
3067 vmid
=> get_standard_option
('pve-vmid'),
3068 snapname
=> get_standard_option
('pve-snapshot-name'),
3072 description
=> "For removal from config file, even if removing disk snapshots fails.",
3078 description
=> "the task ID.",
3083 my $rpcenv = PVE
::RPCEnvironment
::get
();
3085 my $authuser = $rpcenv->get_user();
3087 my $node = extract_param
($param, 'node');
3089 my $vmid = extract_param
($param, 'vmid');
3091 my $snapname = extract_param
($param, 'snapname');
3094 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3095 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3098 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3101 __PACKAGE__-
>register_method({
3103 path
=> '{vmid}/template',
3107 description
=> "Create a Template.",
3109 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3110 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3113 additionalProperties
=> 0,
3115 node
=> get_standard_option
('pve-node'),
3116 vmid
=> get_standard_option
('pve-vmid'),
3120 description
=> "If you want to convert only 1 disk to base image.",
3121 enum
=> [PVE
::QemuServer
::disknames
()],
3126 returns
=> { type
=> 'null'},
3130 my $rpcenv = PVE
::RPCEnvironment
::get
();
3132 my $authuser = $rpcenv->get_user();
3134 my $node = extract_param
($param, 'node');
3136 my $vmid = extract_param
($param, 'vmid');
3138 my $disk = extract_param
($param, 'disk');
3140 my $updatefn = sub {
3142 my $conf = PVE
::QemuServer
::load_config
($vmid);
3144 PVE
::QemuServer
::check_lock
($conf);
3146 die "unable to create template, because VM contains snapshots\n"
3147 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3149 die "you can't convert a template to a template\n"
3150 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3152 die "you can't convert a VM to template if VM is running\n"
3153 if PVE
::QemuServer
::check_running
($vmid);
3156 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3159 $conf->{template
} = 1;
3160 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3162 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3165 PVE
::QemuServer
::lock_config
($vmid, $updatefn);