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',
1328 method => 'GET', # fixme: should be POST, but howto handle that in the HTML client
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 host
=> { type
=> 'string' },
1348 port
=> { type
=> 'integer' },
1354 my $rpcenv = PVE
::RPCEnvironment
::get
();
1356 my $authuser = $rpcenv->get_user();
1358 my $vmid = $param->{vmid
};
1359 my $node = $param->{node
};
1361 my $port = PVE
::Tools
::next_vnc_port
();
1365 # Note: we currectly use "proxyto => 'node'", so this code will never trigger
1366 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1367 $remip = PVE
::Cluster
::remote_node_ip
($node);
1370 my $authpath = "/vms/$vmid";
1372 my $ticket = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $authpath);
1374 # limit ticket length to 59 charachters
1375 $ticket = substr($ticket, 0, 59);
1379 # Note: this only works if VM is on local node
1380 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1381 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1383 my $remcmd = []; #fixme
1388 syslog
('info', "starting spice proxy $upid\n");
1390 my $socket = PVE
::QemuServer
::spice_socket
($vmid);
1392 my $cmd = ['/usr/bin/socat', '-d', '-d',
1393 "TCP-LISTEN:$port,reuseaddr,fork" ];
1396 push @$cmd, "EXEC:'ssh root@$remip socat STDIO UNIX-CONNECT:$socket";
1398 push @$cmd, "UNIX-CONNECT:$socket";
1406 if ($line =~ /successfully connected from/) {
1408 } elsif ($line =~ /exiting with status/) {
1410 # Note: counting connections seems unreliable here
1411 die "client exit\n"; # if $conn_count <= 0;
1415 eval { PVE
::Tools
::run_command
($cmd, errfunc
=> $parser, outfunc
=> sub{}); };
1417 die $err if $err !~ m/client exit$/;
1423 my $upid = $rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd);
1425 PVE
::Tools
::wait_for_vnc_port
($port);
1428 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1435 password
=> $ticket,
1440 __PACKAGE__-
>register_method({
1442 path
=> '{vmid}/status',
1445 description
=> "Directory index",
1450 additionalProperties
=> 0,
1452 node
=> get_standard_option
('pve-node'),
1453 vmid
=> get_standard_option
('pve-vmid'),
1461 subdir
=> { type
=> 'string' },
1464 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1470 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1473 { subdir
=> 'current' },
1474 { subdir
=> 'start' },
1475 { subdir
=> 'stop' },
1481 my $vm_is_ha_managed = sub {
1484 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1485 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1491 __PACKAGE__-
>register_method({
1492 name
=> 'vm_status',
1493 path
=> '{vmid}/status/current',
1496 protected
=> 1, # qemu pid files are only readable by root
1497 description
=> "Get virtual machine status.",
1499 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1502 additionalProperties
=> 0,
1504 node
=> get_standard_option
('pve-node'),
1505 vmid
=> get_standard_option
('pve-vmid'),
1508 returns
=> { type
=> 'object' },
1513 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1515 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1516 my $status = $vmstatus->{$param->{vmid
}};
1518 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1523 __PACKAGE__-
>register_method({
1525 path
=> '{vmid}/status/start',
1529 description
=> "Start virtual machine.",
1531 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1534 additionalProperties
=> 0,
1536 node
=> get_standard_option
('pve-node'),
1537 vmid
=> get_standard_option
('pve-vmid'),
1538 skiplock
=> get_standard_option
('skiplock'),
1539 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1540 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1541 machine
=> get_standard_option
('pve-qm-machine'),
1550 my $rpcenv = PVE
::RPCEnvironment
::get
();
1552 my $authuser = $rpcenv->get_user();
1554 my $node = extract_param
($param, 'node');
1556 my $vmid = extract_param
($param, 'vmid');
1558 my $machine = extract_param
($param, 'machine');
1560 my $stateuri = extract_param
($param, 'stateuri');
1561 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1562 if $stateuri && $authuser ne 'root@pam';
1564 my $skiplock = extract_param
($param, 'skiplock');
1565 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1566 if $skiplock && $authuser ne 'root@pam';
1568 my $migratedfrom = extract_param
($param, 'migratedfrom');
1569 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1570 if $migratedfrom && $authuser ne 'root@pam';
1572 my $storecfg = PVE
::Storage
::config
();
1574 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1575 $rpcenv->{type
} ne 'ha') {
1580 my $service = "pvevm:$vmid";
1582 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1584 print "Executing HA start for VM $vmid\n";
1586 PVE
::Tools
::run_command
($cmd);
1591 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1598 syslog
('info', "start VM $vmid: $upid\n");
1600 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1605 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1609 __PACKAGE__-
>register_method({
1611 path
=> '{vmid}/status/stop',
1615 description
=> "Stop virtual machine.",
1617 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1624 skiplock
=> get_standard_option
('skiplock'),
1625 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1627 description
=> "Wait maximal timeout seconds.",
1633 description
=> "Do not decativate storage volumes.",
1646 my $rpcenv = PVE
::RPCEnvironment
::get
();
1648 my $authuser = $rpcenv->get_user();
1650 my $node = extract_param
($param, 'node');
1652 my $vmid = extract_param
($param, 'vmid');
1654 my $skiplock = extract_param
($param, 'skiplock');
1655 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1656 if $skiplock && $authuser ne 'root@pam';
1658 my $keepActive = extract_param
($param, 'keepActive');
1659 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1660 if $keepActive && $authuser ne 'root@pam';
1662 my $migratedfrom = extract_param
($param, 'migratedfrom');
1663 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1664 if $migratedfrom && $authuser ne 'root@pam';
1667 my $storecfg = PVE
::Storage
::config
();
1669 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1674 my $service = "pvevm:$vmid";
1676 my $cmd = ['clusvcadm', '-d', $service];
1678 print "Executing HA stop for VM $vmid\n";
1680 PVE
::Tools
::run_command
($cmd);
1685 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1691 syslog
('info', "stop VM $vmid: $upid\n");
1693 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1694 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1699 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1703 __PACKAGE__-
>register_method({
1705 path
=> '{vmid}/status/reset',
1709 description
=> "Reset virtual machine.",
1711 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1718 skiplock
=> get_standard_option
('skiplock'),
1727 my $rpcenv = PVE
::RPCEnvironment
::get
();
1729 my $authuser = $rpcenv->get_user();
1731 my $node = extract_param
($param, 'node');
1733 my $vmid = extract_param
($param, 'vmid');
1735 my $skiplock = extract_param
($param, 'skiplock');
1736 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1737 if $skiplock && $authuser ne 'root@pam';
1739 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1744 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1749 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1752 __PACKAGE__-
>register_method({
1753 name
=> 'vm_shutdown',
1754 path
=> '{vmid}/status/shutdown',
1758 description
=> "Shutdown virtual machine.",
1760 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1763 additionalProperties
=> 0,
1765 node
=> get_standard_option
('pve-node'),
1766 vmid
=> get_standard_option
('pve-vmid'),
1767 skiplock
=> get_standard_option
('skiplock'),
1769 description
=> "Wait maximal timeout seconds.",
1775 description
=> "Make sure the VM stops.",
1781 description
=> "Do not decativate storage volumes.",
1794 my $rpcenv = PVE
::RPCEnvironment
::get
();
1796 my $authuser = $rpcenv->get_user();
1798 my $node = extract_param
($param, 'node');
1800 my $vmid = extract_param
($param, 'vmid');
1802 my $skiplock = extract_param
($param, 'skiplock');
1803 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1804 if $skiplock && $authuser ne 'root@pam';
1806 my $keepActive = extract_param
($param, 'keepActive');
1807 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1808 if $keepActive && $authuser ne 'root@pam';
1810 my $storecfg = PVE
::Storage
::config
();
1815 syslog
('info', "shutdown VM $vmid: $upid\n");
1817 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1818 1, $param->{forceStop
}, $keepActive);
1823 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1826 __PACKAGE__-
>register_method({
1827 name
=> 'vm_suspend',
1828 path
=> '{vmid}/status/suspend',
1832 description
=> "Suspend virtual machine.",
1834 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1837 additionalProperties
=> 0,
1839 node
=> get_standard_option
('pve-node'),
1840 vmid
=> get_standard_option
('pve-vmid'),
1841 skiplock
=> get_standard_option
('skiplock'),
1850 my $rpcenv = PVE
::RPCEnvironment
::get
();
1852 my $authuser = $rpcenv->get_user();
1854 my $node = extract_param
($param, 'node');
1856 my $vmid = extract_param
($param, 'vmid');
1858 my $skiplock = extract_param
($param, 'skiplock');
1859 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1860 if $skiplock && $authuser ne 'root@pam';
1862 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1867 syslog
('info', "suspend VM $vmid: $upid\n");
1869 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1874 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1877 __PACKAGE__-
>register_method({
1878 name
=> 'vm_resume',
1879 path
=> '{vmid}/status/resume',
1883 description
=> "Resume virtual machine.",
1885 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1888 additionalProperties
=> 0,
1890 node
=> get_standard_option
('pve-node'),
1891 vmid
=> get_standard_option
('pve-vmid'),
1892 skiplock
=> get_standard_option
('skiplock'),
1901 my $rpcenv = PVE
::RPCEnvironment
::get
();
1903 my $authuser = $rpcenv->get_user();
1905 my $node = extract_param
($param, 'node');
1907 my $vmid = extract_param
($param, 'vmid');
1909 my $skiplock = extract_param
($param, 'skiplock');
1910 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1911 if $skiplock && $authuser ne 'root@pam';
1913 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1918 syslog
('info', "resume VM $vmid: $upid\n");
1920 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1925 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1928 __PACKAGE__-
>register_method({
1929 name
=> 'vm_sendkey',
1930 path
=> '{vmid}/sendkey',
1934 description
=> "Send key event to virtual machine.",
1936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1943 skiplock
=> get_standard_option
('skiplock'),
1945 description
=> "The key (qemu monitor encoding).",
1950 returns
=> { type
=> 'null'},
1954 my $rpcenv = PVE
::RPCEnvironment
::get
();
1956 my $authuser = $rpcenv->get_user();
1958 my $node = extract_param
($param, 'node');
1960 my $vmid = extract_param
($param, 'vmid');
1962 my $skiplock = extract_param
($param, 'skiplock');
1963 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1964 if $skiplock && $authuser ne 'root@pam';
1966 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1971 __PACKAGE__-
>register_method({
1972 name
=> 'vm_feature',
1973 path
=> '{vmid}/feature',
1977 description
=> "Check if feature for virtual machine is available.",
1979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1982 additionalProperties
=> 0,
1984 node
=> get_standard_option
('pve-node'),
1985 vmid
=> get_standard_option
('pve-vmid'),
1987 description
=> "Feature to check.",
1989 enum
=> [ 'snapshot', 'clone', 'copy' ],
1991 snapname
=> get_standard_option
('pve-snapshot-name', {
1999 hasFeature
=> { type
=> 'boolean' },
2002 items
=> { type
=> 'string' },
2009 my $node = extract_param
($param, 'node');
2011 my $vmid = extract_param
($param, 'vmid');
2013 my $snapname = extract_param
($param, 'snapname');
2015 my $feature = extract_param
($param, 'feature');
2017 my $running = PVE
::QemuServer
::check_running
($vmid);
2019 my $conf = PVE
::QemuServer
::load_config
($vmid);
2022 my $snap = $conf->{snapshots
}->{$snapname};
2023 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2026 my $storecfg = PVE
::Storage
::config
();
2028 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2029 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2032 hasFeature
=> $hasFeature,
2033 nodes
=> [ keys %$nodelist ],
2037 __PACKAGE__-
>register_method({
2039 path
=> '{vmid}/clone',
2043 description
=> "Create a copy of virtual machine/template.",
2045 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2046 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2047 "'Datastore.AllocateSpace' on any used storage.",
2050 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2052 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2053 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2058 additionalProperties
=> 0,
2060 node
=> get_standard_option
('pve-node'),
2061 vmid
=> get_standard_option
('pve-vmid'),
2062 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2065 type
=> 'string', format
=> 'dns-name',
2066 description
=> "Set a name for the new VM.",
2071 description
=> "Description for the new VM.",
2075 type
=> 'string', format
=> 'pve-poolid',
2076 description
=> "Add the new VM to the specified pool.",
2078 snapname
=> get_standard_option
('pve-snapshot-name', {
2082 storage
=> get_standard_option
('pve-storage-id', {
2083 description
=> "Target storage for full clone.",
2088 description
=> "Target format for file storage.",
2092 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2097 description
=> "Create a full copy of all disk. This is always done when " .
2098 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2101 target
=> get_standard_option
('pve-node', {
2102 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2113 my $rpcenv = PVE
::RPCEnvironment
::get
();
2115 my $authuser = $rpcenv->get_user();
2117 my $node = extract_param
($param, 'node');
2119 my $vmid = extract_param
($param, 'vmid');
2121 my $newid = extract_param
($param, 'newid');
2123 my $pool = extract_param
($param, 'pool');
2125 if (defined($pool)) {
2126 $rpcenv->check_pool_exist($pool);
2129 my $snapname = extract_param
($param, 'snapname');
2131 my $storage = extract_param
($param, 'storage');
2133 my $format = extract_param
($param, 'format');
2135 my $target = extract_param
($param, 'target');
2137 my $localnode = PVE
::INotify
::nodename
();
2139 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2141 PVE
::Cluster
::check_node_exists
($target) if $target;
2143 my $storecfg = PVE
::Storage
::config
();
2146 # check if storage is enabled on local node
2147 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2149 # check if storage is available on target node
2150 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2151 # clone only works if target storage is shared
2152 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2153 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2157 PVE
::Cluster
::check_cfs_quorum
();
2159 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2161 # exclusive lock if VM is running - else shared lock is enough;
2162 my $shared_lock = $running ?
0 : 1;
2166 # do all tests after lock
2167 # we also try to do all tests before we fork the worker
2169 my $conf = PVE
::QemuServer
::load_config
($vmid);
2171 PVE
::QemuServer
::check_lock
($conf);
2173 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2175 die "unexpected state change\n" if $verify_running != $running;
2177 die "snapshot '$snapname' does not exist\n"
2178 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2180 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2182 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2184 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2186 my $conffile = PVE
::QemuServer
::config_file
($newid);
2188 die "unable to create VM $newid: config file already exists\n"
2191 my $newconf = { lock => 'clone' };
2195 foreach my $opt (keys %$oldconf) {
2196 my $value = $oldconf->{$opt};
2198 # do not copy snapshot related info
2199 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2200 $opt eq 'vmstate' || $opt eq 'snapstate';
2202 # always change MAC! address
2203 if ($opt =~ m/^net(\d+)$/) {
2204 my $net = PVE
::QemuServer
::parse_net
($value);
2205 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2206 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2207 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2208 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2209 $newconf->{$opt} = $value; # simply copy configuration
2211 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2212 die "Full clone feature is not available"
2213 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2216 $drives->{$opt} = $drive;
2217 push @$vollist, $drive->{file
};
2220 # copy everything else
2221 $newconf->{$opt} = $value;
2225 delete $newconf->{template
};
2227 if ($param->{name
}) {
2228 $newconf->{name
} = $param->{name
};
2230 if ($oldconf->{name
}) {
2231 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2233 $newconf->{name
} = "Copy-of-VM-$vmid";
2237 if ($param->{description
}) {
2238 $newconf->{description
} = $param->{description
};
2241 # create empty/temp config - this fails if VM already exists on other node
2242 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2247 my $newvollist = [];
2250 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2252 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2254 foreach my $opt (keys %$drives) {
2255 my $drive = $drives->{$opt};
2257 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2258 $newid, $storage, $format, $drive->{full
}, $newvollist);
2260 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2262 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2265 delete $newconf->{lock};
2266 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2269 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2270 die "Failed to move config to node '$target' - rename failed: $!\n"
2271 if !rename($conffile, $newconffile);
2274 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2279 sleep 1; # some storage like rbd need to wait before release volume - really?
2281 foreach my $volid (@$newvollist) {
2282 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2285 die "clone failed: $err";
2291 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2294 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2295 # Aquire exclusive lock lock for $newid
2296 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2301 __PACKAGE__-
>register_method({
2302 name
=> 'move_vm_disk',
2303 path
=> '{vmid}/move_disk',
2307 description
=> "Move volume to different storage.",
2309 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2310 "and 'Datastore.AllocateSpace' permissions on the storage.",
2313 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2314 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2318 additionalProperties
=> 0,
2320 node
=> get_standard_option
('pve-node'),
2321 vmid
=> get_standard_option
('pve-vmid'),
2324 description
=> "The disk you want to move.",
2325 enum
=> [ PVE
::QemuServer
::disknames
() ],
2327 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2330 description
=> "Target Format.",
2331 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2336 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2342 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2350 description
=> "the task ID.",
2355 my $rpcenv = PVE
::RPCEnvironment
::get
();
2357 my $authuser = $rpcenv->get_user();
2359 my $node = extract_param
($param, 'node');
2361 my $vmid = extract_param
($param, 'vmid');
2363 my $digest = extract_param
($param, 'digest');
2365 my $disk = extract_param
($param, 'disk');
2367 my $storeid = extract_param
($param, 'storage');
2369 my $format = extract_param
($param, 'format');
2371 my $storecfg = PVE
::Storage
::config
();
2373 my $updatefn = sub {
2375 my $conf = PVE
::QemuServer
::load_config
($vmid);
2377 die "checksum missmatch (file change by other user?)\n"
2378 if $digest && $digest ne $conf->{digest
};
2380 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2382 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2384 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2386 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2389 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2390 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2394 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2395 (!$format || !$oldfmt || $oldfmt eq $format);
2397 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2399 my $running = PVE
::QemuServer
::check_running
($vmid);
2401 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2405 my $newvollist = [];
2408 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2410 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2411 $vmid, $storeid, $format, 1, $newvollist);
2413 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2415 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2417 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2421 foreach my $volid (@$newvollist) {
2422 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2425 die "storage migration failed: $err";
2428 if ($param->{delete}) {
2429 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2434 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2437 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2440 __PACKAGE__-
>register_method({
2441 name
=> 'migrate_vm',
2442 path
=> '{vmid}/migrate',
2446 description
=> "Migrate virtual machine. Creates a new migration task.",
2448 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2451 additionalProperties
=> 0,
2453 node
=> get_standard_option
('pve-node'),
2454 vmid
=> get_standard_option
('pve-vmid'),
2455 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2458 description
=> "Use online/live migration.",
2463 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2470 description
=> "the task ID.",
2475 my $rpcenv = PVE
::RPCEnvironment
::get
();
2477 my $authuser = $rpcenv->get_user();
2479 my $target = extract_param
($param, 'target');
2481 my $localnode = PVE
::INotify
::nodename
();
2482 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2484 PVE
::Cluster
::check_cfs_quorum
();
2486 PVE
::Cluster
::check_node_exists
($target);
2488 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2490 my $vmid = extract_param
($param, 'vmid');
2492 raise_param_exc
({ force
=> "Only root may use this option." })
2493 if $param->{force
} && $authuser ne 'root@pam';
2496 my $conf = PVE
::QemuServer
::load_config
($vmid);
2498 # try to detect errors early
2500 PVE
::QemuServer
::check_lock
($conf);
2502 if (PVE
::QemuServer
::check_running
($vmid)) {
2503 die "cant migrate running VM without --online\n"
2504 if !$param->{online
};
2507 my $storecfg = PVE
::Storage
::config
();
2508 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2510 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2515 my $service = "pvevm:$vmid";
2517 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2519 print "Executing HA migrate for VM $vmid to node $target\n";
2521 PVE
::Tools
::run_command
($cmd);
2526 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2533 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2536 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2541 __PACKAGE__-
>register_method({
2543 path
=> '{vmid}/monitor',
2547 description
=> "Execute Qemu monitor commands.",
2549 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2552 additionalProperties
=> 0,
2554 node
=> get_standard_option
('pve-node'),
2555 vmid
=> get_standard_option
('pve-vmid'),
2558 description
=> "The monitor command.",
2562 returns
=> { type
=> 'string'},
2566 my $vmid = $param->{vmid
};
2568 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2572 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2574 $res = "ERROR: $@" if $@;
2579 __PACKAGE__-
>register_method({
2580 name
=> 'resize_vm',
2581 path
=> '{vmid}/resize',
2585 description
=> "Extend volume size.",
2587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2590 additionalProperties
=> 0,
2592 node
=> get_standard_option
('pve-node'),
2593 vmid
=> get_standard_option
('pve-vmid'),
2594 skiplock
=> get_standard_option
('skiplock'),
2597 description
=> "The disk you want to resize.",
2598 enum
=> [PVE
::QemuServer
::disknames
()],
2602 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2603 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.",
2607 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2613 returns
=> { type
=> 'null'},
2617 my $rpcenv = PVE
::RPCEnvironment
::get
();
2619 my $authuser = $rpcenv->get_user();
2621 my $node = extract_param
($param, 'node');
2623 my $vmid = extract_param
($param, 'vmid');
2625 my $digest = extract_param
($param, 'digest');
2627 my $disk = extract_param
($param, 'disk');
2629 my $sizestr = extract_param
($param, 'size');
2631 my $skiplock = extract_param
($param, 'skiplock');
2632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2633 if $skiplock && $authuser ne 'root@pam';
2635 my $storecfg = PVE
::Storage
::config
();
2637 my $updatefn = sub {
2639 my $conf = PVE
::QemuServer
::load_config
($vmid);
2641 die "checksum missmatch (file change by other user?)\n"
2642 if $digest && $digest ne $conf->{digest
};
2643 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2645 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2647 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2649 my $volid = $drive->{file
};
2651 die "disk '$disk' has no associated volume\n" if !$volid;
2653 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2655 die "you can't online resize a virtio windows bootdisk\n"
2656 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2658 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2660 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2662 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2664 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2665 my ($ext, $newsize, $unit) = ($1, $2, $4);
2668 $newsize = $newsize * 1024;
2669 } elsif ($unit eq 'M') {
2670 $newsize = $newsize * 1024 * 1024;
2671 } elsif ($unit eq 'G') {
2672 $newsize = $newsize * 1024 * 1024 * 1024;
2673 } elsif ($unit eq 'T') {
2674 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2677 $newsize += $size if $ext;
2678 $newsize = int($newsize);
2680 die "unable to skrink disk size\n" if $newsize < $size;
2682 return if $size == $newsize;
2684 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2686 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2688 $drive->{size
} = $newsize;
2689 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2691 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2694 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2698 __PACKAGE__-
>register_method({
2699 name
=> 'snapshot_list',
2700 path
=> '{vmid}/snapshot',
2702 description
=> "List all snapshots.",
2704 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2707 protected
=> 1, # qemu pid files are only readable by root
2709 additionalProperties
=> 0,
2711 vmid
=> get_standard_option
('pve-vmid'),
2712 node
=> get_standard_option
('pve-node'),
2721 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2726 my $vmid = $param->{vmid
};
2728 my $conf = PVE
::QemuServer
::load_config
($vmid);
2729 my $snaphash = $conf->{snapshots
} || {};
2733 foreach my $name (keys %$snaphash) {
2734 my $d = $snaphash->{$name};
2737 snaptime
=> $d->{snaptime
} || 0,
2738 vmstate
=> $d->{vmstate
} ?
1 : 0,
2739 description
=> $d->{description
} || '',
2741 $item->{parent
} = $d->{parent
} if $d->{parent
};
2742 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2746 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2747 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2748 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2750 push @$res, $current;
2755 __PACKAGE__-
>register_method({
2757 path
=> '{vmid}/snapshot',
2761 description
=> "Snapshot a VM.",
2763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2766 additionalProperties
=> 0,
2768 node
=> get_standard_option
('pve-node'),
2769 vmid
=> get_standard_option
('pve-vmid'),
2770 snapname
=> get_standard_option
('pve-snapshot-name'),
2774 description
=> "Save the vmstate",
2779 description
=> "Freeze the filesystem",
2784 description
=> "A textual description or comment.",
2790 description
=> "the task ID.",
2795 my $rpcenv = PVE
::RPCEnvironment
::get
();
2797 my $authuser = $rpcenv->get_user();
2799 my $node = extract_param
($param, 'node');
2801 my $vmid = extract_param
($param, 'vmid');
2803 my $snapname = extract_param
($param, 'snapname');
2805 die "unable to use snapshot name 'current' (reserved name)\n"
2806 if $snapname eq 'current';
2809 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2810 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2811 $param->{freezefs
}, $param->{description
});
2814 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2817 __PACKAGE__-
>register_method({
2818 name
=> 'snapshot_cmd_idx',
2819 path
=> '{vmid}/snapshot/{snapname}',
2826 additionalProperties
=> 0,
2828 vmid
=> get_standard_option
('pve-vmid'),
2829 node
=> get_standard_option
('pve-node'),
2830 snapname
=> get_standard_option
('pve-snapshot-name'),
2839 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2846 push @$res, { cmd
=> 'rollback' };
2847 push @$res, { cmd
=> 'config' };
2852 __PACKAGE__-
>register_method({
2853 name
=> 'update_snapshot_config',
2854 path
=> '{vmid}/snapshot/{snapname}/config',
2858 description
=> "Update snapshot metadata.",
2860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid'),
2867 snapname
=> get_standard_option
('pve-snapshot-name'),
2871 description
=> "A textual description or comment.",
2875 returns
=> { type
=> 'null' },
2879 my $rpcenv = PVE
::RPCEnvironment
::get
();
2881 my $authuser = $rpcenv->get_user();
2883 my $vmid = extract_param
($param, 'vmid');
2885 my $snapname = extract_param
($param, 'snapname');
2887 return undef if !defined($param->{description
});
2889 my $updatefn = sub {
2891 my $conf = PVE
::QemuServer
::load_config
($vmid);
2893 PVE
::QemuServer
::check_lock
($conf);
2895 my $snap = $conf->{snapshots
}->{$snapname};
2897 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2899 $snap->{description
} = $param->{description
} if defined($param->{description
});
2901 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2904 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2909 __PACKAGE__-
>register_method({
2910 name
=> 'get_snapshot_config',
2911 path
=> '{vmid}/snapshot/{snapname}/config',
2914 description
=> "Get snapshot configuration",
2916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2919 additionalProperties
=> 0,
2921 node
=> get_standard_option
('pve-node'),
2922 vmid
=> get_standard_option
('pve-vmid'),
2923 snapname
=> get_standard_option
('pve-snapshot-name'),
2926 returns
=> { type
=> "object" },
2930 my $rpcenv = PVE
::RPCEnvironment
::get
();
2932 my $authuser = $rpcenv->get_user();
2934 my $vmid = extract_param
($param, 'vmid');
2936 my $snapname = extract_param
($param, 'snapname');
2938 my $conf = PVE
::QemuServer
::load_config
($vmid);
2940 my $snap = $conf->{snapshots
}->{$snapname};
2942 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2947 __PACKAGE__-
>register_method({
2949 path
=> '{vmid}/snapshot/{snapname}/rollback',
2953 description
=> "Rollback VM state to specified snapshot.",
2955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2958 additionalProperties
=> 0,
2960 node
=> get_standard_option
('pve-node'),
2961 vmid
=> get_standard_option
('pve-vmid'),
2962 snapname
=> get_standard_option
('pve-snapshot-name'),
2967 description
=> "the task ID.",
2972 my $rpcenv = PVE
::RPCEnvironment
::get
();
2974 my $authuser = $rpcenv->get_user();
2976 my $node = extract_param
($param, 'node');
2978 my $vmid = extract_param
($param, 'vmid');
2980 my $snapname = extract_param
($param, 'snapname');
2983 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2984 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2987 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2990 __PACKAGE__-
>register_method({
2991 name
=> 'delsnapshot',
2992 path
=> '{vmid}/snapshot/{snapname}',
2996 description
=> "Delete a VM snapshot.",
2998 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3001 additionalProperties
=> 0,
3003 node
=> get_standard_option
('pve-node'),
3004 vmid
=> get_standard_option
('pve-vmid'),
3005 snapname
=> get_standard_option
('pve-snapshot-name'),
3009 description
=> "For removal from config file, even if removing disk snapshots fails.",
3015 description
=> "the task ID.",
3020 my $rpcenv = PVE
::RPCEnvironment
::get
();
3022 my $authuser = $rpcenv->get_user();
3024 my $node = extract_param
($param, 'node');
3026 my $vmid = extract_param
($param, 'vmid');
3028 my $snapname = extract_param
($param, 'snapname');
3031 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3032 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3035 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3038 __PACKAGE__-
>register_method({
3040 path
=> '{vmid}/template',
3044 description
=> "Create a Template.",
3046 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3047 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3050 additionalProperties
=> 0,
3052 node
=> get_standard_option
('pve-node'),
3053 vmid
=> get_standard_option
('pve-vmid'),
3057 description
=> "If you want to convert only 1 disk to base image.",
3058 enum
=> [PVE
::QemuServer
::disknames
()],
3063 returns
=> { type
=> 'null'},
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3071 my $node = extract_param
($param, 'node');
3073 my $vmid = extract_param
($param, 'vmid');
3075 my $disk = extract_param
($param, 'disk');
3077 my $updatefn = sub {
3079 my $conf = PVE
::QemuServer
::load_config
($vmid);
3081 PVE
::QemuServer
::check_lock
($conf);
3083 die "unable to create template, because VM contains snapshots\n"
3084 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3086 die "you can't convert a template to a template\n"
3087 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3089 die "you can't convert a VM to template if VM is running\n"
3090 if PVE
::QemuServer
::check_running
($vmid);
3093 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3096 $conf->{template
} = 1;
3097 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3099 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3102 PVE
::QemuServer
::lock_config
($vmid, $updatefn);