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 raise_perm_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_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my $volid_is_new = 1;
134 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
135 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
140 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
142 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
144 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
146 die "volume $volid does not exists\n" if !$size;
148 $disk->{size
} = $size;
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
155 # free allocated images on error
157 syslog
('err', "VM $vmid creating disks failed");
158 foreach my $volid (@$vollist) {
159 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
165 # modify vm config if everything went well
166 foreach my $ds (keys %$res) {
167 $conf->{$ds} = $res->{$ds};
173 my $check_vm_modify_config_perm = sub {
174 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
176 return 1 if $authuser eq 'root@pam';
178 foreach my $opt (@$key_list) {
179 # disk checks need to be done somewhere else
180 next if PVE
::QemuServer
::valid_drivename
($opt);
182 if ($opt eq 'sockets' || $opt eq 'cores' ||
183 $opt eq 'cpu' || $opt eq 'smp' ||
184 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
186 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
188 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
190 } elsif ($opt eq 'args' || $opt eq 'lock') {
191 die "only root can set '$opt' config\n";
192 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
193 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
194 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
195 } elsif ($opt =~ m/^net\d+$/) {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
205 __PACKAGE__-
>register_method({
209 description
=> "Virtual machine index (per node).",
211 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
215 protected
=> 1, # qemu pid files are only readable by root
217 additionalProperties
=> 0,
219 node
=> get_standard_option
('pve-node'),
228 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
233 my $rpcenv = PVE
::RPCEnvironment
::get
();
234 my $authuser = $rpcenv->get_user();
236 my $vmstatus = PVE
::QemuServer
::vmstatus
();
239 foreach my $vmid (keys %$vmstatus) {
240 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
242 my $data = $vmstatus->{$vmid};
243 $data->{vmid
} = $vmid;
252 __PACKAGE__-
>register_method({
256 description
=> "Create or restore a virtual machine.",
258 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
259 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
260 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
261 user
=> 'all', # check inside
266 additionalProperties
=> 0,
267 properties
=> PVE
::QemuServer
::json_config_properties
(
269 node
=> get_standard_option
('pve-node'),
270 vmid
=> get_standard_option
('pve-vmid'),
272 description
=> "The backup file.",
277 storage
=> get_standard_option
('pve-storage-id', {
278 description
=> "Default storage.",
284 description
=> "Allow to overwrite existing VM.",
285 requires
=> 'archive',
290 description
=> "Assign a unique random ethernet address.",
291 requires
=> 'archive',
295 type
=> 'string', format
=> 'pve-poolid',
296 description
=> "Add the VM to the specified pool.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 my $node = extract_param
($param, 'node');
312 my $vmid = extract_param
($param, 'vmid');
314 my $archive = extract_param
($param, 'archive');
316 my $storage = extract_param
($param, 'storage');
318 my $force = extract_param
($param, 'force');
320 my $unique = extract_param
($param, 'unique');
322 my $pool = extract_param
($param, 'pool');
324 my $filename = PVE
::QemuServer
::config_file
($vmid);
326 my $storecfg = PVE
::Storage
::config
();
328 PVE
::Cluster
::check_cfs_quorum
();
330 if (defined($pool)) {
331 $rpcenv->check_pool_exist($pool);
334 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
335 if defined($storage);
337 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
339 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
341 } elsif ($archive && $force && (-f
$filename) &&
342 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
343 # OK: user has VM.Backup permissions, and want to restore an existing VM
349 &$resolve_cdrom_alias($param);
351 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
353 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
355 foreach my $opt (keys %$param) {
356 if (PVE
::QemuServer
::valid_drivename
($opt)) {
357 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
358 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
360 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
361 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
365 PVE
::QemuServer
::add_random_macs
($param);
367 my $keystr = join(' ', keys %$param);
368 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
370 if ($archive eq '-') {
371 die "pipe requires cli environment\n"
372 if $rpcenv->{type
} ne 'cli';
374 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
377 if PVE
::Storage
::parse_volume_id
($archive, 1);
379 die "can't find archive file '$archive'\n" if !($path && -f
$path);
384 my $restorefn = sub {
386 # fixme: this test does not work if VM exists on other node!
388 die "unable to restore vm $vmid: config file already exists\n"
391 die "unable to restore vm $vmid: vm is running\n"
392 if PVE
::QemuServer
::check_running
($vmid);
396 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
399 unique
=> $unique });
401 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
404 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
410 die "unable to create vm $vmid: config file already exists\n"
421 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
423 # try to be smart about bootdisk
424 my @disks = PVE
::QemuServer
::disknames
();
426 foreach my $ds (reverse @disks) {
427 next if !$conf->{$ds};
428 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
429 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
433 if (!$conf->{bootdisk
} && $firstdisk) {
434 $conf->{bootdisk
} = $firstdisk;
437 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
443 foreach my $volid (@$vollist) {
444 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
447 die "create failed - $err";
450 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
453 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
456 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
459 __PACKAGE__-
>register_method({
464 description
=> "Directory index",
469 additionalProperties
=> 0,
471 node
=> get_standard_option
('pve-node'),
472 vmid
=> get_standard_option
('pve-vmid'),
480 subdir
=> { type
=> 'string' },
483 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
489 { subdir
=> 'config' },
490 { subdir
=> 'status' },
491 { subdir
=> 'unlink' },
492 { subdir
=> 'vncproxy' },
493 { subdir
=> 'migrate' },
494 { subdir
=> 'resize' },
495 { subdir
=> 'move' },
497 { subdir
=> 'rrddata' },
498 { subdir
=> 'monitor' },
499 { subdir
=> 'snapshot' },
500 { subdir
=> 'spiceproxy' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
771 ($drive->{mbps
} || 0)*1024*1024,
772 ($drive->{mbps_rd
} || 0)*1024*1024,
773 ($drive->{mbps_wr
} || 0)*1024*1024,
775 $drive->{iops_rd
} || 0,
776 $drive->{iops_wr
} || 0)
777 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
782 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
783 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
785 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
786 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
788 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
790 if (PVE
::QemuServer
::check_running
($vmid)) {
791 if ($drive->{file
} eq 'none') {
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
794 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
795 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
800 } else { # hotplug new disks
802 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
806 my $vmconfig_update_net = sub {
807 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
809 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
810 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
811 my $newnet = PVE
::QemuServer
::parse_net
($value);
813 if($oldnet->{model
} ne $newnet->{model
}){
814 #if model change, we try to hot-unplug
815 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
818 if($newnet->{bridge
} && $oldnet->{bridge
}){
819 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
821 if($newnet->{rate
} ne $oldnet->{rate
}){
822 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
825 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
826 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
827 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
831 #if bridge/nat mode change, we try to hot-unplug
832 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
837 $conf->{$opt} = $value;
838 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
839 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
841 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
843 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
846 # POST/PUT {vmid}/config implementation
848 # The original API used PUT (idempotent) an we assumed that all operations
849 # are fast. But it turned out that almost any configuration change can
850 # involve hot-plug actions, or disk alloc/free. Such actions can take long
851 # time to complete and have side effects (not idempotent).
853 # The new implementation uses POST and forks a worker process. We added
854 # a new option 'background_delay'. If specified we wait up to
855 # 'background_delay' second for the worker task to complete. It returns null
856 # if the task is finished within that time, else we return the UPID.
858 my $update_vm_api = sub {
859 my ($param, $sync) = @_;
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $node = extract_param
($param, 'node');
867 my $vmid = extract_param
($param, 'vmid');
869 my $digest = extract_param
($param, 'digest');
871 my $background_delay = extract_param
($param, 'background_delay');
873 my @paramarr = (); # used for log message
874 foreach my $key (keys %$param) {
875 push @paramarr, "-$key", $param->{$key};
878 my $skiplock = extract_param
($param, 'skiplock');
879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
880 if $skiplock && $authuser ne 'root@pam';
882 my $delete_str = extract_param
($param, 'delete');
884 my $force = extract_param
($param, 'force');
886 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
888 my $storecfg = PVE
::Storage
::config
();
890 my $defaults = PVE
::QemuServer
::load_defaults
();
892 &$resolve_cdrom_alias($param);
894 # now try to verify all parameters
897 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
898 $opt = 'ide2' if $opt eq 'cdrom';
899 raise_param_exc
({ delete => "you can't use '-$opt' and " .
900 "-delete $opt' at the same time" })
901 if defined($param->{$opt});
903 if (!PVE
::QemuServer
::option_exists
($opt)) {
904 raise_param_exc
({ delete => "unknown option '$opt'" });
910 foreach my $opt (keys %$param) {
911 if (PVE
::QemuServer
::valid_drivename
($opt)) {
913 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
914 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
915 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
916 } elsif ($opt =~ m/^net(\d+)$/) {
918 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
919 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
923 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
927 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
931 my $conf = PVE
::QemuServer
::load_config
($vmid);
933 die "checksum missmatch (file change by other user?)\n"
934 if $digest && $digest ne $conf->{digest
};
936 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
938 if ($param->{memory
} || defined($param->{balloon
})) {
939 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
940 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
942 die "balloon value too large (must be smaller than assigned memory)\n"
943 if $balloon && $balloon > $maxmem;
946 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
950 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
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);
1000 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1002 if ($background_delay) {
1004 # Note: It would be better to do that in the Event based HTTPServer
1005 # to avoid blocking call to sleep.
1007 my $end_time = time() + $background_delay;
1009 my $task = PVE
::Tools
::upid_decode
($upid);
1012 while (time() < $end_time) {
1013 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1015 sleep(1); # this gets interrupted when child process ends
1019 my $status = PVE
::Tools
::upid_read_status
($upid);
1020 return undef if $status eq 'OK';
1029 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1032 my $vm_config_perm_list = [
1037 'VM.Config.Network',
1039 'VM.Config.Options',
1042 __PACKAGE__-
>register_method({
1043 name
=> 'update_vm_async',
1044 path
=> '{vmid}/config',
1048 description
=> "Set virtual machine options (asynchrounous API).",
1050 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1053 additionalProperties
=> 0,
1054 properties
=> PVE
::QemuServer
::json_config_properties
(
1056 node
=> get_standard_option
('pve-node'),
1057 vmid
=> get_standard_option
('pve-vmid'),
1058 skiplock
=> get_standard_option
('skiplock'),
1060 type
=> 'string', format
=> 'pve-configid-list',
1061 description
=> "A list of settings you want to delete.",
1066 description
=> $opt_force_description,
1068 requires
=> 'delete',
1072 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1076 background_delay
=> {
1078 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1089 code
=> $update_vm_api,
1092 __PACKAGE__-
>register_method({
1093 name
=> 'update_vm',
1094 path
=> '{vmid}/config',
1098 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1100 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1103 additionalProperties
=> 0,
1104 properties
=> PVE
::QemuServer
::json_config_properties
(
1106 node
=> get_standard_option
('pve-node'),
1107 vmid
=> get_standard_option
('pve-vmid'),
1108 skiplock
=> get_standard_option
('skiplock'),
1110 type
=> 'string', format
=> 'pve-configid-list',
1111 description
=> "A list of settings you want to delete.",
1116 description
=> $opt_force_description,
1118 requires
=> 'delete',
1122 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1128 returns
=> { type
=> 'null' },
1131 &$update_vm_api($param, 1);
1137 __PACKAGE__-
>register_method({
1138 name
=> 'destroy_vm',
1143 description
=> "Destroy the vm (also delete all used/owned volumes).",
1145 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1152 skiplock
=> get_standard_option
('skiplock'),
1161 my $rpcenv = PVE
::RPCEnvironment
::get
();
1163 my $authuser = $rpcenv->get_user();
1165 my $vmid = $param->{vmid
};
1167 my $skiplock = $param->{skiplock
};
1168 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1169 if $skiplock && $authuser ne 'root@pam';
1172 my $conf = PVE
::QemuServer
::load_config
($vmid);
1174 my $storecfg = PVE
::Storage
::config
();
1176 my $delVMfromPoolFn = sub {
1177 my $usercfg = cfs_read_file
("user.cfg");
1178 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1179 if (my $data = $usercfg->{pools
}->{$pool}) {
1180 delete $data->{vms
}->{$vmid};
1181 delete $usercfg->{vms
}->{$vmid};
1182 cfs_write_file
("user.cfg", $usercfg);
1190 syslog
('info', "destroy VM $vmid: $upid\n");
1192 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1194 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1197 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1200 __PACKAGE__-
>register_method({
1202 path
=> '{vmid}/unlink',
1206 description
=> "Unlink/delete disk images.",
1208 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1211 additionalProperties
=> 0,
1213 node
=> get_standard_option
('pve-node'),
1214 vmid
=> get_standard_option
('pve-vmid'),
1216 type
=> 'string', format
=> 'pve-configid-list',
1217 description
=> "A list of disk IDs you want to delete.",
1221 description
=> $opt_force_description,
1226 returns
=> { type
=> 'null'},
1230 $param->{delete} = extract_param
($param, 'idlist');
1232 __PACKAGE__-
>update_vm($param);
1239 __PACKAGE__-
>register_method({
1241 path
=> '{vmid}/vncproxy',
1245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1247 description
=> "Creates a TCP VNC proxy connections.",
1249 additionalProperties
=> 0,
1251 node
=> get_standard_option
('pve-node'),
1252 vmid
=> get_standard_option
('pve-vmid'),
1256 additionalProperties
=> 0,
1258 user
=> { type
=> 'string' },
1259 ticket
=> { type
=> 'string' },
1260 cert
=> { type
=> 'string' },
1261 port
=> { type
=> 'integer' },
1262 upid
=> { type
=> 'string' },
1268 my $rpcenv = PVE
::RPCEnvironment
::get
();
1270 my $authuser = $rpcenv->get_user();
1272 my $vmid = $param->{vmid
};
1273 my $node = $param->{node
};
1275 my $authpath = "/vms/$vmid";
1277 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1279 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1282 my $port = PVE
::Tools
::next_vnc_port
();
1286 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1287 $remip = PVE
::Cluster
::remote_node_ip
($node);
1290 # NOTE: kvm VNC traffic is already TLS encrypted
1291 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1298 syslog
('info', "starting vnc proxy $upid\n");
1300 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1302 my $qmstr = join(' ', @$qmcmd);
1304 # also redirect stderr (else we get RFB protocol errors)
1305 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1307 PVE
::Tools
::run_command
($cmd);
1312 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1314 PVE
::Tools
::wait_for_vnc_port
($port);
1325 __PACKAGE__-
>register_method({
1326 name
=> 'spiceproxy',
1327 path
=> '{vmid}/spiceproxy',
1330 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1332 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1334 description
=> "Returns a SPICE configuration to connect to the VM.",
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid'),
1343 additionalProperties
=> 1,
1345 type
=> { type
=> 'string' },
1346 password
=> { type
=> 'string' },
1347 proxy
=> { type
=> 'string' },
1348 host
=> { type
=> 'string' },
1349 'tls-port' => { type
=> 'integer' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1364 # Note: we currectly use "proxyto => 'node'", so this code will never trigger
1365 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1366 $remip = PVE
::Cluster
::remote_node_ip
($node);
1369 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1373 # Note: this only works if VM is on local node
1374 my $port = PVE
::QemuServer
::spice_port
($vmid);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1376 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1379 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1382 my $subject = "OU=PVE Cluster Node, O=Proxmox Virtual Environment, CN=$host";
1384 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1385 $cacert =~ s/\n/\\n/g;
1389 title
=> "VM $vmid",
1390 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1391 proxy
=> "http://$host:3128",
1392 'tls-port' => $port,
1393 'host-subject' => $subject,
1395 password
=> $ticket,
1396 'delete-this-file' => 1,
1400 __PACKAGE__-
>register_method({
1402 path
=> '{vmid}/status',
1405 description
=> "Directory index",
1410 additionalProperties
=> 0,
1412 node
=> get_standard_option
('pve-node'),
1413 vmid
=> get_standard_option
('pve-vmid'),
1421 subdir
=> { type
=> 'string' },
1424 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1430 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1433 { subdir
=> 'current' },
1434 { subdir
=> 'start' },
1435 { subdir
=> 'stop' },
1441 my $vm_is_ha_managed = sub {
1444 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1445 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1451 __PACKAGE__-
>register_method({
1452 name
=> 'vm_status',
1453 path
=> '{vmid}/status/current',
1456 protected
=> 1, # qemu pid files are only readable by root
1457 description
=> "Get virtual machine status.",
1459 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1462 additionalProperties
=> 0,
1464 node
=> get_standard_option
('pve-node'),
1465 vmid
=> get_standard_option
('pve-vmid'),
1468 returns
=> { type
=> 'object' },
1473 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1475 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1476 my $status = $vmstatus->{$param->{vmid
}};
1478 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1480 if ($conf->{vga
} && ($conf->{vga
} eq 'qxl')) {
1481 $status->{spice
} = 1;
1487 __PACKAGE__-
>register_method({
1489 path
=> '{vmid}/status/start',
1493 description
=> "Start virtual machine.",
1495 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1498 additionalProperties
=> 0,
1500 node
=> get_standard_option
('pve-node'),
1501 vmid
=> get_standard_option
('pve-vmid'),
1502 skiplock
=> get_standard_option
('skiplock'),
1503 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1504 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1505 machine
=> get_standard_option
('pve-qm-machine'),
1514 my $rpcenv = PVE
::RPCEnvironment
::get
();
1516 my $authuser = $rpcenv->get_user();
1518 my $node = extract_param
($param, 'node');
1520 my $vmid = extract_param
($param, 'vmid');
1522 my $machine = extract_param
($param, 'machine');
1524 my $stateuri = extract_param
($param, 'stateuri');
1525 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1526 if $stateuri && $authuser ne 'root@pam';
1528 my $skiplock = extract_param
($param, 'skiplock');
1529 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1530 if $skiplock && $authuser ne 'root@pam';
1532 my $migratedfrom = extract_param
($param, 'migratedfrom');
1533 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1534 if $migratedfrom && $authuser ne 'root@pam';
1536 my $storecfg = PVE
::Storage
::config
();
1538 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1539 $rpcenv->{type
} ne 'ha') {
1544 my $service = "pvevm:$vmid";
1546 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1548 print "Executing HA start for VM $vmid\n";
1550 PVE
::Tools
::run_command
($cmd);
1555 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1562 syslog
('info', "start VM $vmid: $upid\n");
1564 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1569 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1573 __PACKAGE__-
>register_method({
1575 path
=> '{vmid}/status/stop',
1579 description
=> "Stop virtual machine.",
1581 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1584 additionalProperties
=> 0,
1586 node
=> get_standard_option
('pve-node'),
1587 vmid
=> get_standard_option
('pve-vmid'),
1588 skiplock
=> get_standard_option
('skiplock'),
1589 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1591 description
=> "Wait maximal timeout seconds.",
1597 description
=> "Do not decativate storage volumes.",
1610 my $rpcenv = PVE
::RPCEnvironment
::get
();
1612 my $authuser = $rpcenv->get_user();
1614 my $node = extract_param
($param, 'node');
1616 my $vmid = extract_param
($param, 'vmid');
1618 my $skiplock = extract_param
($param, 'skiplock');
1619 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1620 if $skiplock && $authuser ne 'root@pam';
1622 my $keepActive = extract_param
($param, 'keepActive');
1623 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1624 if $keepActive && $authuser ne 'root@pam';
1626 my $migratedfrom = extract_param
($param, 'migratedfrom');
1627 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1628 if $migratedfrom && $authuser ne 'root@pam';
1631 my $storecfg = PVE
::Storage
::config
();
1633 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1638 my $service = "pvevm:$vmid";
1640 my $cmd = ['clusvcadm', '-d', $service];
1642 print "Executing HA stop for VM $vmid\n";
1644 PVE
::Tools
::run_command
($cmd);
1649 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1655 syslog
('info', "stop VM $vmid: $upid\n");
1657 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1658 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1663 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1667 __PACKAGE__-
>register_method({
1669 path
=> '{vmid}/status/reset',
1673 description
=> "Reset virtual machine.",
1675 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1678 additionalProperties
=> 0,
1680 node
=> get_standard_option
('pve-node'),
1681 vmid
=> get_standard_option
('pve-vmid'),
1682 skiplock
=> get_standard_option
('skiplock'),
1691 my $rpcenv = PVE
::RPCEnvironment
::get
();
1693 my $authuser = $rpcenv->get_user();
1695 my $node = extract_param
($param, 'node');
1697 my $vmid = extract_param
($param, 'vmid');
1699 my $skiplock = extract_param
($param, 'skiplock');
1700 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1701 if $skiplock && $authuser ne 'root@pam';
1703 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1708 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1713 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1716 __PACKAGE__-
>register_method({
1717 name
=> 'vm_shutdown',
1718 path
=> '{vmid}/status/shutdown',
1722 description
=> "Shutdown virtual machine.",
1724 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1727 additionalProperties
=> 0,
1729 node
=> get_standard_option
('pve-node'),
1730 vmid
=> get_standard_option
('pve-vmid'),
1731 skiplock
=> get_standard_option
('skiplock'),
1733 description
=> "Wait maximal timeout seconds.",
1739 description
=> "Make sure the VM stops.",
1745 description
=> "Do not decativate storage volumes.",
1758 my $rpcenv = PVE
::RPCEnvironment
::get
();
1760 my $authuser = $rpcenv->get_user();
1762 my $node = extract_param
($param, 'node');
1764 my $vmid = extract_param
($param, 'vmid');
1766 my $skiplock = extract_param
($param, 'skiplock');
1767 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1768 if $skiplock && $authuser ne 'root@pam';
1770 my $keepActive = extract_param
($param, 'keepActive');
1771 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1772 if $keepActive && $authuser ne 'root@pam';
1774 my $storecfg = PVE
::Storage
::config
();
1779 syslog
('info', "shutdown VM $vmid: $upid\n");
1781 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1782 1, $param->{forceStop
}, $keepActive);
1787 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1790 __PACKAGE__-
>register_method({
1791 name
=> 'vm_suspend',
1792 path
=> '{vmid}/status/suspend',
1796 description
=> "Suspend virtual machine.",
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1801 additionalProperties
=> 0,
1803 node
=> get_standard_option
('pve-node'),
1804 vmid
=> get_standard_option
('pve-vmid'),
1805 skiplock
=> get_standard_option
('skiplock'),
1814 my $rpcenv = PVE
::RPCEnvironment
::get
();
1816 my $authuser = $rpcenv->get_user();
1818 my $node = extract_param
($param, 'node');
1820 my $vmid = extract_param
($param, 'vmid');
1822 my $skiplock = extract_param
($param, 'skiplock');
1823 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1824 if $skiplock && $authuser ne 'root@pam';
1826 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1831 syslog
('info', "suspend VM $vmid: $upid\n");
1833 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1838 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1841 __PACKAGE__-
>register_method({
1842 name
=> 'vm_resume',
1843 path
=> '{vmid}/status/resume',
1847 description
=> "Resume virtual machine.",
1849 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1852 additionalProperties
=> 0,
1854 node
=> get_standard_option
('pve-node'),
1855 vmid
=> get_standard_option
('pve-vmid'),
1856 skiplock
=> get_standard_option
('skiplock'),
1865 my $rpcenv = PVE
::RPCEnvironment
::get
();
1867 my $authuser = $rpcenv->get_user();
1869 my $node = extract_param
($param, 'node');
1871 my $vmid = extract_param
($param, 'vmid');
1873 my $skiplock = extract_param
($param, 'skiplock');
1874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1875 if $skiplock && $authuser ne 'root@pam';
1877 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1882 syslog
('info', "resume VM $vmid: $upid\n");
1884 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1889 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1892 __PACKAGE__-
>register_method({
1893 name
=> 'vm_sendkey',
1894 path
=> '{vmid}/sendkey',
1898 description
=> "Send key event to virtual machine.",
1900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1903 additionalProperties
=> 0,
1905 node
=> get_standard_option
('pve-node'),
1906 vmid
=> get_standard_option
('pve-vmid'),
1907 skiplock
=> get_standard_option
('skiplock'),
1909 description
=> "The key (qemu monitor encoding).",
1914 returns
=> { type
=> 'null'},
1918 my $rpcenv = PVE
::RPCEnvironment
::get
();
1920 my $authuser = $rpcenv->get_user();
1922 my $node = extract_param
($param, 'node');
1924 my $vmid = extract_param
($param, 'vmid');
1926 my $skiplock = extract_param
($param, 'skiplock');
1927 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1928 if $skiplock && $authuser ne 'root@pam';
1930 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1935 __PACKAGE__-
>register_method({
1936 name
=> 'vm_feature',
1937 path
=> '{vmid}/feature',
1941 description
=> "Check if feature for virtual machine is available.",
1943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1946 additionalProperties
=> 0,
1948 node
=> get_standard_option
('pve-node'),
1949 vmid
=> get_standard_option
('pve-vmid'),
1951 description
=> "Feature to check.",
1953 enum
=> [ 'snapshot', 'clone', 'copy' ],
1955 snapname
=> get_standard_option
('pve-snapshot-name', {
1963 hasFeature
=> { type
=> 'boolean' },
1966 items
=> { type
=> 'string' },
1973 my $node = extract_param
($param, 'node');
1975 my $vmid = extract_param
($param, 'vmid');
1977 my $snapname = extract_param
($param, 'snapname');
1979 my $feature = extract_param
($param, 'feature');
1981 my $running = PVE
::QemuServer
::check_running
($vmid);
1983 my $conf = PVE
::QemuServer
::load_config
($vmid);
1986 my $snap = $conf->{snapshots
}->{$snapname};
1987 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1990 my $storecfg = PVE
::Storage
::config
();
1992 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1993 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1996 hasFeature
=> $hasFeature,
1997 nodes
=> [ keys %$nodelist ],
2001 __PACKAGE__-
>register_method({
2003 path
=> '{vmid}/clone',
2007 description
=> "Create a copy of virtual machine/template.",
2009 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2010 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2011 "'Datastore.AllocateSpace' on any used storage.",
2014 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2016 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2017 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2022 additionalProperties
=> 0,
2024 node
=> get_standard_option
('pve-node'),
2025 vmid
=> get_standard_option
('pve-vmid'),
2026 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2029 type
=> 'string', format
=> 'dns-name',
2030 description
=> "Set a name for the new VM.",
2035 description
=> "Description for the new VM.",
2039 type
=> 'string', format
=> 'pve-poolid',
2040 description
=> "Add the new VM to the specified pool.",
2042 snapname
=> get_standard_option
('pve-snapshot-name', {
2046 storage
=> get_standard_option
('pve-storage-id', {
2047 description
=> "Target storage for full clone.",
2052 description
=> "Target format for file storage.",
2056 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2061 description
=> "Create a full copy of all disk. This is always done when " .
2062 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2065 target
=> get_standard_option
('pve-node', {
2066 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2077 my $rpcenv = PVE
::RPCEnvironment
::get
();
2079 my $authuser = $rpcenv->get_user();
2081 my $node = extract_param
($param, 'node');
2083 my $vmid = extract_param
($param, 'vmid');
2085 my $newid = extract_param
($param, 'newid');
2087 my $pool = extract_param
($param, 'pool');
2089 if (defined($pool)) {
2090 $rpcenv->check_pool_exist($pool);
2093 my $snapname = extract_param
($param, 'snapname');
2095 my $storage = extract_param
($param, 'storage');
2097 my $format = extract_param
($param, 'format');
2099 my $target = extract_param
($param, 'target');
2101 my $localnode = PVE
::INotify
::nodename
();
2103 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2105 PVE
::Cluster
::check_node_exists
($target) if $target;
2107 my $storecfg = PVE
::Storage
::config
();
2110 # check if storage is enabled on local node
2111 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2113 # check if storage is available on target node
2114 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2115 # clone only works if target storage is shared
2116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2117 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2121 PVE
::Cluster
::check_cfs_quorum
();
2123 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2125 # exclusive lock if VM is running - else shared lock is enough;
2126 my $shared_lock = $running ?
0 : 1;
2130 # do all tests after lock
2131 # we also try to do all tests before we fork the worker
2133 my $conf = PVE
::QemuServer
::load_config
($vmid);
2135 PVE
::QemuServer
::check_lock
($conf);
2137 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2139 die "unexpected state change\n" if $verify_running != $running;
2141 die "snapshot '$snapname' does not exist\n"
2142 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2144 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2146 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2148 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2150 my $conffile = PVE
::QemuServer
::config_file
($newid);
2152 die "unable to create VM $newid: config file already exists\n"
2155 my $newconf = { lock => 'clone' };
2159 foreach my $opt (keys %$oldconf) {
2160 my $value = $oldconf->{$opt};
2162 # do not copy snapshot related info
2163 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2164 $opt eq 'vmstate' || $opt eq 'snapstate';
2166 # always change MAC! address
2167 if ($opt =~ m/^net(\d+)$/) {
2168 my $net = PVE
::QemuServer
::parse_net
($value);
2169 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2170 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2171 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2172 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2173 $newconf->{$opt} = $value; # simply copy configuration
2175 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2176 die "Full clone feature is not available"
2177 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2180 $drives->{$opt} = $drive;
2181 push @$vollist, $drive->{file
};
2184 # copy everything else
2185 $newconf->{$opt} = $value;
2189 delete $newconf->{template
};
2191 if ($param->{name
}) {
2192 $newconf->{name
} = $param->{name
};
2194 if ($oldconf->{name
}) {
2195 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2197 $newconf->{name
} = "Copy-of-VM-$vmid";
2201 if ($param->{description
}) {
2202 $newconf->{description
} = $param->{description
};
2205 # create empty/temp config - this fails if VM already exists on other node
2206 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2211 my $newvollist = [];
2214 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2216 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2218 foreach my $opt (keys %$drives) {
2219 my $drive = $drives->{$opt};
2221 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2222 $newid, $storage, $format, $drive->{full
}, $newvollist);
2224 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2226 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2229 delete $newconf->{lock};
2230 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2233 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2234 die "Failed to move config to node '$target' - rename failed: $!\n"
2235 if !rename($conffile, $newconffile);
2238 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2243 sleep 1; # some storage like rbd need to wait before release volume - really?
2245 foreach my $volid (@$newvollist) {
2246 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2249 die "clone failed: $err";
2255 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2258 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2259 # Aquire exclusive lock lock for $newid
2260 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2265 __PACKAGE__-
>register_method({
2266 name
=> 'move_vm_disk',
2267 path
=> '{vmid}/move_disk',
2271 description
=> "Move volume to different storage.",
2273 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2274 "and 'Datastore.AllocateSpace' permissions on the storage.",
2277 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2278 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2282 additionalProperties
=> 0,
2284 node
=> get_standard_option
('pve-node'),
2285 vmid
=> get_standard_option
('pve-vmid'),
2288 description
=> "The disk you want to move.",
2289 enum
=> [ PVE
::QemuServer
::disknames
() ],
2291 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2294 description
=> "Target Format.",
2295 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2300 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2306 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2314 description
=> "the task ID.",
2319 my $rpcenv = PVE
::RPCEnvironment
::get
();
2321 my $authuser = $rpcenv->get_user();
2323 my $node = extract_param
($param, 'node');
2325 my $vmid = extract_param
($param, 'vmid');
2327 my $digest = extract_param
($param, 'digest');
2329 my $disk = extract_param
($param, 'disk');
2331 my $storeid = extract_param
($param, 'storage');
2333 my $format = extract_param
($param, 'format');
2335 my $storecfg = PVE
::Storage
::config
();
2337 my $updatefn = sub {
2339 my $conf = PVE
::QemuServer
::load_config
($vmid);
2341 die "checksum missmatch (file change by other user?)\n"
2342 if $digest && $digest ne $conf->{digest
};
2344 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2346 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2348 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2350 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2353 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2354 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2358 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2359 (!$format || !$oldfmt || $oldfmt eq $format);
2361 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2363 my $running = PVE
::QemuServer
::check_running
($vmid);
2365 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2369 my $newvollist = [];
2372 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2374 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2375 $vmid, $storeid, $format, 1, $newvollist);
2377 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2379 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2381 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2385 foreach my $volid (@$newvollist) {
2386 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2389 die "storage migration failed: $err";
2392 if ($param->{delete}) {
2393 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2398 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2401 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2404 __PACKAGE__-
>register_method({
2405 name
=> 'migrate_vm',
2406 path
=> '{vmid}/migrate',
2410 description
=> "Migrate virtual machine. Creates a new migration task.",
2412 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2415 additionalProperties
=> 0,
2417 node
=> get_standard_option
('pve-node'),
2418 vmid
=> get_standard_option
('pve-vmid'),
2419 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2422 description
=> "Use online/live migration.",
2427 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2434 description
=> "the task ID.",
2439 my $rpcenv = PVE
::RPCEnvironment
::get
();
2441 my $authuser = $rpcenv->get_user();
2443 my $target = extract_param
($param, 'target');
2445 my $localnode = PVE
::INotify
::nodename
();
2446 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2448 PVE
::Cluster
::check_cfs_quorum
();
2450 PVE
::Cluster
::check_node_exists
($target);
2452 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2454 my $vmid = extract_param
($param, 'vmid');
2456 raise_param_exc
({ force
=> "Only root may use this option." })
2457 if $param->{force
} && $authuser ne 'root@pam';
2460 my $conf = PVE
::QemuServer
::load_config
($vmid);
2462 # try to detect errors early
2464 PVE
::QemuServer
::check_lock
($conf);
2466 if (PVE
::QemuServer
::check_running
($vmid)) {
2467 die "cant migrate running VM without --online\n"
2468 if !$param->{online
};
2471 my $storecfg = PVE
::Storage
::config
();
2472 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2474 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2479 my $service = "pvevm:$vmid";
2481 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2483 print "Executing HA migrate for VM $vmid to node $target\n";
2485 PVE
::Tools
::run_command
($cmd);
2490 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2497 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2500 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2505 __PACKAGE__-
>register_method({
2507 path
=> '{vmid}/monitor',
2511 description
=> "Execute Qemu monitor commands.",
2513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2516 additionalProperties
=> 0,
2518 node
=> get_standard_option
('pve-node'),
2519 vmid
=> get_standard_option
('pve-vmid'),
2522 description
=> "The monitor command.",
2526 returns
=> { type
=> 'string'},
2530 my $vmid = $param->{vmid
};
2532 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2536 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2538 $res = "ERROR: $@" if $@;
2543 __PACKAGE__-
>register_method({
2544 name
=> 'resize_vm',
2545 path
=> '{vmid}/resize',
2549 description
=> "Extend volume size.",
2551 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2554 additionalProperties
=> 0,
2556 node
=> get_standard_option
('pve-node'),
2557 vmid
=> get_standard_option
('pve-vmid'),
2558 skiplock
=> get_standard_option
('skiplock'),
2561 description
=> "The disk you want to resize.",
2562 enum
=> [PVE
::QemuServer
::disknames
()],
2566 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2567 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.",
2571 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2577 returns
=> { type
=> 'null'},
2581 my $rpcenv = PVE
::RPCEnvironment
::get
();
2583 my $authuser = $rpcenv->get_user();
2585 my $node = extract_param
($param, 'node');
2587 my $vmid = extract_param
($param, 'vmid');
2589 my $digest = extract_param
($param, 'digest');
2591 my $disk = extract_param
($param, 'disk');
2593 my $sizestr = extract_param
($param, 'size');
2595 my $skiplock = extract_param
($param, 'skiplock');
2596 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2597 if $skiplock && $authuser ne 'root@pam';
2599 my $storecfg = PVE
::Storage
::config
();
2601 my $updatefn = sub {
2603 my $conf = PVE
::QemuServer
::load_config
($vmid);
2605 die "checksum missmatch (file change by other user?)\n"
2606 if $digest && $digest ne $conf->{digest
};
2607 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2609 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2611 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2613 my $volid = $drive->{file
};
2615 die "disk '$disk' has no associated volume\n" if !$volid;
2617 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2619 die "you can't online resize a virtio windows bootdisk\n"
2620 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2622 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2624 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2626 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2628 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2629 my ($ext, $newsize, $unit) = ($1, $2, $4);
2632 $newsize = $newsize * 1024;
2633 } elsif ($unit eq 'M') {
2634 $newsize = $newsize * 1024 * 1024;
2635 } elsif ($unit eq 'G') {
2636 $newsize = $newsize * 1024 * 1024 * 1024;
2637 } elsif ($unit eq 'T') {
2638 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2641 $newsize += $size if $ext;
2642 $newsize = int($newsize);
2644 die "unable to skrink disk size\n" if $newsize < $size;
2646 return if $size == $newsize;
2648 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2650 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2652 $drive->{size
} = $newsize;
2653 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2655 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2658 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2662 __PACKAGE__-
>register_method({
2663 name
=> 'snapshot_list',
2664 path
=> '{vmid}/snapshot',
2666 description
=> "List all snapshots.",
2668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2671 protected
=> 1, # qemu pid files are only readable by root
2673 additionalProperties
=> 0,
2675 vmid
=> get_standard_option
('pve-vmid'),
2676 node
=> get_standard_option
('pve-node'),
2685 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2690 my $vmid = $param->{vmid
};
2692 my $conf = PVE
::QemuServer
::load_config
($vmid);
2693 my $snaphash = $conf->{snapshots
} || {};
2697 foreach my $name (keys %$snaphash) {
2698 my $d = $snaphash->{$name};
2701 snaptime
=> $d->{snaptime
} || 0,
2702 vmstate
=> $d->{vmstate
} ?
1 : 0,
2703 description
=> $d->{description
} || '',
2705 $item->{parent
} = $d->{parent
} if $d->{parent
};
2706 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2710 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2711 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2712 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2714 push @$res, $current;
2719 __PACKAGE__-
>register_method({
2721 path
=> '{vmid}/snapshot',
2725 description
=> "Snapshot a VM.",
2727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2730 additionalProperties
=> 0,
2732 node
=> get_standard_option
('pve-node'),
2733 vmid
=> get_standard_option
('pve-vmid'),
2734 snapname
=> get_standard_option
('pve-snapshot-name'),
2738 description
=> "Save the vmstate",
2743 description
=> "Freeze the filesystem",
2748 description
=> "A textual description or comment.",
2754 description
=> "the task ID.",
2759 my $rpcenv = PVE
::RPCEnvironment
::get
();
2761 my $authuser = $rpcenv->get_user();
2763 my $node = extract_param
($param, 'node');
2765 my $vmid = extract_param
($param, 'vmid');
2767 my $snapname = extract_param
($param, 'snapname');
2769 die "unable to use snapshot name 'current' (reserved name)\n"
2770 if $snapname eq 'current';
2773 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2774 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2775 $param->{freezefs
}, $param->{description
});
2778 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2781 __PACKAGE__-
>register_method({
2782 name
=> 'snapshot_cmd_idx',
2783 path
=> '{vmid}/snapshot/{snapname}',
2790 additionalProperties
=> 0,
2792 vmid
=> get_standard_option
('pve-vmid'),
2793 node
=> get_standard_option
('pve-node'),
2794 snapname
=> get_standard_option
('pve-snapshot-name'),
2803 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2810 push @$res, { cmd
=> 'rollback' };
2811 push @$res, { cmd
=> 'config' };
2816 __PACKAGE__-
>register_method({
2817 name
=> 'update_snapshot_config',
2818 path
=> '{vmid}/snapshot/{snapname}/config',
2822 description
=> "Update snapshot metadata.",
2824 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2827 additionalProperties
=> 0,
2829 node
=> get_standard_option
('pve-node'),
2830 vmid
=> get_standard_option
('pve-vmid'),
2831 snapname
=> get_standard_option
('pve-snapshot-name'),
2835 description
=> "A textual description or comment.",
2839 returns
=> { type
=> 'null' },
2843 my $rpcenv = PVE
::RPCEnvironment
::get
();
2845 my $authuser = $rpcenv->get_user();
2847 my $vmid = extract_param
($param, 'vmid');
2849 my $snapname = extract_param
($param, 'snapname');
2851 return undef if !defined($param->{description
});
2853 my $updatefn = sub {
2855 my $conf = PVE
::QemuServer
::load_config
($vmid);
2857 PVE
::QemuServer
::check_lock
($conf);
2859 my $snap = $conf->{snapshots
}->{$snapname};
2861 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2863 $snap->{description
} = $param->{description
} if defined($param->{description
});
2865 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2868 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2873 __PACKAGE__-
>register_method({
2874 name
=> 'get_snapshot_config',
2875 path
=> '{vmid}/snapshot/{snapname}/config',
2878 description
=> "Get snapshot configuration",
2880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2883 additionalProperties
=> 0,
2885 node
=> get_standard_option
('pve-node'),
2886 vmid
=> get_standard_option
('pve-vmid'),
2887 snapname
=> get_standard_option
('pve-snapshot-name'),
2890 returns
=> { type
=> "object" },
2894 my $rpcenv = PVE
::RPCEnvironment
::get
();
2896 my $authuser = $rpcenv->get_user();
2898 my $vmid = extract_param
($param, 'vmid');
2900 my $snapname = extract_param
($param, 'snapname');
2902 my $conf = PVE
::QemuServer
::load_config
($vmid);
2904 my $snap = $conf->{snapshots
}->{$snapname};
2906 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2911 __PACKAGE__-
>register_method({
2913 path
=> '{vmid}/snapshot/{snapname}/rollback',
2917 description
=> "Rollback VM state to specified snapshot.",
2919 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2922 additionalProperties
=> 0,
2924 node
=> get_standard_option
('pve-node'),
2925 vmid
=> get_standard_option
('pve-vmid'),
2926 snapname
=> get_standard_option
('pve-snapshot-name'),
2931 description
=> "the task ID.",
2936 my $rpcenv = PVE
::RPCEnvironment
::get
();
2938 my $authuser = $rpcenv->get_user();
2940 my $node = extract_param
($param, 'node');
2942 my $vmid = extract_param
($param, 'vmid');
2944 my $snapname = extract_param
($param, 'snapname');
2947 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2948 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2951 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2954 __PACKAGE__-
>register_method({
2955 name
=> 'delsnapshot',
2956 path
=> '{vmid}/snapshot/{snapname}',
2960 description
=> "Delete a VM snapshot.",
2962 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2965 additionalProperties
=> 0,
2967 node
=> get_standard_option
('pve-node'),
2968 vmid
=> get_standard_option
('pve-vmid'),
2969 snapname
=> get_standard_option
('pve-snapshot-name'),
2973 description
=> "For removal from config file, even if removing disk snapshots fails.",
2979 description
=> "the task ID.",
2984 my $rpcenv = PVE
::RPCEnvironment
::get
();
2986 my $authuser = $rpcenv->get_user();
2988 my $node = extract_param
($param, 'node');
2990 my $vmid = extract_param
($param, 'vmid');
2992 my $snapname = extract_param
($param, 'snapname');
2995 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2996 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2999 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3002 __PACKAGE__-
>register_method({
3004 path
=> '{vmid}/template',
3008 description
=> "Create a Template.",
3010 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3011 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3014 additionalProperties
=> 0,
3016 node
=> get_standard_option
('pve-node'),
3017 vmid
=> get_standard_option
('pve-vmid'),
3021 description
=> "If you want to convert only 1 disk to base image.",
3022 enum
=> [PVE
::QemuServer
::disknames
()],
3027 returns
=> { type
=> 'null'},
3031 my $rpcenv = PVE
::RPCEnvironment
::get
();
3033 my $authuser = $rpcenv->get_user();
3035 my $node = extract_param
($param, 'node');
3037 my $vmid = extract_param
($param, 'vmid');
3039 my $disk = extract_param
($param, 'disk');
3041 my $updatefn = sub {
3043 my $conf = PVE
::QemuServer
::load_config
($vmid);
3045 PVE
::QemuServer
::check_lock
($conf);
3047 die "unable to create template, because VM contains snapshots\n"
3048 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3050 die "you can't convert a template to a template\n"
3051 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3053 die "you can't convert a VM to template if VM is running\n"
3054 if PVE
::QemuServer
::check_running
($vmid);
3057 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3060 $conf->{template
} = 1;
3061 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3063 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3066 PVE
::QemuServer
::lock_config
($vmid, $updatefn);