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, $storage) = @_;
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 $sid = $storage if $storage;
84 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
89 # Note: $pool is only needed when creating a VM, because pool permissions
90 # are automatically inherited if VM already exists inside a pool.
91 my $create_disks = sub {
92 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
97 PVE
::QemuServer
::foreach_drive
($settings, sub {
100 my $volid = $disk->{file
};
102 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
103 delete $disk->{size
};
104 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
105 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
106 my ($storeid, $size) = ($2 || $default_storage, $3);
107 die "no storage ID specified (and no default storage)\n" if !$storeid;
108 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
109 my $fmt = $disk->{format
} || $defformat;
110 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
111 $fmt, undef, $size*1024*1024);
112 $disk->{file
} = $volid;
113 $disk->{size
} = $size*1024*1024*1024;
114 push @$vollist, $volid;
115 delete $disk->{format
}; # no longer needed
116 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
121 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
123 my $foundvolid = undef;
126 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
127 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
129 PVE
::Storage
::foreach_volid
($dl, sub {
131 if($volumeid eq $volid) {
138 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
140 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
141 $disk->{size
} = $size;
142 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
146 # free allocated images on error
148 syslog
('err', "VM $vmid creating disks failed");
149 foreach my $volid (@$vollist) {
150 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
156 # modify vm config if everything went well
157 foreach my $ds (keys %$res) {
158 $conf->{$ds} = $res->{$ds};
164 my $check_vm_modify_config_perm = sub {
165 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
167 return 1 if $authuser eq 'root@pam';
169 foreach my $opt (@$key_list) {
170 # disk checks need to be done somewhere else
171 next if PVE
::QemuServer
::valid_drivename
($opt);
173 if ($opt eq 'sockets' || $opt eq 'cores' ||
174 $opt eq 'cpu' || $opt eq 'smp' ||
175 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
176 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
177 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
178 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
179 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
180 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
181 } elsif ($opt eq 'args' || $opt eq 'lock') {
182 die "only root can set '$opt' config\n";
183 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
184 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
186 } elsif ($opt =~ m/^net\d+$/) {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
196 __PACKAGE__-
>register_method({
200 description
=> "Virtual machine index (per node).",
202 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
206 protected
=> 1, # qemu pid files are only readable by root
208 additionalProperties
=> 0,
210 node
=> get_standard_option
('pve-node'),
219 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
224 my $rpcenv = PVE
::RPCEnvironment
::get
();
225 my $authuser = $rpcenv->get_user();
227 my $vmstatus = PVE
::QemuServer
::vmstatus
();
230 foreach my $vmid (keys %$vmstatus) {
231 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
233 my $data = $vmstatus->{$vmid};
234 $data->{vmid
} = $vmid;
241 __PACKAGE__-
>register_method({
245 description
=> "Create or restore a virtual machine.",
247 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.",
249 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
250 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
256 additionalProperties
=> 0,
257 properties
=> PVE
::QemuServer
::json_config_properties
(
259 node
=> get_standard_option
('pve-node'),
260 vmid
=> get_standard_option
('pve-vmid'),
262 description
=> "The backup file.",
267 storage
=> get_standard_option
('pve-storage-id', {
268 description
=> "Default storage.",
274 description
=> "Allow to overwrite existing VM.",
275 requires
=> 'archive',
280 description
=> "Assign a unique random ethernet address.",
281 requires
=> 'archive',
285 type
=> 'string', format
=> 'pve-poolid',
286 description
=> "Add the VM to the specified pool.",
296 my $rpcenv = PVE
::RPCEnvironment
::get
();
298 my $authuser = $rpcenv->get_user();
300 my $node = extract_param
($param, 'node');
302 my $vmid = extract_param
($param, 'vmid');
304 my $archive = extract_param
($param, 'archive');
306 my $storage = extract_param
($param, 'storage');
308 my $force = extract_param
($param, 'force');
310 my $unique = extract_param
($param, 'unique');
312 my $pool = extract_param
($param, 'pool');
314 my $filename = PVE
::QemuServer
::config_file
($vmid);
316 my $storecfg = PVE
::Storage
::config
();
318 PVE
::Cluster
::check_cfs_quorum
();
320 if (defined($pool)) {
321 $rpcenv->check_pool_exist($pool);
324 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
325 if defined($storage);
328 &$resolve_cdrom_alias($param);
330 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
332 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
334 foreach my $opt (keys %$param) {
335 if (PVE
::QemuServer
::valid_drivename
($opt)) {
336 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
337 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
339 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
340 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
344 PVE
::QemuServer
::add_random_macs
($param);
346 my $keystr = join(' ', keys %$param);
347 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
349 if ($archive eq '-') {
350 die "pipe requires cli environment\n"
351 if $rpcenv->{type
} ne 'cli';
353 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
355 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
356 if PVE
::Storage
::parse_volume_id
($archive, 1);
358 die "can't find archive file '$archive'\n" if !($path && -f
$path);
363 my $addVMtoPoolFn = sub {
364 my $usercfg = cfs_read_file
("user.cfg");
365 if (my $data = $usercfg->{pools
}->{$pool}) {
366 $data->{vms
}->{$vmid} = 1;
367 $usercfg->{vms
}->{$vmid} = $pool;
368 cfs_write_file
("user.cfg", $usercfg);
372 my $restorefn = sub {
374 # fixme: this test does not work if VM exists on other node!
376 die "unable to restore vm $vmid: config file already exists\n"
379 die "unable to restore vm $vmid: vm is running\n"
380 if PVE
::QemuServer
::check_running
($vmid);
384 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
387 unique
=> $unique });
389 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
392 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
398 die "unable to create vm $vmid: config file already exists\n"
409 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
411 # try to be smart about bootdisk
412 my @disks = PVE
::QemuServer
::disknames
();
414 foreach my $ds (reverse @disks) {
415 next if !$conf->{$ds};
416 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
417 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
421 if (!$conf->{bootdisk
} && $firstdisk) {
422 $conf->{bootdisk
} = $firstdisk;
425 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
431 foreach my $volid (@$vollist) {
432 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
435 die "create failed - $err";
438 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
441 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
444 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
447 __PACKAGE__-
>register_method({
452 description
=> "Directory index",
457 additionalProperties
=> 0,
459 node
=> get_standard_option
('pve-node'),
460 vmid
=> get_standard_option
('pve-vmid'),
468 subdir
=> { type
=> 'string' },
471 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
477 { subdir
=> 'config' },
478 { subdir
=> 'status' },
479 { subdir
=> 'unlink' },
480 { subdir
=> 'vncproxy' },
481 { subdir
=> 'migrate' },
482 { subdir
=> 'resize' },
484 { subdir
=> 'rrddata' },
485 { subdir
=> 'monitor' },
486 { subdir
=> 'snapshot' },
492 __PACKAGE__-
>register_method({
494 path
=> '{vmid}/rrd',
496 protected
=> 1, # fixme: can we avoid that?
498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
500 description
=> "Read VM RRD statistics (returns PNG)",
502 additionalProperties
=> 0,
504 node
=> get_standard_option
('pve-node'),
505 vmid
=> get_standard_option
('pve-vmid'),
507 description
=> "Specify the time frame you are interested in.",
509 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
512 description
=> "The list of datasources you want to display.",
513 type
=> 'string', format
=> 'pve-configid-list',
516 description
=> "The RRD consolidation function",
518 enum
=> [ 'AVERAGE', 'MAX' ],
526 filename
=> { type
=> 'string' },
532 return PVE
::Cluster
::create_rrd_graph
(
533 "pve2-vm/$param->{vmid}", $param->{timeframe
},
534 $param->{ds
}, $param->{cf
});
538 __PACKAGE__-
>register_method({
540 path
=> '{vmid}/rrddata',
542 protected
=> 1, # fixme: can we avoid that?
544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
546 description
=> "Read VM RRD statistics",
548 additionalProperties
=> 0,
550 node
=> get_standard_option
('pve-node'),
551 vmid
=> get_standard_option
('pve-vmid'),
553 description
=> "Specify the time frame you are interested in.",
555 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
558 description
=> "The RRD consolidation function",
560 enum
=> [ 'AVERAGE', 'MAX' ],
575 return PVE
::Cluster
::create_rrd_data
(
576 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
580 __PACKAGE__-
>register_method({
582 path
=> '{vmid}/config',
585 description
=> "Get virtual machine configuration.",
587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
590 additionalProperties
=> 0,
592 node
=> get_standard_option
('pve-node'),
593 vmid
=> get_standard_option
('pve-vmid'),
601 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
608 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
610 delete $conf->{snapshots
};
615 my $vm_is_volid_owner = sub {
616 my ($storecfg, $vmid, $volid) =@_;
618 if ($volid !~ m
|^/|) {
620 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
621 if ($owner && ($owner == $vmid)) {
629 my $test_deallocate_drive = sub {
630 my ($storecfg, $vmid, $key, $drive, $force) = @_;
632 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
633 my $volid = $drive->{file
};
634 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
635 if ($force || $key =~ m/^unused/) {
636 my $sid = PVE
::Storage
::parse_volume_id
($volid);
645 my $delete_drive = sub {
646 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
648 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
649 my $volid = $drive->{file
};
650 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
651 if ($force || $key =~ m/^unused/) {
652 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
655 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
660 delete $conf->{$key};
663 my $vmconfig_delete_option = sub {
664 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
666 return if !defined($conf->{$opt});
668 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
671 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
673 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
674 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
675 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
679 my $unplugwarning = "";
680 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
681 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
682 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
683 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
684 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
685 $unplugwarning = "<br>verify that your guest support acpi hotplug";
688 if($opt eq 'tablet'){
689 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
691 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
695 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
696 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
698 delete $conf->{$opt};
701 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
704 my $safe_num_ne = sub {
707 return 0 if !defined($a) && !defined($b);
708 return 1 if !defined($a);
709 return 1 if !defined($b);
714 my $vmconfig_update_disk = sub {
715 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
717 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
719 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
720 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
722 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
727 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
729 my $media = $drive->{media
} || 'disk';
730 my $oldmedia = $old_drive->{media
} || 'disk';
731 die "unable to change media type\n" if $media ne $oldmedia;
733 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
734 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
736 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
737 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
740 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
741 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
742 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
743 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
744 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
745 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
746 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
747 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
748 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
749 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
754 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
755 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
758 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
760 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
762 if (PVE
::QemuServer
::check_running
($vmid)) {
763 if ($drive->{file
} eq 'none') {
764 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
766 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
767 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
768 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
772 } else { # hotplug new disks
774 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
778 my $vmconfig_update_net = sub {
779 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
781 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
782 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
783 my $newnet = PVE
::QemuServer
::parse_net
($value);
785 if($oldnet->{model
} ne $newnet->{model
}){
786 #if model change, we try to hot-unplug
787 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
790 if($newnet->{bridge
} && $oldnet->{bridge
}){
791 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
793 if($newnet->{rate
} ne $oldnet->{rate
}){
794 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
797 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
798 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
799 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
803 #if bridge/nat mode change, we try to hot-unplug
804 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
809 $conf->{$opt} = $value;
810 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
811 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
813 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
815 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
818 my $vm_config_perm_list = [
828 __PACKAGE__-
>register_method({
830 path
=> '{vmid}/config',
834 description
=> "Set virtual machine options.",
836 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
839 additionalProperties
=> 0,
840 properties
=> PVE
::QemuServer
::json_config_properties
(
842 node
=> get_standard_option
('pve-node'),
843 vmid
=> get_standard_option
('pve-vmid'),
844 skiplock
=> get_standard_option
('skiplock'),
846 type
=> 'string', format
=> 'pve-configid-list',
847 description
=> "A list of settings you want to delete.",
852 description
=> $opt_force_description,
854 requires
=> 'delete',
858 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
864 returns
=> { type
=> 'null'},
868 my $rpcenv = PVE
::RPCEnvironment
::get
();
870 my $authuser = $rpcenv->get_user();
872 my $node = extract_param
($param, 'node');
874 my $vmid = extract_param
($param, 'vmid');
876 my $digest = extract_param
($param, 'digest');
878 my @paramarr = (); # used for log message
879 foreach my $key (keys %$param) {
880 push @paramarr, "-$key", $param->{$key};
883 my $skiplock = extract_param
($param, 'skiplock');
884 raise_param_exc
({ skiplock
=> "Only root may use this option." })
885 if $skiplock && $authuser ne 'root@pam';
887 my $delete_str = extract_param
($param, 'delete');
889 my $force = extract_param
($param, 'force');
891 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
893 my $storecfg = PVE
::Storage
::config
();
895 my $defaults = PVE
::QemuServer
::load_defaults
();
897 &$resolve_cdrom_alias($param);
899 # now try to verify all parameters
902 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
903 $opt = 'ide2' if $opt eq 'cdrom';
904 raise_param_exc
({ delete => "you can't use '-$opt' and " .
905 "-delete $opt' at the same time" })
906 if defined($param->{$opt});
908 if (!PVE
::QemuServer
::option_exists
($opt)) {
909 raise_param_exc
({ delete => "unknown option '$opt'" });
915 foreach my $opt (keys %$param) {
916 if (PVE
::QemuServer
::valid_drivename
($opt)) {
918 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
919 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
920 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
921 } elsif ($opt =~ m/^net(\d+)$/) {
923 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
924 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
928 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
930 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
932 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
936 my $conf = PVE
::QemuServer
::load_config
($vmid);
938 die "checksum missmatch (file change by other user?)\n"
939 if $digest && $digest ne $conf->{digest
};
941 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
943 if ($param->{memory
} || defined($param->{balloon
})) {
944 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
945 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
947 die "balloon value too large (must be smaller than assigned memory)\n"
948 if $balloon > $maxmem;
951 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
953 foreach my $opt (@delete) { # delete
954 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
955 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
958 my $running = PVE
::QemuServer
::check_running
($vmid);
960 foreach my $opt (keys %$param) { # add/change
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
964 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
966 if (PVE
::QemuServer
::valid_drivename
($opt)) {
968 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt}, $force);
971 } elsif ($opt =~ m/^net(\d+)$/) { #nics
973 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
974 $opt, $param->{$opt});
978 if($opt eq 'tablet' && $param->{$opt} == 1){
979 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
980 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
981 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
984 $conf->{$opt} = $param->{$opt};
985 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
989 # allow manual ballooning if shares is set to zero
990 if ($running && defined($param->{balloon
}) &&
991 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
992 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
993 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
998 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1004 __PACKAGE__-
>register_method({
1005 name
=> 'destroy_vm',
1010 description
=> "Destroy the vm (also delete all used/owned volumes).",
1012 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1015 additionalProperties
=> 0,
1017 node
=> get_standard_option
('pve-node'),
1018 vmid
=> get_standard_option
('pve-vmid'),
1019 skiplock
=> get_standard_option
('skiplock'),
1028 my $rpcenv = PVE
::RPCEnvironment
::get
();
1030 my $authuser = $rpcenv->get_user();
1032 my $vmid = $param->{vmid
};
1034 my $skiplock = $param->{skiplock
};
1035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1036 if $skiplock && $authuser ne 'root@pam';
1039 my $conf = PVE
::QemuServer
::load_config
($vmid);
1041 my $storecfg = PVE
::Storage
::config
();
1043 my $delVMfromPoolFn = sub {
1044 my $usercfg = cfs_read_file
("user.cfg");
1045 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1046 if (my $data = $usercfg->{pools
}->{$pool}) {
1047 delete $data->{vms
}->{$vmid};
1048 delete $usercfg->{vms
}->{$vmid};
1049 cfs_write_file
("user.cfg", $usercfg);
1057 syslog
('info', "destroy VM $vmid: $upid\n");
1059 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1061 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1064 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1067 __PACKAGE__-
>register_method({
1069 path
=> '{vmid}/unlink',
1073 description
=> "Unlink/delete disk images.",
1075 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1078 additionalProperties
=> 0,
1080 node
=> get_standard_option
('pve-node'),
1081 vmid
=> get_standard_option
('pve-vmid'),
1083 type
=> 'string', format
=> 'pve-configid-list',
1084 description
=> "A list of disk IDs you want to delete.",
1088 description
=> $opt_force_description,
1093 returns
=> { type
=> 'null'},
1097 $param->{delete} = extract_param
($param, 'idlist');
1099 __PACKAGE__-
>update_vm($param);
1106 __PACKAGE__-
>register_method({
1108 path
=> '{vmid}/vncproxy',
1112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1114 description
=> "Creates a TCP VNC proxy connections.",
1116 additionalProperties
=> 0,
1118 node
=> get_standard_option
('pve-node'),
1119 vmid
=> get_standard_option
('pve-vmid'),
1123 additionalProperties
=> 0,
1125 user
=> { type
=> 'string' },
1126 ticket
=> { type
=> 'string' },
1127 cert
=> { type
=> 'string' },
1128 port
=> { type
=> 'integer' },
1129 upid
=> { type
=> 'string' },
1135 my $rpcenv = PVE
::RPCEnvironment
::get
();
1137 my $authuser = $rpcenv->get_user();
1139 my $vmid = $param->{vmid
};
1140 my $node = $param->{node
};
1142 my $authpath = "/vms/$vmid";
1144 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1146 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1149 my $port = PVE
::Tools
::next_vnc_port
();
1153 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1154 $remip = PVE
::Cluster
::remote_node_ip
($node);
1157 # NOTE: kvm VNC traffic is already TLS encrypted
1158 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1165 syslog
('info', "starting vnc proxy $upid\n");
1167 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1169 my $qmstr = join(' ', @$qmcmd);
1171 # also redirect stderr (else we get RFB protocol errors)
1172 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1174 PVE
::Tools
::run_command
($cmd);
1179 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1181 PVE
::Tools
::wait_for_vnc_port
($port);
1192 __PACKAGE__-
>register_method({
1194 path
=> '{vmid}/status',
1197 description
=> "Directory index",
1202 additionalProperties
=> 0,
1204 node
=> get_standard_option
('pve-node'),
1205 vmid
=> get_standard_option
('pve-vmid'),
1213 subdir
=> { type
=> 'string' },
1216 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1222 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1225 { subdir
=> 'current' },
1226 { subdir
=> 'start' },
1227 { subdir
=> 'stop' },
1233 my $vm_is_ha_managed = sub {
1236 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1237 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1243 __PACKAGE__-
>register_method({
1244 name
=> 'vm_status',
1245 path
=> '{vmid}/status/current',
1248 protected
=> 1, # qemu pid files are only readable by root
1249 description
=> "Get virtual machine status.",
1251 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1254 additionalProperties
=> 0,
1256 node
=> get_standard_option
('pve-node'),
1257 vmid
=> get_standard_option
('pve-vmid'),
1260 returns
=> { type
=> 'object' },
1265 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1267 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1268 my $status = $vmstatus->{$param->{vmid
}};
1270 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1275 __PACKAGE__-
>register_method({
1277 path
=> '{vmid}/status/start',
1281 description
=> "Start virtual machine.",
1283 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1286 additionalProperties
=> 0,
1288 node
=> get_standard_option
('pve-node'),
1289 vmid
=> get_standard_option
('pve-vmid'),
1290 skiplock
=> get_standard_option
('skiplock'),
1291 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1292 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1302 my $rpcenv = PVE
::RPCEnvironment
::get
();
1304 my $authuser = $rpcenv->get_user();
1306 my $node = extract_param
($param, 'node');
1308 my $vmid = extract_param
($param, 'vmid');
1310 my $stateuri = extract_param
($param, 'stateuri');
1311 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1312 if $stateuri && $authuser ne 'root@pam';
1314 my $skiplock = extract_param
($param, 'skiplock');
1315 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1316 if $skiplock && $authuser ne 'root@pam';
1318 my $migratedfrom = extract_param
($param, 'migratedfrom');
1319 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1320 if $migratedfrom && $authuser ne 'root@pam';
1322 my $storecfg = PVE
::Storage
::config
();
1324 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1325 $rpcenv->{type
} ne 'ha') {
1330 my $service = "pvevm:$vmid";
1332 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1334 print "Executing HA start for VM $vmid\n";
1336 PVE
::Tools
::run_command
($cmd);
1341 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1348 syslog
('info', "start VM $vmid: $upid\n");
1350 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1355 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1359 __PACKAGE__-
>register_method({
1361 path
=> '{vmid}/status/stop',
1365 description
=> "Stop virtual machine.",
1367 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1370 additionalProperties
=> 0,
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid'),
1374 skiplock
=> get_standard_option
('skiplock'),
1375 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1377 description
=> "Wait maximal timeout seconds.",
1383 description
=> "Do not decativate storage volumes.",
1396 my $rpcenv = PVE
::RPCEnvironment
::get
();
1398 my $authuser = $rpcenv->get_user();
1400 my $node = extract_param
($param, 'node');
1402 my $vmid = extract_param
($param, 'vmid');
1404 my $skiplock = extract_param
($param, 'skiplock');
1405 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1406 if $skiplock && $authuser ne 'root@pam';
1408 my $keepActive = extract_param
($param, 'keepActive');
1409 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1410 if $keepActive && $authuser ne 'root@pam';
1412 my $migratedfrom = extract_param
($param, 'migratedfrom');
1413 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1414 if $migratedfrom && $authuser ne 'root@pam';
1417 my $storecfg = PVE
::Storage
::config
();
1419 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1424 my $service = "pvevm:$vmid";
1426 my $cmd = ['clusvcadm', '-d', $service];
1428 print "Executing HA stop for VM $vmid\n";
1430 PVE
::Tools
::run_command
($cmd);
1435 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1441 syslog
('info', "stop VM $vmid: $upid\n");
1443 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1444 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1449 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1453 __PACKAGE__-
>register_method({
1455 path
=> '{vmid}/status/reset',
1459 description
=> "Reset virtual machine.",
1461 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1464 additionalProperties
=> 0,
1466 node
=> get_standard_option
('pve-node'),
1467 vmid
=> get_standard_option
('pve-vmid'),
1468 skiplock
=> get_standard_option
('skiplock'),
1477 my $rpcenv = PVE
::RPCEnvironment
::get
();
1479 my $authuser = $rpcenv->get_user();
1481 my $node = extract_param
($param, 'node');
1483 my $vmid = extract_param
($param, 'vmid');
1485 my $skiplock = extract_param
($param, 'skiplock');
1486 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1487 if $skiplock && $authuser ne 'root@pam';
1489 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1494 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1499 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1502 __PACKAGE__-
>register_method({
1503 name
=> 'vm_shutdown',
1504 path
=> '{vmid}/status/shutdown',
1508 description
=> "Shutdown virtual machine.",
1510 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1513 additionalProperties
=> 0,
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid'),
1517 skiplock
=> get_standard_option
('skiplock'),
1519 description
=> "Wait maximal timeout seconds.",
1525 description
=> "Make sure the VM stops.",
1531 description
=> "Do not decativate storage volumes.",
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $node = extract_param
($param, 'node');
1550 my $vmid = extract_param
($param, 'vmid');
1552 my $skiplock = extract_param
($param, 'skiplock');
1553 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1554 if $skiplock && $authuser ne 'root@pam';
1556 my $keepActive = extract_param
($param, 'keepActive');
1557 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1558 if $keepActive && $authuser ne 'root@pam';
1560 my $storecfg = PVE
::Storage
::config
();
1565 syslog
('info', "shutdown VM $vmid: $upid\n");
1567 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1568 1, $param->{forceStop
}, $keepActive);
1573 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1576 __PACKAGE__-
>register_method({
1577 name
=> 'vm_suspend',
1578 path
=> '{vmid}/status/suspend',
1582 description
=> "Suspend virtual machine.",
1584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1591 skiplock
=> get_standard_option
('skiplock'),
1600 my $rpcenv = PVE
::RPCEnvironment
::get
();
1602 my $authuser = $rpcenv->get_user();
1604 my $node = extract_param
($param, 'node');
1606 my $vmid = extract_param
($param, 'vmid');
1608 my $skiplock = extract_param
($param, 'skiplock');
1609 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1610 if $skiplock && $authuser ne 'root@pam';
1612 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1617 syslog
('info', "suspend VM $vmid: $upid\n");
1619 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1624 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1627 __PACKAGE__-
>register_method({
1628 name
=> 'vm_resume',
1629 path
=> '{vmid}/status/resume',
1633 description
=> "Resume virtual machine.",
1635 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1638 additionalProperties
=> 0,
1640 node
=> get_standard_option
('pve-node'),
1641 vmid
=> get_standard_option
('pve-vmid'),
1642 skiplock
=> get_standard_option
('skiplock'),
1651 my $rpcenv = PVE
::RPCEnvironment
::get
();
1653 my $authuser = $rpcenv->get_user();
1655 my $node = extract_param
($param, 'node');
1657 my $vmid = extract_param
($param, 'vmid');
1659 my $skiplock = extract_param
($param, 'skiplock');
1660 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1661 if $skiplock && $authuser ne 'root@pam';
1663 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1668 syslog
('info', "resume VM $vmid: $upid\n");
1670 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1675 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1678 __PACKAGE__-
>register_method({
1679 name
=> 'vm_sendkey',
1680 path
=> '{vmid}/sendkey',
1684 description
=> "Send key event to virtual machine.",
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1689 additionalProperties
=> 0,
1691 node
=> get_standard_option
('pve-node'),
1692 vmid
=> get_standard_option
('pve-vmid'),
1693 skiplock
=> get_standard_option
('skiplock'),
1695 description
=> "The key (qemu monitor encoding).",
1700 returns
=> { type
=> 'null'},
1704 my $rpcenv = PVE
::RPCEnvironment
::get
();
1706 my $authuser = $rpcenv->get_user();
1708 my $node = extract_param
($param, 'node');
1710 my $vmid = extract_param
($param, 'vmid');
1712 my $skiplock = extract_param
($param, 'skiplock');
1713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1714 if $skiplock && $authuser ne 'root@pam';
1716 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1721 __PACKAGE__-
>register_method({
1722 name
=> 'vm_feature',
1723 path
=> '{vmid}/feature',
1727 description
=> "Check if feature for virtual machine is available.",
1729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1732 additionalProperties
=> 0,
1734 node
=> get_standard_option
('pve-node'),
1735 vmid
=> get_standard_option
('pve-vmid'),
1737 description
=> "Feature to check.",
1739 enum
=> [ 'snapshot', 'clone' ],
1741 snapname
=> get_standard_option
('pve-snapshot-name', {
1753 my $node = extract_param
($param, 'node');
1755 my $vmid = extract_param
($param, 'vmid');
1757 my $snapname = extract_param
($param, 'snapname');
1759 my $feature = extract_param
($param, 'feature');
1761 my $running = PVE
::QemuServer
::check_running
($vmid);
1763 my $conf = PVE
::QemuServer
::load_config
($vmid);
1766 my $snap = $conf->{snapshots
}->{$snapname};
1767 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1770 my $storecfg = PVE
::Storage
::config
();
1772 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1773 my $res = $hasfeature ?
1 : 0 ;
1777 __PACKAGE__-
>register_method({
1779 path
=> '{vmid}/copy',
1783 description
=> "Create a copy of virtual machine/template.",
1785 description
=> "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1786 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1787 "'Datastore.AllocateSpace' on any used storage.",
1790 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1792 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1793 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1798 additionalProperties
=> 0,
1800 # fixme: add other parameters like name and description?
1801 node
=> get_standard_option
('pve-node'),
1802 vmid
=> get_standard_option
('pve-vmid'),
1803 newid
=> get_standard_option
('pve-vmid', {
1804 description
=> 'VMID for the copy.' }),
1807 type
=> 'string', format
=> 'pve-poolid',
1808 description
=> "Add the new VM to the specified pool.",
1810 storage
=> get_standard_option
('pve-storage-id', {
1811 description
=> "Target storage for full copy.",
1817 description
=> "Create a full copy of all disk. This is always done when " .
1818 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1829 my $rpcenv = PVE
::RPCEnvironment
::get
();
1831 my $authuser = $rpcenv->get_user();
1833 my $node = extract_param
($param, 'node');
1835 my $vmid = extract_param
($param, 'vmid');
1837 my $newid = extract_param
($param, 'newid');
1839 # fixme: update pool after create
1840 my $pool = extract_param
($param, 'pool');
1842 if (defined($pool)) {
1843 $rpcenv->check_pool_exist($pool);
1846 my $storage = extract_param
($param, 'storage');
1848 my $storecfg = PVE
::Storage
::config
();
1850 PVE
::Cluster
::check_cfs_quorum
();
1852 # fixme: do early checks - re-check after lock
1854 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1858 # all tests after lock
1859 my $conf = PVE
::QemuServer
::load_config
($vmid);
1861 PVE
::QemuServer
::check_lock
($conf);
1863 my $running = PVE
::QemuServer
::check_running
($vmid);
1865 die "Copy running VM $vmid not implemented\n" if $running;
1867 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf, $storage);
1869 # fixme: snapshots??
1871 my $conffile = PVE
::QemuServer
::config_file
($newid);
1873 die "unable to create VM $newid: config file already exists\n"
1876 # create empty/temp config - this fails if VM already exists on other node
1877 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1882 my $newvollist = [];
1885 my $newconf = { lock => 'copy' };
1888 foreach my $opt (keys %$conf) {
1889 my $value = $conf->{$opt};
1891 next if $opt eq 'snapshots'; # do not copy snapshot info
1893 # always change MAC! address
1894 if ($opt =~ m/^net(\d+)$/) {
1895 my $net = PVE
::QemuServer
::parse_net
($value);
1896 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1897 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1898 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1899 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1900 $newconf->{$opt} = $value; # simply copy configuration
1902 $drives->{$opt} = $drive;
1903 push @$vollist, $drive->{file
};
1906 # copy everything else
1907 $newconf->{$opt} = $value;
1911 delete $newconf->{template
};
1913 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1916 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1918 foreach my $opt (keys %$drives) {
1919 my $drive = $drives->{$opt};
1922 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1923 print "clone drive $opt ($drive->{file})\n";
1924 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1926 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1927 $storeid = $storage if $storage;
1928 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1929 my $fmt = $drive->{format
} || $defformat;
1931 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
1933 print "copy drive $opt ($drive->{file})\n";
1934 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1936 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size);
1939 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
1940 my $disk = { file
=> $newvolid, size
=> $size };
1941 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
1942 push @$newvollist, $newvolid;
1944 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1949 delete $newconf->{lock};
1950 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1955 sleep 1; # some storage like rbd need to wait before release volume - really?
1957 foreach my $volid (@$newvollist) {
1958 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1961 die "copy failed: $err";
1967 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
1970 # Aquire shared lock for $vmid
1971 return PVE
::QemuServer
::lock_config_shared
($vmid, 1, sub {
1972 # Aquire exclusive lock lock for $newid
1973 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
1978 __PACKAGE__-
>register_method({
1979 name
=> 'migrate_vm',
1980 path
=> '{vmid}/migrate',
1984 description
=> "Migrate virtual machine. Creates a new migration task.",
1986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1989 additionalProperties
=> 0,
1991 node
=> get_standard_option
('pve-node'),
1992 vmid
=> get_standard_option
('pve-vmid'),
1993 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1996 description
=> "Use online/live migration.",
2001 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2008 description
=> "the task ID.",
2013 my $rpcenv = PVE
::RPCEnvironment
::get
();
2015 my $authuser = $rpcenv->get_user();
2017 my $target = extract_param
($param, 'target');
2019 my $localnode = PVE
::INotify
::nodename
();
2020 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2022 PVE
::Cluster
::check_cfs_quorum
();
2024 PVE
::Cluster
::check_node_exists
($target);
2026 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2028 my $vmid = extract_param
($param, 'vmid');
2030 raise_param_exc
({ force
=> "Only root may use this option." })
2031 if $param->{force
} && $authuser ne 'root@pam';
2034 my $conf = PVE
::QemuServer
::load_config
($vmid);
2036 # try to detect errors early
2038 PVE
::QemuServer
::check_lock
($conf);
2040 if (PVE
::QemuServer
::check_running
($vmid)) {
2041 die "cant migrate running VM without --online\n"
2042 if !$param->{online
};
2045 my $storecfg = PVE
::Storage
::config
();
2046 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2048 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2053 my $service = "pvevm:$vmid";
2055 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2057 print "Executing HA migrate for VM $vmid to node $target\n";
2059 PVE
::Tools
::run_command
($cmd);
2064 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2071 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2074 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2079 __PACKAGE__-
>register_method({
2081 path
=> '{vmid}/monitor',
2085 description
=> "Execute Qemu monitor commands.",
2087 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2090 additionalProperties
=> 0,
2092 node
=> get_standard_option
('pve-node'),
2093 vmid
=> get_standard_option
('pve-vmid'),
2096 description
=> "The monitor command.",
2100 returns
=> { type
=> 'string'},
2104 my $vmid = $param->{vmid
};
2106 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2110 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2112 $res = "ERROR: $@" if $@;
2117 __PACKAGE__-
>register_method({
2118 name
=> 'resize_vm',
2119 path
=> '{vmid}/resize',
2123 description
=> "Extend volume size.",
2125 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2128 additionalProperties
=> 0,
2130 node
=> get_standard_option
('pve-node'),
2131 vmid
=> get_standard_option
('pve-vmid'),
2132 skiplock
=> get_standard_option
('skiplock'),
2135 description
=> "The disk you want to resize.",
2136 enum
=> [PVE
::QemuServer
::disknames
()],
2140 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2141 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.",
2145 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2151 returns
=> { type
=> 'null'},
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $digest = extract_param
($param, 'digest');
2165 my $disk = extract_param
($param, 'disk');
2167 my $sizestr = extract_param
($param, 'size');
2169 my $skiplock = extract_param
($param, 'skiplock');
2170 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2171 if $skiplock && $authuser ne 'root@pam';
2173 my $storecfg = PVE
::Storage
::config
();
2175 my $updatefn = sub {
2177 my $conf = PVE
::QemuServer
::load_config
($vmid);
2179 die "checksum missmatch (file change by other user?)\n"
2180 if $digest && $digest ne $conf->{digest
};
2181 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2183 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2185 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2187 my $volid = $drive->{file
};
2189 die "disk '$disk' has no associated volume\n" if !$volid;
2191 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2193 die "you can't online resize a virtio windows bootdisk\n"
2194 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2196 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2198 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2200 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2202 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2203 my ($ext, $newsize, $unit) = ($1, $2, $4);
2206 $newsize = $newsize * 1024;
2207 } elsif ($unit eq 'M') {
2208 $newsize = $newsize * 1024 * 1024;
2209 } elsif ($unit eq 'G') {
2210 $newsize = $newsize * 1024 * 1024 * 1024;
2211 } elsif ($unit eq 'T') {
2212 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2215 $newsize += $size if $ext;
2216 $newsize = int($newsize);
2218 die "unable to skrink disk size\n" if $newsize < $size;
2220 return if $size == $newsize;
2222 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2224 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2226 $drive->{size
} = $newsize;
2227 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2229 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2232 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2236 __PACKAGE__-
>register_method({
2237 name
=> 'snapshot_list',
2238 path
=> '{vmid}/snapshot',
2240 description
=> "List all snapshots.",
2242 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2245 protected
=> 1, # qemu pid files are only readable by root
2247 additionalProperties
=> 0,
2249 vmid
=> get_standard_option
('pve-vmid'),
2250 node
=> get_standard_option
('pve-node'),
2259 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2264 my $vmid = $param->{vmid
};
2266 my $conf = PVE
::QemuServer
::load_config
($vmid);
2267 my $snaphash = $conf->{snapshots
} || {};
2271 foreach my $name (keys %$snaphash) {
2272 my $d = $snaphash->{$name};
2275 snaptime
=> $d->{snaptime
} || 0,
2276 vmstate
=> $d->{vmstate
} ?
1 : 0,
2277 description
=> $d->{description
} || '',
2279 $item->{parent
} = $d->{parent
} if $d->{parent
};
2280 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2284 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2285 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2286 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2288 push @$res, $current;
2293 __PACKAGE__-
>register_method({
2295 path
=> '{vmid}/snapshot',
2299 description
=> "Snapshot a VM.",
2301 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2304 additionalProperties
=> 0,
2306 node
=> get_standard_option
('pve-node'),
2307 vmid
=> get_standard_option
('pve-vmid'),
2308 snapname
=> get_standard_option
('pve-snapshot-name'),
2312 description
=> "Save the vmstate",
2317 description
=> "Freeze the filesystem",
2322 description
=> "A textual description or comment.",
2328 description
=> "the task ID.",
2333 my $rpcenv = PVE
::RPCEnvironment
::get
();
2335 my $authuser = $rpcenv->get_user();
2337 my $node = extract_param
($param, 'node');
2339 my $vmid = extract_param
($param, 'vmid');
2341 my $snapname = extract_param
($param, 'snapname');
2343 die "unable to use snapshot name 'current' (reserved name)\n"
2344 if $snapname eq 'current';
2347 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2348 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2349 $param->{freezefs
}, $param->{description
});
2352 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2355 __PACKAGE__-
>register_method({
2356 name
=> 'snapshot_cmd_idx',
2357 path
=> '{vmid}/snapshot/{snapname}',
2364 additionalProperties
=> 0,
2366 vmid
=> get_standard_option
('pve-vmid'),
2367 node
=> get_standard_option
('pve-node'),
2368 snapname
=> get_standard_option
('pve-snapshot-name'),
2377 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2384 push @$res, { cmd
=> 'rollback' };
2385 push @$res, { cmd
=> 'config' };
2390 __PACKAGE__-
>register_method({
2391 name
=> 'update_snapshot_config',
2392 path
=> '{vmid}/snapshot/{snapname}/config',
2396 description
=> "Update snapshot metadata.",
2398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2401 additionalProperties
=> 0,
2403 node
=> get_standard_option
('pve-node'),
2404 vmid
=> get_standard_option
('pve-vmid'),
2405 snapname
=> get_standard_option
('pve-snapshot-name'),
2409 description
=> "A textual description or comment.",
2413 returns
=> { type
=> 'null' },
2417 my $rpcenv = PVE
::RPCEnvironment
::get
();
2419 my $authuser = $rpcenv->get_user();
2421 my $vmid = extract_param
($param, 'vmid');
2423 my $snapname = extract_param
($param, 'snapname');
2425 return undef if !defined($param->{description
});
2427 my $updatefn = sub {
2429 my $conf = PVE
::QemuServer
::load_config
($vmid);
2431 PVE
::QemuServer
::check_lock
($conf);
2433 my $snap = $conf->{snapshots
}->{$snapname};
2435 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2437 $snap->{description
} = $param->{description
} if defined($param->{description
});
2439 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2442 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2447 __PACKAGE__-
>register_method({
2448 name
=> 'get_snapshot_config',
2449 path
=> '{vmid}/snapshot/{snapname}/config',
2452 description
=> "Get snapshot configuration",
2454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2457 additionalProperties
=> 0,
2459 node
=> get_standard_option
('pve-node'),
2460 vmid
=> get_standard_option
('pve-vmid'),
2461 snapname
=> get_standard_option
('pve-snapshot-name'),
2464 returns
=> { type
=> "object" },
2468 my $rpcenv = PVE
::RPCEnvironment
::get
();
2470 my $authuser = $rpcenv->get_user();
2472 my $vmid = extract_param
($param, 'vmid');
2474 my $snapname = extract_param
($param, 'snapname');
2476 my $conf = PVE
::QemuServer
::load_config
($vmid);
2478 my $snap = $conf->{snapshots
}->{$snapname};
2480 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2485 __PACKAGE__-
>register_method({
2487 path
=> '{vmid}/snapshot/{snapname}/rollback',
2491 description
=> "Rollback VM state to specified snapshot.",
2493 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2496 additionalProperties
=> 0,
2498 node
=> get_standard_option
('pve-node'),
2499 vmid
=> get_standard_option
('pve-vmid'),
2500 snapname
=> get_standard_option
('pve-snapshot-name'),
2505 description
=> "the task ID.",
2510 my $rpcenv = PVE
::RPCEnvironment
::get
();
2512 my $authuser = $rpcenv->get_user();
2514 my $node = extract_param
($param, 'node');
2516 my $vmid = extract_param
($param, 'vmid');
2518 my $snapname = extract_param
($param, 'snapname');
2521 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2522 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2525 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2528 __PACKAGE__-
>register_method({
2529 name
=> 'delsnapshot',
2530 path
=> '{vmid}/snapshot/{snapname}',
2534 description
=> "Delete a VM snapshot.",
2536 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2539 additionalProperties
=> 0,
2541 node
=> get_standard_option
('pve-node'),
2542 vmid
=> get_standard_option
('pve-vmid'),
2543 snapname
=> get_standard_option
('pve-snapshot-name'),
2547 description
=> "For removal from config file, even if removing disk snapshots fails.",
2553 description
=> "the task ID.",
2558 my $rpcenv = PVE
::RPCEnvironment
::get
();
2560 my $authuser = $rpcenv->get_user();
2562 my $node = extract_param
($param, 'node');
2564 my $vmid = extract_param
($param, 'vmid');
2566 my $snapname = extract_param
($param, 'snapname');
2569 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2570 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2573 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2576 __PACKAGE__-
>register_method({
2578 path
=> '{vmid}/template',
2582 description
=> "Create a Template.",
2584 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2586 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2587 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2591 additionalProperties
=> 0,
2593 node
=> get_standard_option
('pve-node'),
2594 vmid
=> get_standard_option
('pve-vmid'),
2598 description
=> "If you want to convert only 1 disk to base image.",
2599 enum
=> [PVE
::QemuServer
::disknames
()],
2604 returns
=> { type
=> 'null'},
2608 my $rpcenv = PVE
::RPCEnvironment
::get
();
2610 my $authuser = $rpcenv->get_user();
2612 my $node = extract_param
($param, 'node');
2614 my $vmid = extract_param
($param, 'vmid');
2616 my $disk = extract_param
($param, 'disk');
2618 my $updatefn = sub {
2620 my $conf = PVE
::QemuServer
::load_config
($vmid);
2622 PVE
::QemuServer
::check_lock
($conf);
2624 die "unable to create template, because VM contains snapshots\n"
2625 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2627 die "you can't convert a template to a template\n"
2628 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2630 die "you can't convert a VM to template if VM is running\n"
2631 if PVE
::QemuServer
::check_running
($vmid);
2634 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2637 $conf->{template
} = 1;
2638 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2640 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2643 PVE
::QemuServer
::lock_config
($vmid, $updatefn);