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 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1343 my $qmstr = join(' ', @$qmcmd);
1345 # also redirect stderr (else we get RFB protocol errors)
1346 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1349 $ENV{LC_PVE_TICKET
} = $ticket;
1350 $cmd = ["/usr/share/novnc-pve/utils/wsproxy.py", '--run-once', "--timeout=$timeout", "--idle-timeout=$timeout", '--ssl-only', '--cert', '/etc/pve/local/pve-ssl.pem', '--key', '/etc/pve/local/pve-ssl.key', $port, '--', @$cmd];
1354 PVE
::Tools
::run_command
($cmd);
1359 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1361 PVE
::Tools
::wait_for_vnc_port
($port);
1372 __PACKAGE__-
>register_method({
1373 name
=> 'spiceproxy',
1374 path
=> '{vmid}/spiceproxy',
1379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1381 description
=> "Returns a SPICE configuration to connect to the VM.",
1383 additionalProperties
=> 0,
1385 node
=> get_standard_option
('pve-node'),
1386 vmid
=> get_standard_option
('pve-vmid'),
1387 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1390 returns
=> get_standard_option
('remote-viewer-config'),
1394 my $rpcenv = PVE
::RPCEnvironment
::get
();
1396 my $authuser = $rpcenv->get_user();
1398 my $vmid = $param->{vmid
};
1399 my $node = $param->{node
};
1400 my $proxy = $param->{proxy
};
1402 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1403 my $title = "VM $vmid - $conf->{'name'}",
1405 my $port = PVE
::QemuServer
::spice_port
($vmid);
1407 my ($ticket, undef, $remote_viewer_config) =
1408 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1410 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1411 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1413 return $remote_viewer_config;
1416 __PACKAGE__-
>register_method({
1418 path
=> '{vmid}/status',
1421 description
=> "Directory index",
1426 additionalProperties
=> 0,
1428 node
=> get_standard_option
('pve-node'),
1429 vmid
=> get_standard_option
('pve-vmid'),
1437 subdir
=> { type
=> 'string' },
1440 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1446 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1449 { subdir
=> 'current' },
1450 { subdir
=> 'start' },
1451 { subdir
=> 'stop' },
1457 my $vm_is_ha_managed = sub {
1460 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1461 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1467 __PACKAGE__-
>register_method({
1468 name
=> 'vm_status',
1469 path
=> '{vmid}/status/current',
1472 protected
=> 1, # qemu pid files are only readable by root
1473 description
=> "Get virtual machine status.",
1475 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1478 additionalProperties
=> 0,
1480 node
=> get_standard_option
('pve-node'),
1481 vmid
=> get_standard_option
('pve-vmid'),
1484 returns
=> { type
=> 'object' },
1489 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1491 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1492 my $status = $vmstatus->{$param->{vmid
}};
1494 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1496 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1501 __PACKAGE__-
>register_method({
1503 path
=> '{vmid}/status/start',
1507 description
=> "Start virtual machine.",
1509 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1512 additionalProperties
=> 0,
1514 node
=> get_standard_option
('pve-node'),
1515 vmid
=> get_standard_option
('pve-vmid'),
1516 skiplock
=> get_standard_option
('skiplock'),
1517 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1518 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1519 machine
=> get_standard_option
('pve-qm-machine'),
1528 my $rpcenv = PVE
::RPCEnvironment
::get
();
1530 my $authuser = $rpcenv->get_user();
1532 my $node = extract_param
($param, 'node');
1534 my $vmid = extract_param
($param, 'vmid');
1536 my $machine = extract_param
($param, 'machine');
1538 my $stateuri = extract_param
($param, 'stateuri');
1539 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1540 if $stateuri && $authuser ne 'root@pam';
1542 my $skiplock = extract_param
($param, 'skiplock');
1543 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1544 if $skiplock && $authuser ne 'root@pam';
1546 my $migratedfrom = extract_param
($param, 'migratedfrom');
1547 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1548 if $migratedfrom && $authuser ne 'root@pam';
1550 # read spice ticket from STDIN
1552 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1553 if (defined(my $line = <>)) {
1555 $spice_ticket = $line;
1559 my $storecfg = PVE
::Storage
::config
();
1561 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1562 $rpcenv->{type
} ne 'ha') {
1567 my $service = "pvevm:$vmid";
1569 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1571 print "Executing HA start for VM $vmid\n";
1573 PVE
::Tools
::run_command
($cmd);
1578 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1585 syslog
('info', "start VM $vmid: $upid\n");
1587 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1588 $machine, $spice_ticket);
1593 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1597 __PACKAGE__-
>register_method({
1599 path
=> '{vmid}/status/stop',
1603 description
=> "Stop virtual machine.",
1605 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1608 additionalProperties
=> 0,
1610 node
=> get_standard_option
('pve-node'),
1611 vmid
=> get_standard_option
('pve-vmid'),
1612 skiplock
=> get_standard_option
('skiplock'),
1613 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1615 description
=> "Wait maximal timeout seconds.",
1621 description
=> "Do not decativate storage volumes.",
1634 my $rpcenv = PVE
::RPCEnvironment
::get
();
1636 my $authuser = $rpcenv->get_user();
1638 my $node = extract_param
($param, 'node');
1640 my $vmid = extract_param
($param, 'vmid');
1642 my $skiplock = extract_param
($param, 'skiplock');
1643 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1644 if $skiplock && $authuser ne 'root@pam';
1646 my $keepActive = extract_param
($param, 'keepActive');
1647 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1648 if $keepActive && $authuser ne 'root@pam';
1650 my $migratedfrom = extract_param
($param, 'migratedfrom');
1651 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1652 if $migratedfrom && $authuser ne 'root@pam';
1655 my $storecfg = PVE
::Storage
::config
();
1657 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1662 my $service = "pvevm:$vmid";
1664 my $cmd = ['clusvcadm', '-d', $service];
1666 print "Executing HA stop for VM $vmid\n";
1668 PVE
::Tools
::run_command
($cmd);
1673 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1679 syslog
('info', "stop VM $vmid: $upid\n");
1681 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1682 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1687 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1691 __PACKAGE__-
>register_method({
1693 path
=> '{vmid}/status/reset',
1697 description
=> "Reset virtual machine.",
1699 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1702 additionalProperties
=> 0,
1704 node
=> get_standard_option
('pve-node'),
1705 vmid
=> get_standard_option
('pve-vmid'),
1706 skiplock
=> get_standard_option
('skiplock'),
1715 my $rpcenv = PVE
::RPCEnvironment
::get
();
1717 my $authuser = $rpcenv->get_user();
1719 my $node = extract_param
($param, 'node');
1721 my $vmid = extract_param
($param, 'vmid');
1723 my $skiplock = extract_param
($param, 'skiplock');
1724 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1725 if $skiplock && $authuser ne 'root@pam';
1727 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1732 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1737 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1740 __PACKAGE__-
>register_method({
1741 name
=> 'vm_shutdown',
1742 path
=> '{vmid}/status/shutdown',
1746 description
=> "Shutdown virtual machine.",
1748 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1751 additionalProperties
=> 0,
1753 node
=> get_standard_option
('pve-node'),
1754 vmid
=> get_standard_option
('pve-vmid'),
1755 skiplock
=> get_standard_option
('skiplock'),
1757 description
=> "Wait maximal timeout seconds.",
1763 description
=> "Make sure the VM stops.",
1769 description
=> "Do not decativate storage volumes.",
1782 my $rpcenv = PVE
::RPCEnvironment
::get
();
1784 my $authuser = $rpcenv->get_user();
1786 my $node = extract_param
($param, 'node');
1788 my $vmid = extract_param
($param, 'vmid');
1790 my $skiplock = extract_param
($param, 'skiplock');
1791 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1792 if $skiplock && $authuser ne 'root@pam';
1794 my $keepActive = extract_param
($param, 'keepActive');
1795 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1796 if $keepActive && $authuser ne 'root@pam';
1798 my $storecfg = PVE
::Storage
::config
();
1803 syslog
('info', "shutdown VM $vmid: $upid\n");
1805 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1806 1, $param->{forceStop
}, $keepActive);
1811 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1814 __PACKAGE__-
>register_method({
1815 name
=> 'vm_suspend',
1816 path
=> '{vmid}/status/suspend',
1820 description
=> "Suspend virtual machine.",
1822 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1825 additionalProperties
=> 0,
1827 node
=> get_standard_option
('pve-node'),
1828 vmid
=> get_standard_option
('pve-vmid'),
1829 skiplock
=> get_standard_option
('skiplock'),
1838 my $rpcenv = PVE
::RPCEnvironment
::get
();
1840 my $authuser = $rpcenv->get_user();
1842 my $node = extract_param
($param, 'node');
1844 my $vmid = extract_param
($param, 'vmid');
1846 my $skiplock = extract_param
($param, 'skiplock');
1847 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1848 if $skiplock && $authuser ne 'root@pam';
1850 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1855 syslog
('info', "suspend VM $vmid: $upid\n");
1857 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1862 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1865 __PACKAGE__-
>register_method({
1866 name
=> 'vm_resume',
1867 path
=> '{vmid}/status/resume',
1871 description
=> "Resume virtual machine.",
1873 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1876 additionalProperties
=> 0,
1878 node
=> get_standard_option
('pve-node'),
1879 vmid
=> get_standard_option
('pve-vmid'),
1880 skiplock
=> get_standard_option
('skiplock'),
1889 my $rpcenv = PVE
::RPCEnvironment
::get
();
1891 my $authuser = $rpcenv->get_user();
1893 my $node = extract_param
($param, 'node');
1895 my $vmid = extract_param
($param, 'vmid');
1897 my $skiplock = extract_param
($param, 'skiplock');
1898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1899 if $skiplock && $authuser ne 'root@pam';
1901 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1906 syslog
('info', "resume VM $vmid: $upid\n");
1908 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1913 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1916 __PACKAGE__-
>register_method({
1917 name
=> 'vm_sendkey',
1918 path
=> '{vmid}/sendkey',
1922 description
=> "Send key event to virtual machine.",
1924 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1927 additionalProperties
=> 0,
1929 node
=> get_standard_option
('pve-node'),
1930 vmid
=> get_standard_option
('pve-vmid'),
1931 skiplock
=> get_standard_option
('skiplock'),
1933 description
=> "The key (qemu monitor encoding).",
1938 returns
=> { type
=> 'null'},
1942 my $rpcenv = PVE
::RPCEnvironment
::get
();
1944 my $authuser = $rpcenv->get_user();
1946 my $node = extract_param
($param, 'node');
1948 my $vmid = extract_param
($param, 'vmid');
1950 my $skiplock = extract_param
($param, 'skiplock');
1951 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1952 if $skiplock && $authuser ne 'root@pam';
1954 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1959 __PACKAGE__-
>register_method({
1960 name
=> 'vm_feature',
1961 path
=> '{vmid}/feature',
1965 description
=> "Check if feature for virtual machine is available.",
1967 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1970 additionalProperties
=> 0,
1972 node
=> get_standard_option
('pve-node'),
1973 vmid
=> get_standard_option
('pve-vmid'),
1975 description
=> "Feature to check.",
1977 enum
=> [ 'snapshot', 'clone', 'copy' ],
1979 snapname
=> get_standard_option
('pve-snapshot-name', {
1987 hasFeature
=> { type
=> 'boolean' },
1990 items
=> { type
=> 'string' },
1997 my $node = extract_param
($param, 'node');
1999 my $vmid = extract_param
($param, 'vmid');
2001 my $snapname = extract_param
($param, 'snapname');
2003 my $feature = extract_param
($param, 'feature');
2005 my $running = PVE
::QemuServer
::check_running
($vmid);
2007 my $conf = PVE
::QemuServer
::load_config
($vmid);
2010 my $snap = $conf->{snapshots
}->{$snapname};
2011 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2014 my $storecfg = PVE
::Storage
::config
();
2016 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2017 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2020 hasFeature
=> $hasFeature,
2021 nodes
=> [ keys %$nodelist ],
2025 __PACKAGE__-
>register_method({
2027 path
=> '{vmid}/clone',
2031 description
=> "Create a copy of virtual machine/template.",
2033 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2034 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2035 "'Datastore.AllocateSpace' on any used storage.",
2038 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2040 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2041 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid'),
2050 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2053 type
=> 'string', format
=> 'dns-name',
2054 description
=> "Set a name for the new VM.",
2059 description
=> "Description for the new VM.",
2063 type
=> 'string', format
=> 'pve-poolid',
2064 description
=> "Add the new VM to the specified pool.",
2066 snapname
=> get_standard_option
('pve-snapshot-name', {
2070 storage
=> get_standard_option
('pve-storage-id', {
2071 description
=> "Target storage for full clone.",
2076 description
=> "Target format for file storage.",
2080 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2085 description
=> "Create a full copy of all disk. This is always done when " .
2086 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2089 target
=> get_standard_option
('pve-node', {
2090 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2101 my $rpcenv = PVE
::RPCEnvironment
::get
();
2103 my $authuser = $rpcenv->get_user();
2105 my $node = extract_param
($param, 'node');
2107 my $vmid = extract_param
($param, 'vmid');
2109 my $newid = extract_param
($param, 'newid');
2111 my $pool = extract_param
($param, 'pool');
2113 if (defined($pool)) {
2114 $rpcenv->check_pool_exist($pool);
2117 my $snapname = extract_param
($param, 'snapname');
2119 my $storage = extract_param
($param, 'storage');
2121 my $format = extract_param
($param, 'format');
2123 my $target = extract_param
($param, 'target');
2125 my $localnode = PVE
::INotify
::nodename
();
2127 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2129 PVE
::Cluster
::check_node_exists
($target) if $target;
2131 my $storecfg = PVE
::Storage
::config
();
2134 # check if storage is enabled on local node
2135 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2137 # check if storage is available on target node
2138 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2139 # clone only works if target storage is shared
2140 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2141 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2145 PVE
::Cluster
::check_cfs_quorum
();
2147 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2149 # exclusive lock if VM is running - else shared lock is enough;
2150 my $shared_lock = $running ?
0 : 1;
2154 # do all tests after lock
2155 # we also try to do all tests before we fork the worker
2157 my $conf = PVE
::QemuServer
::load_config
($vmid);
2159 PVE
::QemuServer
::check_lock
($conf);
2161 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2163 die "unexpected state change\n" if $verify_running != $running;
2165 die "snapshot '$snapname' does not exist\n"
2166 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2168 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2170 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2172 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2174 my $conffile = PVE
::QemuServer
::config_file
($newid);
2176 die "unable to create VM $newid: config file already exists\n"
2179 my $newconf = { lock => 'clone' };
2183 foreach my $opt (keys %$oldconf) {
2184 my $value = $oldconf->{$opt};
2186 # do not copy snapshot related info
2187 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2188 $opt eq 'vmstate' || $opt eq 'snapstate';
2190 # always change MAC! address
2191 if ($opt =~ m/^net(\d+)$/) {
2192 my $net = PVE
::QemuServer
::parse_net
($value);
2193 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2194 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2195 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2196 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2197 die "unable to parse drive options for '$opt'\n" if !$drive;
2198 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2199 $newconf->{$opt} = $value; # simply copy configuration
2201 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2202 die "Full clone feature is not available"
2203 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2206 $drives->{$opt} = $drive;
2207 push @$vollist, $drive->{file
};
2210 # copy everything else
2211 $newconf->{$opt} = $value;
2215 delete $newconf->{template
};
2217 if ($param->{name
}) {
2218 $newconf->{name
} = $param->{name
};
2220 if ($oldconf->{name
}) {
2221 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2223 $newconf->{name
} = "Copy-of-VM-$vmid";
2227 if ($param->{description
}) {
2228 $newconf->{description
} = $param->{description
};
2231 # create empty/temp config - this fails if VM already exists on other node
2232 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2237 my $newvollist = [];
2240 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2242 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2244 foreach my $opt (keys %$drives) {
2245 my $drive = $drives->{$opt};
2247 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2248 $newid, $storage, $format, $drive->{full
}, $newvollist);
2250 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2252 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2255 delete $newconf->{lock};
2256 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2259 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2260 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2262 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2263 die "Failed to move config to node '$target' - rename failed: $!\n"
2264 if !rename($conffile, $newconffile);
2267 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2272 sleep 1; # some storage like rbd need to wait before release volume - really?
2274 foreach my $volid (@$newvollist) {
2275 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2278 die "clone failed: $err";
2284 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2287 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2288 # Aquire exclusive lock lock for $newid
2289 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2294 __PACKAGE__-
>register_method({
2295 name
=> 'move_vm_disk',
2296 path
=> '{vmid}/move_disk',
2300 description
=> "Move volume to different storage.",
2302 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2303 "and 'Datastore.AllocateSpace' permissions on the storage.",
2306 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2307 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2311 additionalProperties
=> 0,
2313 node
=> get_standard_option
('pve-node'),
2314 vmid
=> get_standard_option
('pve-vmid'),
2317 description
=> "The disk you want to move.",
2318 enum
=> [ PVE
::QemuServer
::disknames
() ],
2320 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2323 description
=> "Target Format.",
2324 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2329 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2335 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2343 description
=> "the task ID.",
2348 my $rpcenv = PVE
::RPCEnvironment
::get
();
2350 my $authuser = $rpcenv->get_user();
2352 my $node = extract_param
($param, 'node');
2354 my $vmid = extract_param
($param, 'vmid');
2356 my $digest = extract_param
($param, 'digest');
2358 my $disk = extract_param
($param, 'disk');
2360 my $storeid = extract_param
($param, 'storage');
2362 my $format = extract_param
($param, 'format');
2364 my $storecfg = PVE
::Storage
::config
();
2366 my $updatefn = sub {
2368 my $conf = PVE
::QemuServer
::load_config
($vmid);
2370 die "checksum missmatch (file change by other user?)\n"
2371 if $digest && $digest ne $conf->{digest
};
2373 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2375 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2377 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2379 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2382 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2383 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2387 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2388 (!$format || !$oldfmt || $oldfmt eq $format);
2390 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2392 my $running = PVE
::QemuServer
::check_running
($vmid);
2394 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2398 my $newvollist = [];
2401 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2403 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2404 $vmid, $storeid, $format, 1, $newvollist);
2406 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2408 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2410 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2413 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2414 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2421 foreach my $volid (@$newvollist) {
2422 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2425 die "storage migration failed: $err";
2428 if ($param->{delete}) {
2429 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2430 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2431 if ($used_paths->{$path}){
2432 warn "volume $old_volid have snapshots. Can't delete it\n";
2433 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2434 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2436 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2442 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2445 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2448 __PACKAGE__-
>register_method({
2449 name
=> 'migrate_vm',
2450 path
=> '{vmid}/migrate',
2454 description
=> "Migrate virtual machine. Creates a new migration task.",
2456 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2459 additionalProperties
=> 0,
2461 node
=> get_standard_option
('pve-node'),
2462 vmid
=> get_standard_option
('pve-vmid'),
2463 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2466 description
=> "Use online/live migration.",
2471 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2478 description
=> "the task ID.",
2483 my $rpcenv = PVE
::RPCEnvironment
::get
();
2485 my $authuser = $rpcenv->get_user();
2487 my $target = extract_param
($param, 'target');
2489 my $localnode = PVE
::INotify
::nodename
();
2490 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2492 PVE
::Cluster
::check_cfs_quorum
();
2494 PVE
::Cluster
::check_node_exists
($target);
2496 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2498 my $vmid = extract_param
($param, 'vmid');
2500 raise_param_exc
({ force
=> "Only root may use this option." })
2501 if $param->{force
} && $authuser ne 'root@pam';
2504 my $conf = PVE
::QemuServer
::load_config
($vmid);
2506 # try to detect errors early
2508 PVE
::QemuServer
::check_lock
($conf);
2510 if (PVE
::QemuServer
::check_running
($vmid)) {
2511 die "cant migrate running VM without --online\n"
2512 if !$param->{online
};
2515 my $storecfg = PVE
::Storage
::config
();
2516 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2518 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2523 my $service = "pvevm:$vmid";
2525 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2527 print "Executing HA migrate for VM $vmid to node $target\n";
2529 PVE
::Tools
::run_command
($cmd);
2534 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2541 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2544 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2549 __PACKAGE__-
>register_method({
2551 path
=> '{vmid}/monitor',
2555 description
=> "Execute Qemu monitor commands.",
2557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2560 additionalProperties
=> 0,
2562 node
=> get_standard_option
('pve-node'),
2563 vmid
=> get_standard_option
('pve-vmid'),
2566 description
=> "The monitor command.",
2570 returns
=> { type
=> 'string'},
2574 my $vmid = $param->{vmid
};
2576 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2580 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2582 $res = "ERROR: $@" if $@;
2587 __PACKAGE__-
>register_method({
2588 name
=> 'resize_vm',
2589 path
=> '{vmid}/resize',
2593 description
=> "Extend volume size.",
2595 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2598 additionalProperties
=> 0,
2600 node
=> get_standard_option
('pve-node'),
2601 vmid
=> get_standard_option
('pve-vmid'),
2602 skiplock
=> get_standard_option
('skiplock'),
2605 description
=> "The disk you want to resize.",
2606 enum
=> [PVE
::QemuServer
::disknames
()],
2610 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2611 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.",
2615 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2621 returns
=> { type
=> 'null'},
2625 my $rpcenv = PVE
::RPCEnvironment
::get
();
2627 my $authuser = $rpcenv->get_user();
2629 my $node = extract_param
($param, 'node');
2631 my $vmid = extract_param
($param, 'vmid');
2633 my $digest = extract_param
($param, 'digest');
2635 my $disk = extract_param
($param, 'disk');
2637 my $sizestr = extract_param
($param, 'size');
2639 my $skiplock = extract_param
($param, 'skiplock');
2640 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2641 if $skiplock && $authuser ne 'root@pam';
2643 my $storecfg = PVE
::Storage
::config
();
2645 my $updatefn = sub {
2647 my $conf = PVE
::QemuServer
::load_config
($vmid);
2649 die "checksum missmatch (file change by other user?)\n"
2650 if $digest && $digest ne $conf->{digest
};
2651 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2653 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2655 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2657 my $volid = $drive->{file
};
2659 die "disk '$disk' has no associated volume\n" if !$volid;
2661 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2663 die "you can't online resize a virtio windows bootdisk\n"
2664 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2666 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2668 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2670 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2672 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2673 my ($ext, $newsize, $unit) = ($1, $2, $4);
2676 $newsize = $newsize * 1024;
2677 } elsif ($unit eq 'M') {
2678 $newsize = $newsize * 1024 * 1024;
2679 } elsif ($unit eq 'G') {
2680 $newsize = $newsize * 1024 * 1024 * 1024;
2681 } elsif ($unit eq 'T') {
2682 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2685 $newsize += $size if $ext;
2686 $newsize = int($newsize);
2688 die "unable to skrink disk size\n" if $newsize < $size;
2690 return if $size == $newsize;
2692 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2694 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2696 $drive->{size
} = $newsize;
2697 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2699 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2702 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2706 __PACKAGE__-
>register_method({
2707 name
=> 'snapshot_list',
2708 path
=> '{vmid}/snapshot',
2710 description
=> "List all snapshots.",
2712 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2715 protected
=> 1, # qemu pid files are only readable by root
2717 additionalProperties
=> 0,
2719 vmid
=> get_standard_option
('pve-vmid'),
2720 node
=> get_standard_option
('pve-node'),
2729 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2734 my $vmid = $param->{vmid
};
2736 my $conf = PVE
::QemuServer
::load_config
($vmid);
2737 my $snaphash = $conf->{snapshots
} || {};
2741 foreach my $name (keys %$snaphash) {
2742 my $d = $snaphash->{$name};
2745 snaptime
=> $d->{snaptime
} || 0,
2746 vmstate
=> $d->{vmstate
} ?
1 : 0,
2747 description
=> $d->{description
} || '',
2749 $item->{parent
} = $d->{parent
} if $d->{parent
};
2750 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2754 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2755 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2756 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2758 push @$res, $current;
2763 __PACKAGE__-
>register_method({
2765 path
=> '{vmid}/snapshot',
2769 description
=> "Snapshot a VM.",
2771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2774 additionalProperties
=> 0,
2776 node
=> get_standard_option
('pve-node'),
2777 vmid
=> get_standard_option
('pve-vmid'),
2778 snapname
=> get_standard_option
('pve-snapshot-name'),
2782 description
=> "Save the vmstate",
2787 description
=> "Freeze the filesystem",
2792 description
=> "A textual description or comment.",
2798 description
=> "the task ID.",
2803 my $rpcenv = PVE
::RPCEnvironment
::get
();
2805 my $authuser = $rpcenv->get_user();
2807 my $node = extract_param
($param, 'node');
2809 my $vmid = extract_param
($param, 'vmid');
2811 my $snapname = extract_param
($param, 'snapname');
2813 die "unable to use snapshot name 'current' (reserved name)\n"
2814 if $snapname eq 'current';
2817 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2818 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2819 $param->{freezefs
}, $param->{description
});
2822 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2825 __PACKAGE__-
>register_method({
2826 name
=> 'snapshot_cmd_idx',
2827 path
=> '{vmid}/snapshot/{snapname}',
2834 additionalProperties
=> 0,
2836 vmid
=> get_standard_option
('pve-vmid'),
2837 node
=> get_standard_option
('pve-node'),
2838 snapname
=> get_standard_option
('pve-snapshot-name'),
2847 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2854 push @$res, { cmd
=> 'rollback' };
2855 push @$res, { cmd
=> 'config' };
2860 __PACKAGE__-
>register_method({
2861 name
=> 'update_snapshot_config',
2862 path
=> '{vmid}/snapshot/{snapname}/config',
2866 description
=> "Update snapshot metadata.",
2868 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2871 additionalProperties
=> 0,
2873 node
=> get_standard_option
('pve-node'),
2874 vmid
=> get_standard_option
('pve-vmid'),
2875 snapname
=> get_standard_option
('pve-snapshot-name'),
2879 description
=> "A textual description or comment.",
2883 returns
=> { type
=> 'null' },
2887 my $rpcenv = PVE
::RPCEnvironment
::get
();
2889 my $authuser = $rpcenv->get_user();
2891 my $vmid = extract_param
($param, 'vmid');
2893 my $snapname = extract_param
($param, 'snapname');
2895 return undef if !defined($param->{description
});
2897 my $updatefn = sub {
2899 my $conf = PVE
::QemuServer
::load_config
($vmid);
2901 PVE
::QemuServer
::check_lock
($conf);
2903 my $snap = $conf->{snapshots
}->{$snapname};
2905 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2907 $snap->{description
} = $param->{description
} if defined($param->{description
});
2909 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2912 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2917 __PACKAGE__-
>register_method({
2918 name
=> 'get_snapshot_config',
2919 path
=> '{vmid}/snapshot/{snapname}/config',
2922 description
=> "Get snapshot configuration",
2924 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2927 additionalProperties
=> 0,
2929 node
=> get_standard_option
('pve-node'),
2930 vmid
=> get_standard_option
('pve-vmid'),
2931 snapname
=> get_standard_option
('pve-snapshot-name'),
2934 returns
=> { type
=> "object" },
2938 my $rpcenv = PVE
::RPCEnvironment
::get
();
2940 my $authuser = $rpcenv->get_user();
2942 my $vmid = extract_param
($param, 'vmid');
2944 my $snapname = extract_param
($param, 'snapname');
2946 my $conf = PVE
::QemuServer
::load_config
($vmid);
2948 my $snap = $conf->{snapshots
}->{$snapname};
2950 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2955 __PACKAGE__-
>register_method({
2957 path
=> '{vmid}/snapshot/{snapname}/rollback',
2961 description
=> "Rollback VM state to specified snapshot.",
2963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2966 additionalProperties
=> 0,
2968 node
=> get_standard_option
('pve-node'),
2969 vmid
=> get_standard_option
('pve-vmid'),
2970 snapname
=> get_standard_option
('pve-snapshot-name'),
2975 description
=> "the task ID.",
2980 my $rpcenv = PVE
::RPCEnvironment
::get
();
2982 my $authuser = $rpcenv->get_user();
2984 my $node = extract_param
($param, 'node');
2986 my $vmid = extract_param
($param, 'vmid');
2988 my $snapname = extract_param
($param, 'snapname');
2991 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2992 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2995 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2998 __PACKAGE__-
>register_method({
2999 name
=> 'delsnapshot',
3000 path
=> '{vmid}/snapshot/{snapname}',
3004 description
=> "Delete a VM snapshot.",
3006 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3009 additionalProperties
=> 0,
3011 node
=> get_standard_option
('pve-node'),
3012 vmid
=> get_standard_option
('pve-vmid'),
3013 snapname
=> get_standard_option
('pve-snapshot-name'),
3017 description
=> "For removal from config file, even if removing disk snapshots fails.",
3023 description
=> "the task ID.",
3028 my $rpcenv = PVE
::RPCEnvironment
::get
();
3030 my $authuser = $rpcenv->get_user();
3032 my $node = extract_param
($param, 'node');
3034 my $vmid = extract_param
($param, 'vmid');
3036 my $snapname = extract_param
($param, 'snapname');
3039 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3040 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3043 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3046 __PACKAGE__-
>register_method({
3048 path
=> '{vmid}/template',
3052 description
=> "Create a Template.",
3054 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3055 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3058 additionalProperties
=> 0,
3060 node
=> get_standard_option
('pve-node'),
3061 vmid
=> get_standard_option
('pve-vmid'),
3065 description
=> "If you want to convert only 1 disk to base image.",
3066 enum
=> [PVE
::QemuServer
::disknames
()],
3071 returns
=> { type
=> 'null'},
3075 my $rpcenv = PVE
::RPCEnvironment
::get
();
3077 my $authuser = $rpcenv->get_user();
3079 my $node = extract_param
($param, 'node');
3081 my $vmid = extract_param
($param, 'vmid');
3083 my $disk = extract_param
($param, 'disk');
3085 my $updatefn = sub {
3087 my $conf = PVE
::QemuServer
::load_config
($vmid);
3089 PVE
::QemuServer
::check_lock
($conf);
3091 die "unable to create template, because VM contains snapshots\n"
3092 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3094 die "you can't convert a template to a template\n"
3095 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3097 die "you can't convert a VM to template if VM is running\n"
3098 if PVE
::QemuServer
::check_running
($vmid);
3101 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3104 $conf->{template
} = 1;
3105 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3107 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3110 PVE
::QemuServer
::lock_config
($vmid, $updatefn);