1 package PVE
::API2
::Qemu
;
8 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
10 use PVE
::Tools
qw(extract_param);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
18 use PVE
::AccessControl
;
22 use Data
::Dumper
; # fixme: remove
24 use base
qw(PVE::RESTHandler);
26 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.";
28 my $resolve_cdrom_alias = sub {
31 if (my $value = $param->{cdrom
}) {
32 $value .= ",media=cdrom" if $value !~ m/media=/;
33 $param->{ide2
} = $value;
34 delete $param->{cdrom
};
39 my $check_storage_access = sub {
40 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
42 PVE
::QemuServer
::foreach_drive
($settings, sub {
43 my ($ds, $drive) = @_;
45 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
47 my $volid = $drive->{file
};
49 if (!$volid || $volid eq 'none') {
51 } elsif ($isCDROM && ($volid eq 'cdrom')) {
52 $rpcenv->check($authuser, "/", ['Sys.Console']);
53 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
54 my ($storeid, $size) = ($2 || $default_storage, $3);
55 die "no storage ID specified (and no default storage)\n" if !$storeid;
56 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
58 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
63 my $check_storage_access_clone = sub {
64 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
68 PVE
::QemuServer
::foreach_drive
($conf, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
75 return if !$volid || $volid eq 'none';
78 if ($volid eq 'cdrom') {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
81 # we simply allow access
82 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
83 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
84 $sharedvm = 0 if !$scfg->{shared
};
88 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
89 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
90 $sharedvm = 0 if !$scfg->{shared
};
92 $sid = $storage if $storage;
93 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
100 # Note: $pool is only needed when creating a VM, because pool permissions
101 # are automatically inherited if VM already exists inside a pool.
102 my $create_disks = sub {
103 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
108 PVE
::QemuServer
::foreach_drive
($settings, sub {
109 my ($ds, $disk) = @_;
111 my $volid = $disk->{file
};
113 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
114 delete $disk->{size
};
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
116 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
117 my ($storeid, $size) = ($2 || $default_storage, $3);
118 die "no storage ID specified (and no default storage)\n" if !$storeid;
119 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
120 my $fmt = $disk->{format
} || $defformat;
121 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
122 $fmt, undef, $size*1024*1024);
123 $disk->{file
} = $volid;
124 $disk->{size
} = $size*1024*1024*1024;
125 push @$vollist, $volid;
126 delete $disk->{format
}; # no longer needed
127 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
130 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
132 my $volid_is_new = 1;
135 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
136 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
141 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
143 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
145 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
147 die "volume $volid does not exists\n" if !$size;
149 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
380 my $restorefn = sub {
382 # fixme: this test does not work if VM exists on other node!
384 die "unable to restore vm $vmid: config file already exists\n"
387 die "unable to restore vm $vmid: vm is running\n"
388 if PVE
::QemuServer
::check_running
($vmid);
392 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
395 unique
=> $unique });
397 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
400 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
406 die "unable to create vm $vmid: config file already exists\n"
417 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
419 # try to be smart about bootdisk
420 my @disks = PVE
::QemuServer
::disknames
();
422 foreach my $ds (reverse @disks) {
423 next if !$conf->{$ds};
424 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
425 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
429 if (!$conf->{bootdisk
} && $firstdisk) {
430 $conf->{bootdisk
} = $firstdisk;
433 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
439 foreach my $volid (@$vollist) {
440 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
443 die "create failed - $err";
446 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
449 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
452 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
455 __PACKAGE__-
>register_method({
460 description
=> "Directory index",
465 additionalProperties
=> 0,
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid'),
476 subdir
=> { type
=> 'string' },
479 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
485 { subdir
=> 'config' },
486 { subdir
=> 'status' },
487 { subdir
=> 'unlink' },
488 { subdir
=> 'vncproxy' },
489 { subdir
=> 'migrate' },
490 { subdir
=> 'resize' },
491 { subdir
=> 'move' },
493 { subdir
=> 'rrddata' },
494 { subdir
=> 'monitor' },
495 { subdir
=> 'snapshot' },
496 { subdir
=> 'spiceproxy' },
502 __PACKAGE__-
>register_method({
504 path
=> '{vmid}/rrd',
506 protected
=> 1, # fixme: can we avoid that?
508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
510 description
=> "Read VM RRD statistics (returns PNG)",
512 additionalProperties
=> 0,
514 node
=> get_standard_option
('pve-node'),
515 vmid
=> get_standard_option
('pve-vmid'),
517 description
=> "Specify the time frame you are interested in.",
519 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
522 description
=> "The list of datasources you want to display.",
523 type
=> 'string', format
=> 'pve-configid-list',
526 description
=> "The RRD consolidation function",
528 enum
=> [ 'AVERAGE', 'MAX' ],
536 filename
=> { type
=> 'string' },
542 return PVE
::Cluster
::create_rrd_graph
(
543 "pve2-vm/$param->{vmid}", $param->{timeframe
},
544 $param->{ds
}, $param->{cf
});
548 __PACKAGE__-
>register_method({
550 path
=> '{vmid}/rrddata',
552 protected
=> 1, # fixme: can we avoid that?
554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
556 description
=> "Read VM RRD statistics",
558 additionalProperties
=> 0,
560 node
=> get_standard_option
('pve-node'),
561 vmid
=> get_standard_option
('pve-vmid'),
563 description
=> "Specify the time frame you are interested in.",
565 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
568 description
=> "The RRD consolidation function",
570 enum
=> [ 'AVERAGE', 'MAX' ],
585 return PVE
::Cluster
::create_rrd_data
(
586 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
590 __PACKAGE__-
>register_method({
592 path
=> '{vmid}/config',
595 description
=> "Get virtual machine configuration.",
597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
603 vmid
=> get_standard_option
('pve-vmid'),
611 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
618 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
620 delete $conf->{snapshots
};
625 my $vm_is_volid_owner = sub {
626 my ($storecfg, $vmid, $volid) =@_;
628 if ($volid !~ m
|^/|) {
630 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
631 if ($owner && ($owner == $vmid)) {
639 my $test_deallocate_drive = sub {
640 my ($storecfg, $vmid, $key, $drive, $force) = @_;
642 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
643 my $volid = $drive->{file
};
644 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
645 if ($force || $key =~ m/^unused/) {
646 my $sid = PVE
::Storage
::parse_volume_id
($volid);
655 my $delete_drive = sub {
656 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
658 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
659 my $volid = $drive->{file
};
661 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
662 if ($force || $key =~ m/^unused/) {
664 # check if the disk is really unused
665 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
666 my $path = PVE
::Storage
::path
($storecfg, $volid);
668 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
669 if $used_paths->{$path};
671 PVE
::Storage
::vdisk_free
($storecfg, $volid);
675 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
680 delete $conf->{$key};
683 my $vmconfig_delete_option = sub {
684 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
686 return if !defined($conf->{$opt});
688 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
691 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
693 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
694 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
695 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
699 my $unplugwarning = "";
700 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
701 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
702 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
703 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
704 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
705 $unplugwarning = "<br>verify that your guest support acpi hotplug";
708 if ($opt eq 'tablet') {
709 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
711 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
715 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
716 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
718 delete $conf->{$opt};
721 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
724 my $safe_num_ne = sub {
727 return 0 if !defined($a) && !defined($b);
728 return 1 if !defined($a);
729 return 1 if !defined($b);
734 my $vmconfig_update_disk = sub {
735 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
737 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
739 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
740 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
742 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
747 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
749 my $media = $drive->{media
} || 'disk';
750 my $oldmedia = $old_drive->{media
} || 'disk';
751 die "unable to change media type\n" if $media ne $oldmedia;
753 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
754 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
756 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
760 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
761 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
762 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
763 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
764 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
765 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
766 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
767 ($drive->{mbps
} || 0)*1024*1024,
768 ($drive->{mbps_rd
} || 0)*1024*1024,
769 ($drive->{mbps_wr
} || 0)*1024*1024,
771 $drive->{iops_rd
} || 0,
772 $drive->{iops_wr
} || 0)
773 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
778 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
779 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
782 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
784 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
786 if (PVE
::QemuServer
::check_running
($vmid)) {
787 if ($drive->{file
} eq 'none') {
788 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
790 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
796 } else { # hotplug new disks
798 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
802 my $vmconfig_update_net = sub {
803 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
805 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
806 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
807 my $newnet = PVE
::QemuServer
::parse_net
($value);
809 if($oldnet->{model
} ne $newnet->{model
}){
810 #if model change, we try to hot-unplug
811 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
814 if($newnet->{bridge
} && $oldnet->{bridge
}){
815 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
817 if($newnet->{rate
} ne $oldnet->{rate
}){
818 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
821 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
822 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
823 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
827 #if bridge/nat mode change, we try to hot-unplug
828 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
833 $conf->{$opt} = $value;
834 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
835 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
837 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
839 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
842 # POST/PUT {vmid}/config implementation
844 # The original API used PUT (idempotent) an we assumed that all operations
845 # are fast. But it turned out that almost any configuration change can
846 # involve hot-plug actions, or disk alloc/free. Such actions can take long
847 # time to complete and have side effects (not idempotent).
849 # The new implementation uses POST and forks a worker process. We added
850 # a new option 'background_delay'. If specified we wait up to
851 # 'background_delay' second for the worker task to complete. It returns null
852 # if the task is finished within that time, else we return the UPID.
854 my $update_vm_api = sub {
855 my ($param, $sync) = @_;
857 my $rpcenv = PVE
::RPCEnvironment
::get
();
859 my $authuser = $rpcenv->get_user();
861 my $node = extract_param
($param, 'node');
863 my $vmid = extract_param
($param, 'vmid');
865 my $digest = extract_param
($param, 'digest');
867 my $background_delay = extract_param
($param, 'background_delay');
869 my @paramarr = (); # used for log message
870 foreach my $key (keys %$param) {
871 push @paramarr, "-$key", $param->{$key};
874 my $skiplock = extract_param
($param, 'skiplock');
875 raise_param_exc
({ skiplock
=> "Only root may use this option." })
876 if $skiplock && $authuser ne 'root@pam';
878 my $delete_str = extract_param
($param, 'delete');
880 my $force = extract_param
($param, 'force');
882 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
884 my $storecfg = PVE
::Storage
::config
();
886 my $defaults = PVE
::QemuServer
::load_defaults
();
888 &$resolve_cdrom_alias($param);
890 # now try to verify all parameters
893 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
894 $opt = 'ide2' if $opt eq 'cdrom';
895 raise_param_exc
({ delete => "you can't use '-$opt' and " .
896 "-delete $opt' at the same time" })
897 if defined($param->{$opt});
899 if (!PVE
::QemuServer
::option_exists
($opt)) {
900 raise_param_exc
({ delete => "unknown option '$opt'" });
906 foreach my $opt (keys %$param) {
907 if (PVE
::QemuServer
::valid_drivename
($opt)) {
909 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
910 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
911 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
912 } elsif ($opt =~ m/^net(\d+)$/) {
914 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
915 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
919 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
921 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
923 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
927 my $conf = PVE
::QemuServer
::load_config
($vmid);
929 die "checksum missmatch (file change by other user?)\n"
930 if $digest && $digest ne $conf->{digest
};
932 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
934 if ($param->{memory
} || defined($param->{balloon
})) {
935 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
936 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
938 die "balloon value too large (must be smaller than assigned memory)\n"
939 if $balloon && $balloon > $maxmem;
942 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
946 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
948 foreach my $opt (@delete) { # delete
949 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
950 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
953 my $running = PVE
::QemuServer
::check_running
($vmid);
955 foreach my $opt (keys %$param) { # add/change
957 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
959 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
961 if (PVE
::QemuServer
::valid_drivename
($opt)) {
963 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
964 $opt, $param->{$opt}, $force);
966 } elsif ($opt =~ m/^net(\d+)$/) { #nics
968 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt});
973 if($opt eq 'tablet' && $param->{$opt} == 1){
974 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
975 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
976 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
979 $conf->{$opt} = $param->{$opt};
980 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
984 # allow manual ballooning if shares is set to zero
985 if ($running && defined($param->{balloon
}) &&
986 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
987 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
988 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
996 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
998 if ($background_delay) {
1000 # Note: It would be better to do that in the Event based HTTPServer
1001 # to avoid blocking call to sleep.
1003 my $end_time = time() + $background_delay;
1005 my $task = PVE
::Tools
::upid_decode
($upid);
1008 while (time() < $end_time) {
1009 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1011 sleep(1); # this gets interrupted when child process ends
1015 my $status = PVE
::Tools
::upid_read_status
($upid);
1016 return undef if $status eq 'OK';
1025 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1028 my $vm_config_perm_list = [
1033 'VM.Config.Network',
1035 'VM.Config.Options',
1038 __PACKAGE__-
>register_method({
1039 name
=> 'update_vm_async',
1040 path
=> '{vmid}/config',
1044 description
=> "Set virtual machine options (asynchrounous API).",
1046 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1049 additionalProperties
=> 0,
1050 properties
=> PVE
::QemuServer
::json_config_properties
(
1052 node
=> get_standard_option
('pve-node'),
1053 vmid
=> get_standard_option
('pve-vmid'),
1054 skiplock
=> get_standard_option
('skiplock'),
1056 type
=> 'string', format
=> 'pve-configid-list',
1057 description
=> "A list of settings you want to delete.",
1062 description
=> $opt_force_description,
1064 requires
=> 'delete',
1068 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1072 background_delay
=> {
1074 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1085 code
=> $update_vm_api,
1088 __PACKAGE__-
>register_method({
1089 name
=> 'update_vm',
1090 path
=> '{vmid}/config',
1094 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1096 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1099 additionalProperties
=> 0,
1100 properties
=> PVE
::QemuServer
::json_config_properties
(
1102 node
=> get_standard_option
('pve-node'),
1103 vmid
=> get_standard_option
('pve-vmid'),
1104 skiplock
=> get_standard_option
('skiplock'),
1106 type
=> 'string', format
=> 'pve-configid-list',
1107 description
=> "A list of settings you want to delete.",
1112 description
=> $opt_force_description,
1114 requires
=> 'delete',
1118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1124 returns
=> { type
=> 'null' },
1127 &$update_vm_api($param, 1);
1133 __PACKAGE__-
>register_method({
1134 name
=> 'destroy_vm',
1139 description
=> "Destroy the vm (also delete all used/owned volumes).",
1141 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1144 additionalProperties
=> 0,
1146 node
=> get_standard_option
('pve-node'),
1147 vmid
=> get_standard_option
('pve-vmid'),
1148 skiplock
=> get_standard_option
('skiplock'),
1157 my $rpcenv = PVE
::RPCEnvironment
::get
();
1159 my $authuser = $rpcenv->get_user();
1161 my $vmid = $param->{vmid
};
1163 my $skiplock = $param->{skiplock
};
1164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1165 if $skiplock && $authuser ne 'root@pam';
1168 my $conf = PVE
::QemuServer
::load_config
($vmid);
1170 my $storecfg = PVE
::Storage
::config
();
1172 my $delVMfromPoolFn = sub {
1173 my $usercfg = cfs_read_file
("user.cfg");
1174 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1175 if (my $data = $usercfg->{pools
}->{$pool}) {
1176 delete $data->{vms
}->{$vmid};
1177 delete $usercfg->{vms
}->{$vmid};
1178 cfs_write_file
("user.cfg", $usercfg);
1186 syslog
('info', "destroy VM $vmid: $upid\n");
1188 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1190 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1193 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1196 __PACKAGE__-
>register_method({
1198 path
=> '{vmid}/unlink',
1202 description
=> "Unlink/delete disk images.",
1204 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1207 additionalProperties
=> 0,
1209 node
=> get_standard_option
('pve-node'),
1210 vmid
=> get_standard_option
('pve-vmid'),
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "A list of disk IDs you want to delete.",
1217 description
=> $opt_force_description,
1222 returns
=> { type
=> 'null'},
1226 $param->{delete} = extract_param
($param, 'idlist');
1228 __PACKAGE__-
>update_vm($param);
1235 __PACKAGE__-
>register_method({
1237 path
=> '{vmid}/vncproxy',
1241 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1243 description
=> "Creates a TCP VNC proxy connections.",
1245 additionalProperties
=> 0,
1247 node
=> get_standard_option
('pve-node'),
1248 vmid
=> get_standard_option
('pve-vmid'),
1252 additionalProperties
=> 0,
1254 user
=> { type
=> 'string' },
1255 ticket
=> { type
=> 'string' },
1256 cert
=> { type
=> 'string' },
1257 port
=> { type
=> 'integer' },
1258 upid
=> { type
=> 'string' },
1264 my $rpcenv = PVE
::RPCEnvironment
::get
();
1266 my $authuser = $rpcenv->get_user();
1268 my $vmid = $param->{vmid
};
1269 my $node = $param->{node
};
1271 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1273 my $authpath = "/vms/$vmid";
1275 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1277 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1280 my $port = PVE
::Tools
::next_vnc_port
();
1285 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1286 $remip = PVE
::Cluster
::remote_node_ip
($node);
1287 # NOTE: kvm VNC traffic is already TLS encrypted
1288 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1296 syslog
('info', "starting vnc proxy $upid\n");
1300 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1302 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1303 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1304 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1305 '-timeout', $timeout, '-authpath', $authpath,
1306 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1309 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1311 my $qmstr = join(' ', @$qmcmd);
1313 # also redirect stderr (else we get RFB protocol errors)
1314 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1317 PVE
::Tools
::run_command
($cmd);
1322 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1324 PVE
::Tools
::wait_for_vnc_port
($port);
1335 __PACKAGE__-
>register_method({
1336 name
=> 'spiceproxy',
1337 path
=> '{vmid}/spiceproxy',
1340 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1342 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1344 description
=> "Returns a SPICE configuration to connect to the VM.",
1346 additionalProperties
=> 0,
1348 node
=> get_standard_option
('pve-node'),
1349 vmid
=> get_standard_option
('pve-vmid'),
1351 description
=> "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
1352 type
=> 'string', format
=> 'dns-name',
1358 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1359 additionalProperties
=> 1,
1361 type
=> { type
=> 'string' },
1362 password
=> { type
=> 'string' },
1363 proxy
=> { type
=> 'string' },
1364 host
=> { type
=> 'string' },
1365 'tls-port' => { type
=> 'integer' },
1371 my $rpcenv = PVE
::RPCEnvironment
::get
();
1373 my $authuser = $rpcenv->get_user();
1375 my $vmid = $param->{vmid
};
1376 my $node = $param->{node
};
1377 my $proxy = $param->{proxy
};
1379 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1383 my $port = PVE
::QemuServer
::spice_port
($vmid);
1384 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1385 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1388 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1393 my $filename = "/etc/pve/local/pve-ssl.pem";
1394 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1396 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1397 $cacert =~ s/\n/\\n/g;
1401 title
=> "VM $vmid",
1402 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1403 proxy
=> "http://$proxy:3128",
1404 'tls-port' => $port,
1405 'host-subject' => $subject,
1407 password
=> $ticket,
1408 'delete-this-file' => 1,
1412 __PACKAGE__-
>register_method({
1414 path
=> '{vmid}/status',
1417 description
=> "Directory index",
1422 additionalProperties
=> 0,
1424 node
=> get_standard_option
('pve-node'),
1425 vmid
=> get_standard_option
('pve-vmid'),
1433 subdir
=> { type
=> 'string' },
1436 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1442 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1445 { subdir
=> 'current' },
1446 { subdir
=> 'start' },
1447 { subdir
=> 'stop' },
1453 my $vm_is_ha_managed = sub {
1456 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1457 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1463 __PACKAGE__-
>register_method({
1464 name
=> 'vm_status',
1465 path
=> '{vmid}/status/current',
1468 protected
=> 1, # qemu pid files are only readable by root
1469 description
=> "Get virtual machine status.",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1480 returns
=> { type
=> 'object' },
1485 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1487 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1488 my $status = $vmstatus->{$param->{vmid
}};
1490 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1492 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1497 __PACKAGE__-
>register_method({
1499 path
=> '{vmid}/status/start',
1503 description
=> "Start virtual machine.",
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1508 additionalProperties
=> 0,
1510 node
=> get_standard_option
('pve-node'),
1511 vmid
=> get_standard_option
('pve-vmid'),
1512 skiplock
=> get_standard_option
('skiplock'),
1513 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1514 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1515 machine
=> get_standard_option
('pve-qm-machine'),
1524 my $rpcenv = PVE
::RPCEnvironment
::get
();
1526 my $authuser = $rpcenv->get_user();
1528 my $node = extract_param
($param, 'node');
1530 my $vmid = extract_param
($param, 'vmid');
1532 my $machine = extract_param
($param, 'machine');
1534 my $stateuri = extract_param
($param, 'stateuri');
1535 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1536 if $stateuri && $authuser ne 'root@pam';
1538 my $skiplock = extract_param
($param, 'skiplock');
1539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1540 if $skiplock && $authuser ne 'root@pam';
1542 my $migratedfrom = extract_param
($param, 'migratedfrom');
1543 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1544 if $migratedfrom && $authuser ne 'root@pam';
1546 # read spice ticket from STDIN
1548 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1549 if (defined(my $line = <>)) {
1551 $spice_ticket = $line;
1555 my $storecfg = PVE
::Storage
::config
();
1557 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1558 $rpcenv->{type
} ne 'ha') {
1563 my $service = "pvevm:$vmid";
1565 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1567 print "Executing HA start for VM $vmid\n";
1569 PVE
::Tools
::run_command
($cmd);
1574 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1581 syslog
('info', "start VM $vmid: $upid\n");
1583 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1584 $machine, $spice_ticket);
1589 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1593 __PACKAGE__-
>register_method({
1595 path
=> '{vmid}/status/stop',
1599 description
=> "Stop virtual machine.",
1601 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1604 additionalProperties
=> 0,
1606 node
=> get_standard_option
('pve-node'),
1607 vmid
=> get_standard_option
('pve-vmid'),
1608 skiplock
=> get_standard_option
('skiplock'),
1609 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1611 description
=> "Wait maximal timeout seconds.",
1617 description
=> "Do not decativate storage volumes.",
1630 my $rpcenv = PVE
::RPCEnvironment
::get
();
1632 my $authuser = $rpcenv->get_user();
1634 my $node = extract_param
($param, 'node');
1636 my $vmid = extract_param
($param, 'vmid');
1638 my $skiplock = extract_param
($param, 'skiplock');
1639 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1640 if $skiplock && $authuser ne 'root@pam';
1642 my $keepActive = extract_param
($param, 'keepActive');
1643 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1644 if $keepActive && $authuser ne 'root@pam';
1646 my $migratedfrom = extract_param
($param, 'migratedfrom');
1647 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1648 if $migratedfrom && $authuser ne 'root@pam';
1651 my $storecfg = PVE
::Storage
::config
();
1653 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1658 my $service = "pvevm:$vmid";
1660 my $cmd = ['clusvcadm', '-d', $service];
1662 print "Executing HA stop for VM $vmid\n";
1664 PVE
::Tools
::run_command
($cmd);
1669 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1675 syslog
('info', "stop VM $vmid: $upid\n");
1677 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1678 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1683 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1687 __PACKAGE__-
>register_method({
1689 path
=> '{vmid}/status/reset',
1693 description
=> "Reset virtual machine.",
1695 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1698 additionalProperties
=> 0,
1700 node
=> get_standard_option
('pve-node'),
1701 vmid
=> get_standard_option
('pve-vmid'),
1702 skiplock
=> get_standard_option
('skiplock'),
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $node = extract_param
($param, 'node');
1717 my $vmid = extract_param
($param, 'vmid');
1719 my $skiplock = extract_param
($param, 'skiplock');
1720 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1721 if $skiplock && $authuser ne 'root@pam';
1723 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1728 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1733 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1736 __PACKAGE__-
>register_method({
1737 name
=> 'vm_shutdown',
1738 path
=> '{vmid}/status/shutdown',
1742 description
=> "Shutdown virtual machine.",
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1751 skiplock
=> get_standard_option
('skiplock'),
1753 description
=> "Wait maximal timeout seconds.",
1759 description
=> "Make sure the VM stops.",
1765 description
=> "Do not decativate storage volumes.",
1778 my $rpcenv = PVE
::RPCEnvironment
::get
();
1780 my $authuser = $rpcenv->get_user();
1782 my $node = extract_param
($param, 'node');
1784 my $vmid = extract_param
($param, 'vmid');
1786 my $skiplock = extract_param
($param, 'skiplock');
1787 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1788 if $skiplock && $authuser ne 'root@pam';
1790 my $keepActive = extract_param
($param, 'keepActive');
1791 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1792 if $keepActive && $authuser ne 'root@pam';
1794 my $storecfg = PVE
::Storage
::config
();
1799 syslog
('info', "shutdown VM $vmid: $upid\n");
1801 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1802 1, $param->{forceStop
}, $keepActive);
1807 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1810 __PACKAGE__-
>register_method({
1811 name
=> 'vm_suspend',
1812 path
=> '{vmid}/status/suspend',
1816 description
=> "Suspend virtual machine.",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid'),
1825 skiplock
=> get_standard_option
('skiplock'),
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $node = extract_param
($param, 'node');
1840 my $vmid = extract_param
($param, 'vmid');
1842 my $skiplock = extract_param
($param, 'skiplock');
1843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1844 if $skiplock && $authuser ne 'root@pam';
1846 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1851 syslog
('info', "suspend VM $vmid: $upid\n");
1853 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1858 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1861 __PACKAGE__-
>register_method({
1862 name
=> 'vm_resume',
1863 path
=> '{vmid}/status/resume',
1867 description
=> "Resume virtual machine.",
1869 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1872 additionalProperties
=> 0,
1874 node
=> get_standard_option
('pve-node'),
1875 vmid
=> get_standard_option
('pve-vmid'),
1876 skiplock
=> get_standard_option
('skiplock'),
1885 my $rpcenv = PVE
::RPCEnvironment
::get
();
1887 my $authuser = $rpcenv->get_user();
1889 my $node = extract_param
($param, 'node');
1891 my $vmid = extract_param
($param, 'vmid');
1893 my $skiplock = extract_param
($param, 'skiplock');
1894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1895 if $skiplock && $authuser ne 'root@pam';
1897 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1902 syslog
('info', "resume VM $vmid: $upid\n");
1904 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1909 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1912 __PACKAGE__-
>register_method({
1913 name
=> 'vm_sendkey',
1914 path
=> '{vmid}/sendkey',
1918 description
=> "Send key event to virtual machine.",
1920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1923 additionalProperties
=> 0,
1925 node
=> get_standard_option
('pve-node'),
1926 vmid
=> get_standard_option
('pve-vmid'),
1927 skiplock
=> get_standard_option
('skiplock'),
1929 description
=> "The key (qemu monitor encoding).",
1934 returns
=> { type
=> 'null'},
1938 my $rpcenv = PVE
::RPCEnvironment
::get
();
1940 my $authuser = $rpcenv->get_user();
1942 my $node = extract_param
($param, 'node');
1944 my $vmid = extract_param
($param, 'vmid');
1946 my $skiplock = extract_param
($param, 'skiplock');
1947 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1948 if $skiplock && $authuser ne 'root@pam';
1950 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_feature',
1957 path
=> '{vmid}/feature',
1961 description
=> "Check if feature for virtual machine is available.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid'),
1971 description
=> "Feature to check.",
1973 enum
=> [ 'snapshot', 'clone', 'copy' ],
1975 snapname
=> get_standard_option
('pve-snapshot-name', {
1983 hasFeature
=> { type
=> 'boolean' },
1986 items
=> { type
=> 'string' },
1993 my $node = extract_param
($param, 'node');
1995 my $vmid = extract_param
($param, 'vmid');
1997 my $snapname = extract_param
($param, 'snapname');
1999 my $feature = extract_param
($param, 'feature');
2001 my $running = PVE
::QemuServer
::check_running
($vmid);
2003 my $conf = PVE
::QemuServer
::load_config
($vmid);
2006 my $snap = $conf->{snapshots
}->{$snapname};
2007 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2010 my $storecfg = PVE
::Storage
::config
();
2012 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2013 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2016 hasFeature
=> $hasFeature,
2017 nodes
=> [ keys %$nodelist ],
2021 __PACKAGE__-
>register_method({
2023 path
=> '{vmid}/clone',
2027 description
=> "Create a copy of virtual machine/template.",
2029 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2030 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2031 "'Datastore.AllocateSpace' on any used storage.",
2034 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2036 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2037 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2042 additionalProperties
=> 0,
2044 node
=> get_standard_option
('pve-node'),
2045 vmid
=> get_standard_option
('pve-vmid'),
2046 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2049 type
=> 'string', format
=> 'dns-name',
2050 description
=> "Set a name for the new VM.",
2055 description
=> "Description for the new VM.",
2059 type
=> 'string', format
=> 'pve-poolid',
2060 description
=> "Add the new VM to the specified pool.",
2062 snapname
=> get_standard_option
('pve-snapshot-name', {
2066 storage
=> get_standard_option
('pve-storage-id', {
2067 description
=> "Target storage for full clone.",
2072 description
=> "Target format for file storage.",
2076 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2081 description
=> "Create a full copy of all disk. This is always done when " .
2082 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2085 target
=> get_standard_option
('pve-node', {
2086 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2097 my $rpcenv = PVE
::RPCEnvironment
::get
();
2099 my $authuser = $rpcenv->get_user();
2101 my $node = extract_param
($param, 'node');
2103 my $vmid = extract_param
($param, 'vmid');
2105 my $newid = extract_param
($param, 'newid');
2107 my $pool = extract_param
($param, 'pool');
2109 if (defined($pool)) {
2110 $rpcenv->check_pool_exist($pool);
2113 my $snapname = extract_param
($param, 'snapname');
2115 my $storage = extract_param
($param, 'storage');
2117 my $format = extract_param
($param, 'format');
2119 my $target = extract_param
($param, 'target');
2121 my $localnode = PVE
::INotify
::nodename
();
2123 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2125 PVE
::Cluster
::check_node_exists
($target) if $target;
2127 my $storecfg = PVE
::Storage
::config
();
2130 # check if storage is enabled on local node
2131 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2133 # check if storage is available on target node
2134 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2135 # clone only works if target storage is shared
2136 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2137 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2141 PVE
::Cluster
::check_cfs_quorum
();
2143 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2145 # exclusive lock if VM is running - else shared lock is enough;
2146 my $shared_lock = $running ?
0 : 1;
2150 # do all tests after lock
2151 # we also try to do all tests before we fork the worker
2153 my $conf = PVE
::QemuServer
::load_config
($vmid);
2155 PVE
::QemuServer
::check_lock
($conf);
2157 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2159 die "unexpected state change\n" if $verify_running != $running;
2161 die "snapshot '$snapname' does not exist\n"
2162 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2164 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2166 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2168 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2170 my $conffile = PVE
::QemuServer
::config_file
($newid);
2172 die "unable to create VM $newid: config file already exists\n"
2175 my $newconf = { lock => 'clone' };
2179 foreach my $opt (keys %$oldconf) {
2180 my $value = $oldconf->{$opt};
2182 # do not copy snapshot related info
2183 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2184 $opt eq 'vmstate' || $opt eq 'snapstate';
2186 # always change MAC! address
2187 if ($opt =~ m/^net(\d+)$/) {
2188 my $net = PVE
::QemuServer
::parse_net
($value);
2189 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2190 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2191 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2192 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2193 $newconf->{$opt} = $value; # simply copy configuration
2195 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2196 die "Full clone feature is not available"
2197 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2200 $drives->{$opt} = $drive;
2201 push @$vollist, $drive->{file
};
2204 # copy everything else
2205 $newconf->{$opt} = $value;
2209 delete $newconf->{template
};
2211 if ($param->{name
}) {
2212 $newconf->{name
} = $param->{name
};
2214 if ($oldconf->{name
}) {
2215 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2217 $newconf->{name
} = "Copy-of-VM-$vmid";
2221 if ($param->{description
}) {
2222 $newconf->{description
} = $param->{description
};
2225 # create empty/temp config - this fails if VM already exists on other node
2226 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2231 my $newvollist = [];
2234 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2236 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2238 foreach my $opt (keys %$drives) {
2239 my $drive = $drives->{$opt};
2241 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2242 $newid, $storage, $format, $drive->{full
}, $newvollist);
2244 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2246 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2249 delete $newconf->{lock};
2250 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2253 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2254 die "Failed to move config to node '$target' - rename failed: $!\n"
2255 if !rename($conffile, $newconffile);
2258 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2263 sleep 1; # some storage like rbd need to wait before release volume - really?
2265 foreach my $volid (@$newvollist) {
2266 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2269 die "clone failed: $err";
2275 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2278 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2279 # Aquire exclusive lock lock for $newid
2280 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2285 __PACKAGE__-
>register_method({
2286 name
=> 'move_vm_disk',
2287 path
=> '{vmid}/move_disk',
2291 description
=> "Move volume to different storage.",
2293 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2294 "and 'Datastore.AllocateSpace' permissions on the storage.",
2297 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2298 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2302 additionalProperties
=> 0,
2304 node
=> get_standard_option
('pve-node'),
2305 vmid
=> get_standard_option
('pve-vmid'),
2308 description
=> "The disk you want to move.",
2309 enum
=> [ PVE
::QemuServer
::disknames
() ],
2311 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2314 description
=> "Target Format.",
2315 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2320 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2326 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2334 description
=> "the task ID.",
2339 my $rpcenv = PVE
::RPCEnvironment
::get
();
2341 my $authuser = $rpcenv->get_user();
2343 my $node = extract_param
($param, 'node');
2345 my $vmid = extract_param
($param, 'vmid');
2347 my $digest = extract_param
($param, 'digest');
2349 my $disk = extract_param
($param, 'disk');
2351 my $storeid = extract_param
($param, 'storage');
2353 my $format = extract_param
($param, 'format');
2355 my $storecfg = PVE
::Storage
::config
();
2357 my $updatefn = sub {
2359 my $conf = PVE
::QemuServer
::load_config
($vmid);
2361 die "checksum missmatch (file change by other user?)\n"
2362 if $digest && $digest ne $conf->{digest
};
2364 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2366 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2368 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2370 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2373 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2374 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2378 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2379 (!$format || !$oldfmt || $oldfmt eq $format);
2381 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2383 my $running = PVE
::QemuServer
::check_running
($vmid);
2385 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2389 my $newvollist = [];
2392 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2394 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2395 $vmid, $storeid, $format, 1, $newvollist);
2397 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2399 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2401 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2405 foreach my $volid (@$newvollist) {
2406 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2409 die "storage migration failed: $err";
2412 if ($param->{delete}) {
2413 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2418 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2421 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2424 __PACKAGE__-
>register_method({
2425 name
=> 'migrate_vm',
2426 path
=> '{vmid}/migrate',
2430 description
=> "Migrate virtual machine. Creates a new migration task.",
2432 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2435 additionalProperties
=> 0,
2437 node
=> get_standard_option
('pve-node'),
2438 vmid
=> get_standard_option
('pve-vmid'),
2439 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2442 description
=> "Use online/live migration.",
2447 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2454 description
=> "the task ID.",
2459 my $rpcenv = PVE
::RPCEnvironment
::get
();
2461 my $authuser = $rpcenv->get_user();
2463 my $target = extract_param
($param, 'target');
2465 my $localnode = PVE
::INotify
::nodename
();
2466 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2468 PVE
::Cluster
::check_cfs_quorum
();
2470 PVE
::Cluster
::check_node_exists
($target);
2472 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2474 my $vmid = extract_param
($param, 'vmid');
2476 raise_param_exc
({ force
=> "Only root may use this option." })
2477 if $param->{force
} && $authuser ne 'root@pam';
2480 my $conf = PVE
::QemuServer
::load_config
($vmid);
2482 # try to detect errors early
2484 PVE
::QemuServer
::check_lock
($conf);
2486 if (PVE
::QemuServer
::check_running
($vmid)) {
2487 die "cant migrate running VM without --online\n"
2488 if !$param->{online
};
2491 my $storecfg = PVE
::Storage
::config
();
2492 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2494 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2499 my $service = "pvevm:$vmid";
2501 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2503 print "Executing HA migrate for VM $vmid to node $target\n";
2505 PVE
::Tools
::run_command
($cmd);
2510 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2517 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2520 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2525 __PACKAGE__-
>register_method({
2527 path
=> '{vmid}/monitor',
2531 description
=> "Execute Qemu monitor commands.",
2533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2536 additionalProperties
=> 0,
2538 node
=> get_standard_option
('pve-node'),
2539 vmid
=> get_standard_option
('pve-vmid'),
2542 description
=> "The monitor command.",
2546 returns
=> { type
=> 'string'},
2550 my $vmid = $param->{vmid
};
2552 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2556 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2558 $res = "ERROR: $@" if $@;
2563 __PACKAGE__-
>register_method({
2564 name
=> 'resize_vm',
2565 path
=> '{vmid}/resize',
2569 description
=> "Extend volume size.",
2571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2574 additionalProperties
=> 0,
2576 node
=> get_standard_option
('pve-node'),
2577 vmid
=> get_standard_option
('pve-vmid'),
2578 skiplock
=> get_standard_option
('skiplock'),
2581 description
=> "The disk you want to resize.",
2582 enum
=> [PVE
::QemuServer
::disknames
()],
2586 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2587 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.",
2591 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2597 returns
=> { type
=> 'null'},
2601 my $rpcenv = PVE
::RPCEnvironment
::get
();
2603 my $authuser = $rpcenv->get_user();
2605 my $node = extract_param
($param, 'node');
2607 my $vmid = extract_param
($param, 'vmid');
2609 my $digest = extract_param
($param, 'digest');
2611 my $disk = extract_param
($param, 'disk');
2613 my $sizestr = extract_param
($param, 'size');
2615 my $skiplock = extract_param
($param, 'skiplock');
2616 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2617 if $skiplock && $authuser ne 'root@pam';
2619 my $storecfg = PVE
::Storage
::config
();
2621 my $updatefn = sub {
2623 my $conf = PVE
::QemuServer
::load_config
($vmid);
2625 die "checksum missmatch (file change by other user?)\n"
2626 if $digest && $digest ne $conf->{digest
};
2627 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2629 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2631 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2633 my $volid = $drive->{file
};
2635 die "disk '$disk' has no associated volume\n" if !$volid;
2637 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2639 die "you can't online resize a virtio windows bootdisk\n"
2640 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2642 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2644 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2646 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2648 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2649 my ($ext, $newsize, $unit) = ($1, $2, $4);
2652 $newsize = $newsize * 1024;
2653 } elsif ($unit eq 'M') {
2654 $newsize = $newsize * 1024 * 1024;
2655 } elsif ($unit eq 'G') {
2656 $newsize = $newsize * 1024 * 1024 * 1024;
2657 } elsif ($unit eq 'T') {
2658 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2661 $newsize += $size if $ext;
2662 $newsize = int($newsize);
2664 die "unable to skrink disk size\n" if $newsize < $size;
2666 return if $size == $newsize;
2668 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2670 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2672 $drive->{size
} = $newsize;
2673 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2675 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2678 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2682 __PACKAGE__-
>register_method({
2683 name
=> 'snapshot_list',
2684 path
=> '{vmid}/snapshot',
2686 description
=> "List all snapshots.",
2688 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2691 protected
=> 1, # qemu pid files are only readable by root
2693 additionalProperties
=> 0,
2695 vmid
=> get_standard_option
('pve-vmid'),
2696 node
=> get_standard_option
('pve-node'),
2705 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2710 my $vmid = $param->{vmid
};
2712 my $conf = PVE
::QemuServer
::load_config
($vmid);
2713 my $snaphash = $conf->{snapshots
} || {};
2717 foreach my $name (keys %$snaphash) {
2718 my $d = $snaphash->{$name};
2721 snaptime
=> $d->{snaptime
} || 0,
2722 vmstate
=> $d->{vmstate
} ?
1 : 0,
2723 description
=> $d->{description
} || '',
2725 $item->{parent
} = $d->{parent
} if $d->{parent
};
2726 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2730 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2731 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2732 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2734 push @$res, $current;
2739 __PACKAGE__-
>register_method({
2741 path
=> '{vmid}/snapshot',
2745 description
=> "Snapshot a VM.",
2747 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2750 additionalProperties
=> 0,
2752 node
=> get_standard_option
('pve-node'),
2753 vmid
=> get_standard_option
('pve-vmid'),
2754 snapname
=> get_standard_option
('pve-snapshot-name'),
2758 description
=> "Save the vmstate",
2763 description
=> "Freeze the filesystem",
2768 description
=> "A textual description or comment.",
2774 description
=> "the task ID.",
2779 my $rpcenv = PVE
::RPCEnvironment
::get
();
2781 my $authuser = $rpcenv->get_user();
2783 my $node = extract_param
($param, 'node');
2785 my $vmid = extract_param
($param, 'vmid');
2787 my $snapname = extract_param
($param, 'snapname');
2789 die "unable to use snapshot name 'current' (reserved name)\n"
2790 if $snapname eq 'current';
2793 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2794 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2795 $param->{freezefs
}, $param->{description
});
2798 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2801 __PACKAGE__-
>register_method({
2802 name
=> 'snapshot_cmd_idx',
2803 path
=> '{vmid}/snapshot/{snapname}',
2810 additionalProperties
=> 0,
2812 vmid
=> get_standard_option
('pve-vmid'),
2813 node
=> get_standard_option
('pve-node'),
2814 snapname
=> get_standard_option
('pve-snapshot-name'),
2823 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2830 push @$res, { cmd
=> 'rollback' };
2831 push @$res, { cmd
=> 'config' };
2836 __PACKAGE__-
>register_method({
2837 name
=> 'update_snapshot_config',
2838 path
=> '{vmid}/snapshot/{snapname}/config',
2842 description
=> "Update snapshot metadata.",
2844 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2847 additionalProperties
=> 0,
2849 node
=> get_standard_option
('pve-node'),
2850 vmid
=> get_standard_option
('pve-vmid'),
2851 snapname
=> get_standard_option
('pve-snapshot-name'),
2855 description
=> "A textual description or comment.",
2859 returns
=> { type
=> 'null' },
2863 my $rpcenv = PVE
::RPCEnvironment
::get
();
2865 my $authuser = $rpcenv->get_user();
2867 my $vmid = extract_param
($param, 'vmid');
2869 my $snapname = extract_param
($param, 'snapname');
2871 return undef if !defined($param->{description
});
2873 my $updatefn = sub {
2875 my $conf = PVE
::QemuServer
::load_config
($vmid);
2877 PVE
::QemuServer
::check_lock
($conf);
2879 my $snap = $conf->{snapshots
}->{$snapname};
2881 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2883 $snap->{description
} = $param->{description
} if defined($param->{description
});
2885 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2888 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2893 __PACKAGE__-
>register_method({
2894 name
=> 'get_snapshot_config',
2895 path
=> '{vmid}/snapshot/{snapname}/config',
2898 description
=> "Get snapshot configuration",
2900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2903 additionalProperties
=> 0,
2905 node
=> get_standard_option
('pve-node'),
2906 vmid
=> get_standard_option
('pve-vmid'),
2907 snapname
=> get_standard_option
('pve-snapshot-name'),
2910 returns
=> { type
=> "object" },
2914 my $rpcenv = PVE
::RPCEnvironment
::get
();
2916 my $authuser = $rpcenv->get_user();
2918 my $vmid = extract_param
($param, 'vmid');
2920 my $snapname = extract_param
($param, 'snapname');
2922 my $conf = PVE
::QemuServer
::load_config
($vmid);
2924 my $snap = $conf->{snapshots
}->{$snapname};
2926 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2931 __PACKAGE__-
>register_method({
2933 path
=> '{vmid}/snapshot/{snapname}/rollback',
2937 description
=> "Rollback VM state to specified snapshot.",
2939 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2942 additionalProperties
=> 0,
2944 node
=> get_standard_option
('pve-node'),
2945 vmid
=> get_standard_option
('pve-vmid'),
2946 snapname
=> get_standard_option
('pve-snapshot-name'),
2951 description
=> "the task ID.",
2956 my $rpcenv = PVE
::RPCEnvironment
::get
();
2958 my $authuser = $rpcenv->get_user();
2960 my $node = extract_param
($param, 'node');
2962 my $vmid = extract_param
($param, 'vmid');
2964 my $snapname = extract_param
($param, 'snapname');
2967 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2968 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2971 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2974 __PACKAGE__-
>register_method({
2975 name
=> 'delsnapshot',
2976 path
=> '{vmid}/snapshot/{snapname}',
2980 description
=> "Delete a VM snapshot.",
2982 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2985 additionalProperties
=> 0,
2987 node
=> get_standard_option
('pve-node'),
2988 vmid
=> get_standard_option
('pve-vmid'),
2989 snapname
=> get_standard_option
('pve-snapshot-name'),
2993 description
=> "For removal from config file, even if removing disk snapshots fails.",
2999 description
=> "the task ID.",
3004 my $rpcenv = PVE
::RPCEnvironment
::get
();
3006 my $authuser = $rpcenv->get_user();
3008 my $node = extract_param
($param, 'node');
3010 my $vmid = extract_param
($param, 'vmid');
3012 my $snapname = extract_param
($param, 'snapname');
3015 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3016 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3019 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3022 __PACKAGE__-
>register_method({
3024 path
=> '{vmid}/template',
3028 description
=> "Create a Template.",
3030 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3031 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3034 additionalProperties
=> 0,
3036 node
=> get_standard_option
('pve-node'),
3037 vmid
=> get_standard_option
('pve-vmid'),
3041 description
=> "If you want to convert only 1 disk to base image.",
3042 enum
=> [PVE
::QemuServer
::disknames
()],
3047 returns
=> { type
=> 'null'},
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $node = extract_param
($param, 'node');
3057 my $vmid = extract_param
($param, 'vmid');
3059 my $disk = extract_param
($param, 'disk');
3061 my $updatefn = sub {
3063 my $conf = PVE
::QemuServer
::load_config
($vmid);
3065 PVE
::QemuServer
::check_lock
($conf);
3067 die "unable to create template, because VM contains snapshots\n"
3068 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3070 die "you can't convert a template to a template\n"
3071 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3073 die "you can't convert a VM to template if VM is running\n"
3074 if PVE
::QemuServer
::check_running
($vmid);
3077 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3080 $conf->{template
} = 1;
3081 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3083 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3086 PVE
::QemuServer
::lock_config
($vmid, $updatefn);