1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 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.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_copy = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf) = @_;
65 PVE
::QemuServer
::foreach_drive
($conf, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
72 return if !$volid || $volid eq 'none';
75 if ($volid eq 'cdrom') {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
82 die "unable to copy arbitrary files\n" if !$sid;
83 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
88 # Note: $pool is only needed when creating a VM, because pool permissions
89 # are automatically inherited if VM already exists inside a pool.
90 my $create_disks = sub {
91 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
96 PVE
::QemuServer
::foreach_drive
($settings, sub {
99 my $volid = $disk->{file
};
101 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
102 delete $disk->{size
};
103 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
104 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
105 my ($storeid, $size) = ($2 || $default_storage, $3);
106 die "no storage ID specified (and no default storage)\n" if !$storeid;
107 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
108 my $fmt = $disk->{format
} || $defformat;
109 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
110 $fmt, undef, $size*1024*1024);
111 $disk->{file
} = $volid;
112 $disk->{size
} = $size*1024*1024*1024;
113 push @$vollist, $volid;
114 delete $disk->{format
}; # no longer needed
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
120 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
122 my $foundvolid = undef;
125 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
126 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
128 PVE
::Storage
::foreach_volid
($dl, sub {
130 if($volumeid eq $volid) {
137 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
139 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
140 $disk->{size
} = $size;
141 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
145 # free allocated images on error
147 syslog
('err', "VM $vmid creating disks failed");
148 foreach my $volid (@$vollist) {
149 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
155 # modify vm config if everything went well
156 foreach my $ds (keys %$res) {
157 $conf->{$ds} = $res->{$ds};
163 my $check_vm_modify_config_perm = sub {
164 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
166 return 1 if $authuser eq 'root@pam';
168 foreach my $opt (@$key_list) {
169 # disk checks need to be done somewhere else
170 next if PVE
::QemuServer
::valid_drivename
($opt);
172 if ($opt eq 'sockets' || $opt eq 'cores' ||
173 $opt eq 'cpu' || $opt eq 'smp' ||
174 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
175 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
176 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
177 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
178 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
179 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
180 } elsif ($opt eq 'args' || $opt eq 'lock') {
181 die "only root can set '$opt' config\n";
182 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
183 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
184 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
185 } elsif ($opt =~ m/^net\d+$/) {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
195 __PACKAGE__-
>register_method({
199 description
=> "Virtual machine index (per node).",
201 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
205 protected
=> 1, # qemu pid files are only readable by root
207 additionalProperties
=> 0,
209 node
=> get_standard_option
('pve-node'),
218 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
223 my $rpcenv = PVE
::RPCEnvironment
::get
();
224 my $authuser = $rpcenv->get_user();
226 my $vmstatus = PVE
::QemuServer
::vmstatus
();
229 foreach my $vmid (keys %$vmstatus) {
230 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
232 my $data = $vmstatus->{$vmid};
233 $data->{vmid
} = $vmid;
240 __PACKAGE__-
>register_method({
244 description
=> "Create or restore a virtual machine.",
246 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
248 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
249 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
255 additionalProperties
=> 0,
256 properties
=> PVE
::QemuServer
::json_config_properties
(
258 node
=> get_standard_option
('pve-node'),
259 vmid
=> get_standard_option
('pve-vmid'),
261 description
=> "The backup file.",
266 storage
=> get_standard_option
('pve-storage-id', {
267 description
=> "Default storage.",
273 description
=> "Allow to overwrite existing VM.",
274 requires
=> 'archive',
279 description
=> "Assign a unique random ethernet address.",
280 requires
=> 'archive',
284 type
=> 'string', format
=> 'pve-poolid',
285 description
=> "Add the VM to the specified pool.",
295 my $rpcenv = PVE
::RPCEnvironment
::get
();
297 my $authuser = $rpcenv->get_user();
299 my $node = extract_param
($param, 'node');
301 my $vmid = extract_param
($param, 'vmid');
303 my $archive = extract_param
($param, 'archive');
305 my $storage = extract_param
($param, 'storage');
307 my $force = extract_param
($param, 'force');
309 my $unique = extract_param
($param, 'unique');
311 my $pool = extract_param
($param, 'pool');
313 my $filename = PVE
::QemuServer
::config_file
($vmid);
315 my $storecfg = PVE
::Storage
::config
();
317 PVE
::Cluster
::check_cfs_quorum
();
319 if (defined($pool)) {
320 $rpcenv->check_pool_exist($pool);
323 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
324 if defined($storage);
327 &$resolve_cdrom_alias($param);
329 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
331 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
333 foreach my $opt (keys %$param) {
334 if (PVE
::QemuServer
::valid_drivename
($opt)) {
335 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
336 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
338 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
339 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
343 PVE
::QemuServer
::add_random_macs
($param);
345 my $keystr = join(' ', keys %$param);
346 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
348 if ($archive eq '-') {
349 die "pipe requires cli environment\n"
350 if $rpcenv->{type
} ne 'cli';
352 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
354 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
355 if PVE
::Storage
::parse_volume_id
($archive, 1);
357 die "can't find archive file '$archive'\n" if !($path && -f
$path);
362 my $addVMtoPoolFn = sub {
363 my $usercfg = cfs_read_file
("user.cfg");
364 if (my $data = $usercfg->{pools
}->{$pool}) {
365 $data->{vms
}->{$vmid} = 1;
366 $usercfg->{vms
}->{$vmid} = $pool;
367 cfs_write_file
("user.cfg", $usercfg);
371 my $restorefn = sub {
373 # fixme: this test does not work if VM exists on other node!
375 die "unable to restore vm $vmid: config file already exists\n"
378 die "unable to restore vm $vmid: vm is running\n"
379 if PVE
::QemuServer
::check_running
($vmid);
383 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
386 unique
=> $unique });
388 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
391 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
397 die "unable to create vm $vmid: config file already exists\n"
408 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
410 # try to be smart about bootdisk
411 my @disks = PVE
::QemuServer
::disknames
();
413 foreach my $ds (reverse @disks) {
414 next if !$conf->{$ds};
415 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
416 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
420 if (!$conf->{bootdisk
} && $firstdisk) {
421 $conf->{bootdisk
} = $firstdisk;
424 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
430 foreach my $volid (@$vollist) {
431 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
434 die "create failed - $err";
437 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
440 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
443 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
446 __PACKAGE__-
>register_method({
451 description
=> "Directory index",
456 additionalProperties
=> 0,
458 node
=> get_standard_option
('pve-node'),
459 vmid
=> get_standard_option
('pve-vmid'),
467 subdir
=> { type
=> 'string' },
470 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
476 { subdir
=> 'config' },
477 { subdir
=> 'status' },
478 { subdir
=> 'unlink' },
479 { subdir
=> 'vncproxy' },
480 { subdir
=> 'migrate' },
481 { subdir
=> 'resize' },
483 { subdir
=> 'rrddata' },
484 { subdir
=> 'monitor' },
485 { subdir
=> 'snapshot' },
491 __PACKAGE__-
>register_method({
493 path
=> '{vmid}/rrd',
495 protected
=> 1, # fixme: can we avoid that?
497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
499 description
=> "Read VM RRD statistics (returns PNG)",
501 additionalProperties
=> 0,
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid'),
506 description
=> "Specify the time frame you are interested in.",
508 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
511 description
=> "The list of datasources you want to display.",
512 type
=> 'string', format
=> 'pve-configid-list',
515 description
=> "The RRD consolidation function",
517 enum
=> [ 'AVERAGE', 'MAX' ],
525 filename
=> { type
=> 'string' },
531 return PVE
::Cluster
::create_rrd_graph
(
532 "pve2-vm/$param->{vmid}", $param->{timeframe
},
533 $param->{ds
}, $param->{cf
});
537 __PACKAGE__-
>register_method({
539 path
=> '{vmid}/rrddata',
541 protected
=> 1, # fixme: can we avoid that?
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
545 description
=> "Read VM RRD statistics",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "Specify the time frame you are interested in.",
554 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
557 description
=> "The RRD consolidation function",
559 enum
=> [ 'AVERAGE', 'MAX' ],
574 return PVE
::Cluster
::create_rrd_data
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
579 __PACKAGE__-
>register_method({
581 path
=> '{vmid}/config',
584 description
=> "Get virtual machine configuration.",
586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
589 additionalProperties
=> 0,
591 node
=> get_standard_option
('pve-node'),
592 vmid
=> get_standard_option
('pve-vmid'),
600 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
607 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
609 delete $conf->{snapshots
};
614 my $vm_is_volid_owner = sub {
615 my ($storecfg, $vmid, $volid) =@_;
617 if ($volid !~ m
|^/|) {
619 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
620 if ($owner && ($owner == $vmid)) {
628 my $test_deallocate_drive = sub {
629 my ($storecfg, $vmid, $key, $drive, $force) = @_;
631 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
632 my $volid = $drive->{file
};
633 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
634 if ($force || $key =~ m/^unused/) {
635 my $sid = PVE
::Storage
::parse_volume_id
($volid);
644 my $delete_drive = sub {
645 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
647 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
648 my $volid = $drive->{file
};
649 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
650 if ($force || $key =~ m/^unused/) {
651 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
654 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
659 delete $conf->{$key};
662 my $vmconfig_delete_option = sub {
663 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
665 return if !defined($conf->{$opt});
667 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
670 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
672 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
673 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
674 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
678 my $unplugwarning = "";
679 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
680 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
681 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
682 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
683 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
684 $unplugwarning = "<br>verify that your guest support acpi hotplug";
687 if($opt eq 'tablet'){
688 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
690 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
694 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
695 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
697 delete $conf->{$opt};
700 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
703 my $safe_num_ne = sub {
706 return 0 if !defined($a) && !defined($b);
707 return 1 if !defined($a);
708 return 1 if !defined($b);
713 my $vmconfig_update_disk = sub {
714 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
716 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
718 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
719 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
721 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
726 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
728 my $media = $drive->{media
} || 'disk';
729 my $oldmedia = $old_drive->{media
} || 'disk';
730 die "unable to change media type\n" if $media ne $oldmedia;
732 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
733 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
735 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
736 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
739 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
740 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
741 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
742 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
743 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
744 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
745 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
746 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
747 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
748 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
753 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
754 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
756 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
757 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
759 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
761 if (PVE
::QemuServer
::check_running
($vmid)) {
762 if ($drive->{file
} eq 'none') {
763 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
765 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
766 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
767 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
771 } else { # hotplug new disks
773 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
777 my $vmconfig_update_net = sub {
778 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
780 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
781 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
782 my $newnet = PVE
::QemuServer
::parse_net
($value);
784 if($oldnet->{model
} ne $newnet->{model
}){
785 #if model change, we try to hot-unplug
786 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
789 if($newnet->{bridge
} && $oldnet->{bridge
}){
790 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
792 if($newnet->{rate
} ne $oldnet->{rate
}){
793 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
796 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
797 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
798 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
802 #if bridge/nat mode change, we try to hot-unplug
803 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
808 $conf->{$opt} = $value;
809 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
812 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
814 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
817 my $vm_config_perm_list = [
827 __PACKAGE__-
>register_method({
829 path
=> '{vmid}/config',
833 description
=> "Set virtual machine options.",
835 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
838 additionalProperties
=> 0,
839 properties
=> PVE
::QemuServer
::json_config_properties
(
841 node
=> get_standard_option
('pve-node'),
842 vmid
=> get_standard_option
('pve-vmid'),
843 skiplock
=> get_standard_option
('skiplock'),
845 type
=> 'string', format
=> 'pve-configid-list',
846 description
=> "A list of settings you want to delete.",
851 description
=> $opt_force_description,
853 requires
=> 'delete',
857 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
863 returns
=> { type
=> 'null'},
867 my $rpcenv = PVE
::RPCEnvironment
::get
();
869 my $authuser = $rpcenv->get_user();
871 my $node = extract_param
($param, 'node');
873 my $vmid = extract_param
($param, 'vmid');
875 my $digest = extract_param
($param, 'digest');
877 my @paramarr = (); # used for log message
878 foreach my $key (keys %$param) {
879 push @paramarr, "-$key", $param->{$key};
882 my $skiplock = extract_param
($param, 'skiplock');
883 raise_param_exc
({ skiplock
=> "Only root may use this option." })
884 if $skiplock && $authuser ne 'root@pam';
886 my $delete_str = extract_param
($param, 'delete');
888 my $force = extract_param
($param, 'force');
890 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
892 my $storecfg = PVE
::Storage
::config
();
894 my $defaults = PVE
::QemuServer
::load_defaults
();
896 &$resolve_cdrom_alias($param);
898 # now try to verify all parameters
901 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
902 $opt = 'ide2' if $opt eq 'cdrom';
903 raise_param_exc
({ delete => "you can't use '-$opt' and " .
904 "-delete $opt' at the same time" })
905 if defined($param->{$opt});
907 if (!PVE
::QemuServer
::option_exists
($opt)) {
908 raise_param_exc
({ delete => "unknown option '$opt'" });
914 foreach my $opt (keys %$param) {
915 if (PVE
::QemuServer
::valid_drivename
($opt)) {
917 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
918 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
919 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
920 } elsif ($opt =~ m/^net(\d+)$/) {
922 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
923 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
927 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
929 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
931 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
935 my $conf = PVE
::QemuServer
::load_config
($vmid);
937 die "checksum missmatch (file change by other user?)\n"
938 if $digest && $digest ne $conf->{digest
};
940 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
942 if ($param->{memory
} || defined($param->{balloon
})) {
943 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
944 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
946 die "balloon value too large (must be smaller than assigned memory)\n"
947 if $balloon > $maxmem;
950 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
952 foreach my $opt (@delete) { # delete
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
957 my $running = PVE
::QemuServer
::check_running
($vmid);
959 foreach my $opt (keys %$param) { # add/change
961 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt}, $force);
970 } elsif ($opt =~ m/^net(\d+)$/) { #nics
972 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
973 $opt, $param->{$opt});
977 if($opt eq 'tablet' && $param->{$opt} == 1){
978 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
979 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
980 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
983 $conf->{$opt} = $param->{$opt};
984 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
988 # allow manual ballooning if shares is set to zero
989 if ($running && defined($param->{balloon
}) &&
990 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
991 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
992 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
997 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1003 __PACKAGE__-
>register_method({
1004 name
=> 'destroy_vm',
1009 description
=> "Destroy the vm (also delete all used/owned volumes).",
1011 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1014 additionalProperties
=> 0,
1016 node
=> get_standard_option
('pve-node'),
1017 vmid
=> get_standard_option
('pve-vmid'),
1018 skiplock
=> get_standard_option
('skiplock'),
1027 my $rpcenv = PVE
::RPCEnvironment
::get
();
1029 my $authuser = $rpcenv->get_user();
1031 my $vmid = $param->{vmid
};
1033 my $skiplock = $param->{skiplock
};
1034 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1035 if $skiplock && $authuser ne 'root@pam';
1038 my $conf = PVE
::QemuServer
::load_config
($vmid);
1040 my $storecfg = PVE
::Storage
::config
();
1042 my $delVMfromPoolFn = sub {
1043 my $usercfg = cfs_read_file
("user.cfg");
1044 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1045 if (my $data = $usercfg->{pools
}->{$pool}) {
1046 delete $data->{vms
}->{$vmid};
1047 delete $usercfg->{vms
}->{$vmid};
1048 cfs_write_file
("user.cfg", $usercfg);
1056 syslog
('info', "destroy VM $vmid: $upid\n");
1058 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1060 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1063 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1066 __PACKAGE__-
>register_method({
1068 path
=> '{vmid}/unlink',
1072 description
=> "Unlink/delete disk images.",
1074 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1077 additionalProperties
=> 0,
1079 node
=> get_standard_option
('pve-node'),
1080 vmid
=> get_standard_option
('pve-vmid'),
1082 type
=> 'string', format
=> 'pve-configid-list',
1083 description
=> "A list of disk IDs you want to delete.",
1087 description
=> $opt_force_description,
1092 returns
=> { type
=> 'null'},
1096 $param->{delete} = extract_param
($param, 'idlist');
1098 __PACKAGE__-
>update_vm($param);
1105 __PACKAGE__-
>register_method({
1107 path
=> '{vmid}/vncproxy',
1111 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1113 description
=> "Creates a TCP VNC proxy connections.",
1115 additionalProperties
=> 0,
1117 node
=> get_standard_option
('pve-node'),
1118 vmid
=> get_standard_option
('pve-vmid'),
1122 additionalProperties
=> 0,
1124 user
=> { type
=> 'string' },
1125 ticket
=> { type
=> 'string' },
1126 cert
=> { type
=> 'string' },
1127 port
=> { type
=> 'integer' },
1128 upid
=> { type
=> 'string' },
1134 my $rpcenv = PVE
::RPCEnvironment
::get
();
1136 my $authuser = $rpcenv->get_user();
1138 my $vmid = $param->{vmid
};
1139 my $node = $param->{node
};
1141 my $authpath = "/vms/$vmid";
1143 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1145 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1148 my $port = PVE
::Tools
::next_vnc_port
();
1152 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1153 $remip = PVE
::Cluster
::remote_node_ip
($node);
1156 # NOTE: kvm VNC traffic is already TLS encrypted
1157 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1164 syslog
('info', "starting vnc proxy $upid\n");
1166 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1168 my $qmstr = join(' ', @$qmcmd);
1170 # also redirect stderr (else we get RFB protocol errors)
1171 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1173 PVE
::Tools
::run_command
($cmd);
1178 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1180 PVE
::Tools
::wait_for_vnc_port
($port);
1191 __PACKAGE__-
>register_method({
1193 path
=> '{vmid}/status',
1196 description
=> "Directory index",
1201 additionalProperties
=> 0,
1203 node
=> get_standard_option
('pve-node'),
1204 vmid
=> get_standard_option
('pve-vmid'),
1212 subdir
=> { type
=> 'string' },
1215 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1221 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1224 { subdir
=> 'current' },
1225 { subdir
=> 'start' },
1226 { subdir
=> 'stop' },
1232 my $vm_is_ha_managed = sub {
1235 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1236 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1242 __PACKAGE__-
>register_method({
1243 name
=> 'vm_status',
1244 path
=> '{vmid}/status/current',
1247 protected
=> 1, # qemu pid files are only readable by root
1248 description
=> "Get virtual machine status.",
1250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1253 additionalProperties
=> 0,
1255 node
=> get_standard_option
('pve-node'),
1256 vmid
=> get_standard_option
('pve-vmid'),
1259 returns
=> { type
=> 'object' },
1264 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1266 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1267 my $status = $vmstatus->{$param->{vmid
}};
1269 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1274 __PACKAGE__-
>register_method({
1276 path
=> '{vmid}/status/start',
1280 description
=> "Start virtual machine.",
1282 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1285 additionalProperties
=> 0,
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid'),
1289 skiplock
=> get_standard_option
('skiplock'),
1290 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1291 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1301 my $rpcenv = PVE
::RPCEnvironment
::get
();
1303 my $authuser = $rpcenv->get_user();
1305 my $node = extract_param
($param, 'node');
1307 my $vmid = extract_param
($param, 'vmid');
1309 my $stateuri = extract_param
($param, 'stateuri');
1310 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1311 if $stateuri && $authuser ne 'root@pam';
1313 my $skiplock = extract_param
($param, 'skiplock');
1314 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1315 if $skiplock && $authuser ne 'root@pam';
1317 my $migratedfrom = extract_param
($param, 'migratedfrom');
1318 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1319 if $migratedfrom && $authuser ne 'root@pam';
1321 my $storecfg = PVE
::Storage
::config
();
1323 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1324 $rpcenv->{type
} ne 'ha') {
1329 my $service = "pvevm:$vmid";
1331 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1333 print "Executing HA start for VM $vmid\n";
1335 PVE
::Tools
::run_command
($cmd);
1340 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1347 syslog
('info', "start VM $vmid: $upid\n");
1349 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1354 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1358 __PACKAGE__-
>register_method({
1360 path
=> '{vmid}/status/stop',
1364 description
=> "Stop virtual machine.",
1366 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1369 additionalProperties
=> 0,
1371 node
=> get_standard_option
('pve-node'),
1372 vmid
=> get_standard_option
('pve-vmid'),
1373 skiplock
=> get_standard_option
('skiplock'),
1374 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1376 description
=> "Wait maximal timeout seconds.",
1382 description
=> "Do not decativate storage volumes.",
1395 my $rpcenv = PVE
::RPCEnvironment
::get
();
1397 my $authuser = $rpcenv->get_user();
1399 my $node = extract_param
($param, 'node');
1401 my $vmid = extract_param
($param, 'vmid');
1403 my $skiplock = extract_param
($param, 'skiplock');
1404 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1405 if $skiplock && $authuser ne 'root@pam';
1407 my $keepActive = extract_param
($param, 'keepActive');
1408 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1409 if $keepActive && $authuser ne 'root@pam';
1411 my $migratedfrom = extract_param
($param, 'migratedfrom');
1412 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1413 if $migratedfrom && $authuser ne 'root@pam';
1416 my $storecfg = PVE
::Storage
::config
();
1418 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1423 my $service = "pvevm:$vmid";
1425 my $cmd = ['clusvcadm', '-d', $service];
1427 print "Executing HA stop for VM $vmid\n";
1429 PVE
::Tools
::run_command
($cmd);
1434 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1440 syslog
('info', "stop VM $vmid: $upid\n");
1442 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1443 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1448 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1452 __PACKAGE__-
>register_method({
1454 path
=> '{vmid}/status/reset',
1458 description
=> "Reset virtual machine.",
1460 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1463 additionalProperties
=> 0,
1465 node
=> get_standard_option
('pve-node'),
1466 vmid
=> get_standard_option
('pve-vmid'),
1467 skiplock
=> get_standard_option
('skiplock'),
1476 my $rpcenv = PVE
::RPCEnvironment
::get
();
1478 my $authuser = $rpcenv->get_user();
1480 my $node = extract_param
($param, 'node');
1482 my $vmid = extract_param
($param, 'vmid');
1484 my $skiplock = extract_param
($param, 'skiplock');
1485 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1486 if $skiplock && $authuser ne 'root@pam';
1488 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1493 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1498 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1501 __PACKAGE__-
>register_method({
1502 name
=> 'vm_shutdown',
1503 path
=> '{vmid}/status/shutdown',
1507 description
=> "Shutdown 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'),
1518 description
=> "Wait maximal timeout seconds.",
1524 description
=> "Make sure the VM stops.",
1530 description
=> "Do not decativate storage volumes.",
1543 my $rpcenv = PVE
::RPCEnvironment
::get
();
1545 my $authuser = $rpcenv->get_user();
1547 my $node = extract_param
($param, 'node');
1549 my $vmid = extract_param
($param, 'vmid');
1551 my $skiplock = extract_param
($param, 'skiplock');
1552 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1553 if $skiplock && $authuser ne 'root@pam';
1555 my $keepActive = extract_param
($param, 'keepActive');
1556 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1557 if $keepActive && $authuser ne 'root@pam';
1559 my $storecfg = PVE
::Storage
::config
();
1564 syslog
('info', "shutdown VM $vmid: $upid\n");
1566 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1567 1, $param->{forceStop
}, $keepActive);
1572 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1575 __PACKAGE__-
>register_method({
1576 name
=> 'vm_suspend',
1577 path
=> '{vmid}/status/suspend',
1581 description
=> "Suspend virtual machine.",
1583 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1586 additionalProperties
=> 0,
1588 node
=> get_standard_option
('pve-node'),
1589 vmid
=> get_standard_option
('pve-vmid'),
1590 skiplock
=> get_standard_option
('skiplock'),
1599 my $rpcenv = PVE
::RPCEnvironment
::get
();
1601 my $authuser = $rpcenv->get_user();
1603 my $node = extract_param
($param, 'node');
1605 my $vmid = extract_param
($param, 'vmid');
1607 my $skiplock = extract_param
($param, 'skiplock');
1608 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1609 if $skiplock && $authuser ne 'root@pam';
1611 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1616 syslog
('info', "suspend VM $vmid: $upid\n");
1618 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1623 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1626 __PACKAGE__-
>register_method({
1627 name
=> 'vm_resume',
1628 path
=> '{vmid}/status/resume',
1632 description
=> "Resume virtual machine.",
1634 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1637 additionalProperties
=> 0,
1639 node
=> get_standard_option
('pve-node'),
1640 vmid
=> get_standard_option
('pve-vmid'),
1641 skiplock
=> get_standard_option
('skiplock'),
1650 my $rpcenv = PVE
::RPCEnvironment
::get
();
1652 my $authuser = $rpcenv->get_user();
1654 my $node = extract_param
($param, 'node');
1656 my $vmid = extract_param
($param, 'vmid');
1658 my $skiplock = extract_param
($param, 'skiplock');
1659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1660 if $skiplock && $authuser ne 'root@pam';
1662 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1667 syslog
('info', "resume VM $vmid: $upid\n");
1669 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1674 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1677 __PACKAGE__-
>register_method({
1678 name
=> 'vm_sendkey',
1679 path
=> '{vmid}/sendkey',
1683 description
=> "Send key event to virtual machine.",
1685 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid'),
1692 skiplock
=> get_standard_option
('skiplock'),
1694 description
=> "The key (qemu monitor encoding).",
1699 returns
=> { type
=> 'null'},
1703 my $rpcenv = PVE
::RPCEnvironment
::get
();
1705 my $authuser = $rpcenv->get_user();
1707 my $node = extract_param
($param, 'node');
1709 my $vmid = extract_param
($param, 'vmid');
1711 my $skiplock = extract_param
($param, 'skiplock');
1712 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1713 if $skiplock && $authuser ne 'root@pam';
1715 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1720 __PACKAGE__-
>register_method({
1721 name
=> 'vm_feature',
1722 path
=> '{vmid}/feature',
1726 description
=> "Check if feature for virtual machine is available.",
1728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1731 additionalProperties
=> 0,
1733 node
=> get_standard_option
('pve-node'),
1734 vmid
=> get_standard_option
('pve-vmid'),
1736 description
=> "Feature to check.",
1738 enum
=> [ 'snapshot', 'clone' ],
1740 snapname
=> get_standard_option
('pve-snapshot-name', {
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $snapname = extract_param
($param, 'snapname');
1758 my $feature = extract_param
($param, 'feature');
1760 my $running = PVE
::QemuServer
::check_running
($vmid);
1762 my $conf = PVE
::QemuServer
::load_config
($vmid);
1765 my $snap = $conf->{snapshots
}->{$snapname};
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1769 my $storecfg = PVE
::Storage
::config
();
1771 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1772 my $res = $hasfeature ?
1 : 0 ;
1776 __PACKAGE__-
>register_method({
1778 path
=> '{vmid}/copy',
1782 description
=> "Creat a copy of virtual machine/template.",
1784 description
=> "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1785 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1786 "'Datastore.AllocateSpace' on any used storage.",
1789 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1791 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1792 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1797 additionalProperties
=> 0,
1799 # fixme: add other parameters like name and description?
1800 node
=> get_standard_option
('pve-node'),
1801 vmid
=> get_standard_option
('pve-vmid'),
1802 newid
=> get_standard_option
('pve-vmid', {
1803 description
=> 'VMID for the copy.' }),
1806 type
=> 'string', format
=> 'pve-poolid',
1807 description
=> "Add the new VM to the specified pool.",
1812 description
=> "Create a full copy of all disk. This is always done when " .
1813 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1824 my $rpcenv = PVE
::RPCEnvironment
::get
();
1826 my $authuser = $rpcenv->get_user();
1828 my $node = extract_param
($param, 'node');
1830 my $vmid = extract_param
($param, 'vmid');
1832 my $newid = extract_param
($param, 'newid');
1834 # fixme: update pool after create
1835 my $pool = extract_param
($param, 'pool');
1837 if (defined($pool)) {
1838 $rpcenv->check_pool_exist($pool);
1841 my $storecfg = PVE
::Storage
::config
();
1843 PVE
::Cluster
::check_cfs_quorum
();
1845 # fixme: do early checks - re-check after lock
1847 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1851 # all tests after lock
1852 my $conf = PVE
::QemuServer
::load_config
($vmid);
1854 PVE
::QemuServer
::check_lock
($conf);
1856 my $running = PVE
::QemuServer
::check_running
($vmid);
1858 die "Copy running VM $vmid not implemented\n" if $running;
1860 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf);
1862 # fixme: snapshots??
1864 my $conffile = PVE
::QemuServer
::config_file
($newid);
1866 die "unable to create VM $newid: config file already exists\n"
1869 # create empty/temp config - this fails if VM already exists on other node
1870 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\n");
1876 print "COPY VM $vmid start\n";
1881 foreach my $opt (keys %$conf) {
1882 my $value = $conf->{$opt};
1884 # always change MAC! address
1885 if ($opt =~ m/^net(\d+)$/) {
1886 my $net = PVE
::QemuServer
::parse_net
($value);
1887 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1888 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1889 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1890 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1891 $newconf->{$opt} = $value; # simply copy configuration
1893 $drives->{$opt} = $drive;
1894 push @$vollist, $drive->{file
};
1897 # copy everything else
1898 $newconf->{$opt} = $value;
1902 delete $newconf->{template
};
1904 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1906 foreach my $opt (keys %$drives) {
1907 my $drive = $drives->{$opt};
1908 print "COPY drive $opt: $drive->{file}\n";
1913 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1915 print "COPY VM $vmid end\n";
1918 # fixme: remove all created files
1921 die "copy failed: $err";
1927 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
1930 # Aquire shared lock for $vmid
1931 return PVE
::QemuServer
::lock_config_shared
($vmid, 1, sub {
1932 # Aquire exclusive lock lock for $newid
1933 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
1938 __PACKAGE__-
>register_method({
1939 name
=> 'migrate_vm',
1940 path
=> '{vmid}/migrate',
1944 description
=> "Migrate virtual machine. Creates a new migration task.",
1946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1949 additionalProperties
=> 0,
1951 node
=> get_standard_option
('pve-node'),
1952 vmid
=> get_standard_option
('pve-vmid'),
1953 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1956 description
=> "Use online/live migration.",
1961 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1968 description
=> "the task ID.",
1973 my $rpcenv = PVE
::RPCEnvironment
::get
();
1975 my $authuser = $rpcenv->get_user();
1977 my $target = extract_param
($param, 'target');
1979 my $localnode = PVE
::INotify
::nodename
();
1980 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1982 PVE
::Cluster
::check_cfs_quorum
();
1984 PVE
::Cluster
::check_node_exists
($target);
1986 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1988 my $vmid = extract_param
($param, 'vmid');
1990 raise_param_exc
({ force
=> "Only root may use this option." })
1991 if $param->{force
} && $authuser ne 'root@pam';
1994 my $conf = PVE
::QemuServer
::load_config
($vmid);
1996 # try to detect errors early
1998 PVE
::QemuServer
::check_lock
($conf);
2000 if (PVE
::QemuServer
::check_running
($vmid)) {
2001 die "cant migrate running VM without --online\n"
2002 if !$param->{online
};
2005 my $storecfg = PVE
::Storage
::config
();
2006 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2008 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2013 my $service = "pvevm:$vmid";
2015 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2017 print "Executing HA migrate for VM $vmid to node $target\n";
2019 PVE
::Tools
::run_command
($cmd);
2024 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2031 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2034 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2039 __PACKAGE__-
>register_method({
2041 path
=> '{vmid}/monitor',
2045 description
=> "Execute Qemu monitor commands.",
2047 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2050 additionalProperties
=> 0,
2052 node
=> get_standard_option
('pve-node'),
2053 vmid
=> get_standard_option
('pve-vmid'),
2056 description
=> "The monitor command.",
2060 returns
=> { type
=> 'string'},
2064 my $vmid = $param->{vmid
};
2066 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2070 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2072 $res = "ERROR: $@" if $@;
2077 __PACKAGE__-
>register_method({
2078 name
=> 'resize_vm',
2079 path
=> '{vmid}/resize',
2083 description
=> "Extend volume size.",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid'),
2092 skiplock
=> get_standard_option
('skiplock'),
2095 description
=> "The disk you want to resize.",
2096 enum
=> [PVE
::QemuServer
::disknames
()],
2100 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2101 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.",
2105 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2111 returns
=> { type
=> 'null'},
2115 my $rpcenv = PVE
::RPCEnvironment
::get
();
2117 my $authuser = $rpcenv->get_user();
2119 my $node = extract_param
($param, 'node');
2121 my $vmid = extract_param
($param, 'vmid');
2123 my $digest = extract_param
($param, 'digest');
2125 my $disk = extract_param
($param, 'disk');
2127 my $sizestr = extract_param
($param, 'size');
2129 my $skiplock = extract_param
($param, 'skiplock');
2130 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2131 if $skiplock && $authuser ne 'root@pam';
2133 my $storecfg = PVE
::Storage
::config
();
2135 my $updatefn = sub {
2137 my $conf = PVE
::QemuServer
::load_config
($vmid);
2139 die "checksum missmatch (file change by other user?)\n"
2140 if $digest && $digest ne $conf->{digest
};
2141 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2143 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2145 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2147 my $volid = $drive->{file
};
2149 die "disk '$disk' has no associated volume\n" if !$volid;
2151 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2153 die "you can't online resize a virtio windows bootdisk\n"
2154 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2156 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2158 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2160 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2162 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2163 my ($ext, $newsize, $unit) = ($1, $2, $4);
2166 $newsize = $newsize * 1024;
2167 } elsif ($unit eq 'M') {
2168 $newsize = $newsize * 1024 * 1024;
2169 } elsif ($unit eq 'G') {
2170 $newsize = $newsize * 1024 * 1024 * 1024;
2171 } elsif ($unit eq 'T') {
2172 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2175 $newsize += $size if $ext;
2176 $newsize = int($newsize);
2178 die "unable to skrink disk size\n" if $newsize < $size;
2180 return if $size == $newsize;
2182 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2184 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2186 $drive->{size
} = $newsize;
2187 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2189 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2192 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2196 __PACKAGE__-
>register_method({
2197 name
=> 'snapshot_list',
2198 path
=> '{vmid}/snapshot',
2200 description
=> "List all snapshots.",
2202 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2205 protected
=> 1, # qemu pid files are only readable by root
2207 additionalProperties
=> 0,
2209 vmid
=> get_standard_option
('pve-vmid'),
2210 node
=> get_standard_option
('pve-node'),
2219 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2224 my $vmid = $param->{vmid
};
2226 my $conf = PVE
::QemuServer
::load_config
($vmid);
2227 my $snaphash = $conf->{snapshots
} || {};
2231 foreach my $name (keys %$snaphash) {
2232 my $d = $snaphash->{$name};
2235 snaptime
=> $d->{snaptime
} || 0,
2236 vmstate
=> $d->{vmstate
} ?
1 : 0,
2237 description
=> $d->{description
} || '',
2239 $item->{parent
} = $d->{parent
} if $d->{parent
};
2240 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2244 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2245 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2246 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2248 push @$res, $current;
2253 __PACKAGE__-
>register_method({
2255 path
=> '{vmid}/snapshot',
2259 description
=> "Snapshot a VM.",
2261 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2264 additionalProperties
=> 0,
2266 node
=> get_standard_option
('pve-node'),
2267 vmid
=> get_standard_option
('pve-vmid'),
2268 snapname
=> get_standard_option
('pve-snapshot-name'),
2272 description
=> "Save the vmstate",
2277 description
=> "Freeze the filesystem",
2282 description
=> "A textual description or comment.",
2288 description
=> "the task ID.",
2293 my $rpcenv = PVE
::RPCEnvironment
::get
();
2295 my $authuser = $rpcenv->get_user();
2297 my $node = extract_param
($param, 'node');
2299 my $vmid = extract_param
($param, 'vmid');
2301 my $snapname = extract_param
($param, 'snapname');
2303 die "unable to use snapshot name 'current' (reserved name)\n"
2304 if $snapname eq 'current';
2307 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2308 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2309 $param->{freezefs
}, $param->{description
});
2312 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2315 __PACKAGE__-
>register_method({
2316 name
=> 'snapshot_cmd_idx',
2317 path
=> '{vmid}/snapshot/{snapname}',
2324 additionalProperties
=> 0,
2326 vmid
=> get_standard_option
('pve-vmid'),
2327 node
=> get_standard_option
('pve-node'),
2328 snapname
=> get_standard_option
('pve-snapshot-name'),
2337 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2344 push @$res, { cmd
=> 'rollback' };
2345 push @$res, { cmd
=> 'config' };
2350 __PACKAGE__-
>register_method({
2351 name
=> 'update_snapshot_config',
2352 path
=> '{vmid}/snapshot/{snapname}/config',
2356 description
=> "Update snapshot metadata.",
2358 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2361 additionalProperties
=> 0,
2363 node
=> get_standard_option
('pve-node'),
2364 vmid
=> get_standard_option
('pve-vmid'),
2365 snapname
=> get_standard_option
('pve-snapshot-name'),
2369 description
=> "A textual description or comment.",
2373 returns
=> { type
=> 'null' },
2377 my $rpcenv = PVE
::RPCEnvironment
::get
();
2379 my $authuser = $rpcenv->get_user();
2381 my $vmid = extract_param
($param, 'vmid');
2383 my $snapname = extract_param
($param, 'snapname');
2385 return undef if !defined($param->{description
});
2387 my $updatefn = sub {
2389 my $conf = PVE
::QemuServer
::load_config
($vmid);
2391 PVE
::QemuServer
::check_lock
($conf);
2393 my $snap = $conf->{snapshots
}->{$snapname};
2395 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2397 $snap->{description
} = $param->{description
} if defined($param->{description
});
2399 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2402 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2407 __PACKAGE__-
>register_method({
2408 name
=> 'get_snapshot_config',
2409 path
=> '{vmid}/snapshot/{snapname}/config',
2412 description
=> "Get snapshot configuration",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid'),
2421 snapname
=> get_standard_option
('pve-snapshot-name'),
2424 returns
=> { type
=> "object" },
2428 my $rpcenv = PVE
::RPCEnvironment
::get
();
2430 my $authuser = $rpcenv->get_user();
2432 my $vmid = extract_param
($param, 'vmid');
2434 my $snapname = extract_param
($param, 'snapname');
2436 my $conf = PVE
::QemuServer
::load_config
($vmid);
2438 my $snap = $conf->{snapshots
}->{$snapname};
2440 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2445 __PACKAGE__-
>register_method({
2447 path
=> '{vmid}/snapshot/{snapname}/rollback',
2451 description
=> "Rollback VM state to specified snapshot.",
2453 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2456 additionalProperties
=> 0,
2458 node
=> get_standard_option
('pve-node'),
2459 vmid
=> get_standard_option
('pve-vmid'),
2460 snapname
=> get_standard_option
('pve-snapshot-name'),
2465 description
=> "the task ID.",
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $node = extract_param
($param, 'node');
2476 my $vmid = extract_param
($param, 'vmid');
2478 my $snapname = extract_param
($param, 'snapname');
2481 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2482 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2485 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2488 __PACKAGE__-
>register_method({
2489 name
=> 'delsnapshot',
2490 path
=> '{vmid}/snapshot/{snapname}',
2494 description
=> "Delete a VM snapshot.",
2496 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2499 additionalProperties
=> 0,
2501 node
=> get_standard_option
('pve-node'),
2502 vmid
=> get_standard_option
('pve-vmid'),
2503 snapname
=> get_standard_option
('pve-snapshot-name'),
2507 description
=> "For removal from config file, even if removing disk snapshots fails.",
2513 description
=> "the task ID.",
2518 my $rpcenv = PVE
::RPCEnvironment
::get
();
2520 my $authuser = $rpcenv->get_user();
2522 my $node = extract_param
($param, 'node');
2524 my $vmid = extract_param
($param, 'vmid');
2526 my $snapname = extract_param
($param, 'snapname');
2529 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2530 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2533 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2536 __PACKAGE__-
>register_method({
2538 path
=> '{vmid}/template',
2542 description
=> "Create a Template.",
2544 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2546 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2547 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2551 additionalProperties
=> 0,
2553 node
=> get_standard_option
('pve-node'),
2554 vmid
=> get_standard_option
('pve-vmid'),
2558 description
=> "If you want to convert only 1 disk to base image.",
2559 enum
=> [PVE
::QemuServer
::disknames
()],
2564 returns
=> { type
=> 'null'},
2568 my $rpcenv = PVE
::RPCEnvironment
::get
();
2570 my $authuser = $rpcenv->get_user();
2572 my $node = extract_param
($param, 'node');
2574 my $vmid = extract_param
($param, 'vmid');
2576 my $disk = extract_param
($param, 'disk');
2578 my $updatefn = sub {
2580 my $conf = PVE
::QemuServer
::load_config
($vmid);
2582 PVE
::QemuServer
::check_lock
($conf);
2584 die "unable to create template, because VM contains snapshots\n"
2585 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2587 die "you can't convert a template to a template\n"
2588 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2590 die "you can't convert a VM to template if VM is running\n"
2591 if PVE
::QemuServer
::check_running
($vmid);
2594 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2597 $conf->{template
} = 1;
2598 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2600 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2603 PVE
::QemuServer
::lock_config
($vmid, $updatefn);