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 (PVE
::QemuServer
::valid_drivename
($opt)) {
2192 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2193 die "unable to parse drive options for '$opt'\n" if !$drive;
2194 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2195 $newconf->{$opt} = $value; # simply copy configuration
2197 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2198 die "Full clone feature is not available"
2199 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2202 $drives->{$opt} = $drive;
2203 push @$vollist, $drive->{file
};
2206 # copy everything else
2207 $newconf->{$opt} = $value;
2211 delete $newconf->{template
};
2213 if ($param->{name
}) {
2214 $newconf->{name
} = $param->{name
};
2216 if ($oldconf->{name
}) {
2217 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2219 $newconf->{name
} = "Copy-of-VM-$vmid";
2223 if ($param->{description
}) {
2224 $newconf->{description
} = $param->{description
};
2227 # create empty/temp config - this fails if VM already exists on other node
2228 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2233 my $newvollist = [];
2236 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2238 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2240 foreach my $opt (keys %$drives) {
2241 my $drive = $drives->{$opt};
2243 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2244 $newid, $storage, $format, $drive->{full
}, $newvollist);
2246 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2248 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2251 delete $newconf->{lock};
2252 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2255 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2256 die "Failed to move config to node '$target' - rename failed: $!\n"
2257 if !rename($conffile, $newconffile);
2260 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2265 sleep 1; # some storage like rbd need to wait before release volume - really?
2267 foreach my $volid (@$newvollist) {
2268 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2271 die "clone failed: $err";
2277 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2280 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2281 # Aquire exclusive lock lock for $newid
2282 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2287 __PACKAGE__-
>register_method({
2288 name
=> 'move_vm_disk',
2289 path
=> '{vmid}/move_disk',
2293 description
=> "Move volume to different storage.",
2295 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2296 "and 'Datastore.AllocateSpace' permissions on the storage.",
2299 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2300 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2304 additionalProperties
=> 0,
2306 node
=> get_standard_option
('pve-node'),
2307 vmid
=> get_standard_option
('pve-vmid'),
2310 description
=> "The disk you want to move.",
2311 enum
=> [ PVE
::QemuServer
::disknames
() ],
2313 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2316 description
=> "Target Format.",
2317 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2322 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2328 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2336 description
=> "the task ID.",
2341 my $rpcenv = PVE
::RPCEnvironment
::get
();
2343 my $authuser = $rpcenv->get_user();
2345 my $node = extract_param
($param, 'node');
2347 my $vmid = extract_param
($param, 'vmid');
2349 my $digest = extract_param
($param, 'digest');
2351 my $disk = extract_param
($param, 'disk');
2353 my $storeid = extract_param
($param, 'storage');
2355 my $format = extract_param
($param, 'format');
2357 my $storecfg = PVE
::Storage
::config
();
2359 my $updatefn = sub {
2361 my $conf = PVE
::QemuServer
::load_config
($vmid);
2363 die "checksum missmatch (file change by other user?)\n"
2364 if $digest && $digest ne $conf->{digest
};
2366 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2368 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2370 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2372 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2375 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2376 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2380 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2381 (!$format || !$oldfmt || $oldfmt eq $format);
2383 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2385 my $running = PVE
::QemuServer
::check_running
($vmid);
2387 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2391 my $newvollist = [];
2394 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2396 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2397 $vmid, $storeid, $format, 1, $newvollist);
2399 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2401 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2403 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2407 foreach my $volid (@$newvollist) {
2408 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2411 die "storage migration failed: $err";
2414 if ($param->{delete}) {
2415 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2420 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2423 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2426 __PACKAGE__-
>register_method({
2427 name
=> 'migrate_vm',
2428 path
=> '{vmid}/migrate',
2432 description
=> "Migrate virtual machine. Creates a new migration task.",
2434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2437 additionalProperties
=> 0,
2439 node
=> get_standard_option
('pve-node'),
2440 vmid
=> get_standard_option
('pve-vmid'),
2441 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2444 description
=> "Use online/live migration.",
2449 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2456 description
=> "the task ID.",
2461 my $rpcenv = PVE
::RPCEnvironment
::get
();
2463 my $authuser = $rpcenv->get_user();
2465 my $target = extract_param
($param, 'target');
2467 my $localnode = PVE
::INotify
::nodename
();
2468 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2470 PVE
::Cluster
::check_cfs_quorum
();
2472 PVE
::Cluster
::check_node_exists
($target);
2474 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2476 my $vmid = extract_param
($param, 'vmid');
2478 raise_param_exc
({ force
=> "Only root may use this option." })
2479 if $param->{force
} && $authuser ne 'root@pam';
2482 my $conf = PVE
::QemuServer
::load_config
($vmid);
2484 # try to detect errors early
2486 PVE
::QemuServer
::check_lock
($conf);
2488 if (PVE
::QemuServer
::check_running
($vmid)) {
2489 die "cant migrate running VM without --online\n"
2490 if !$param->{online
};
2493 my $storecfg = PVE
::Storage
::config
();
2494 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2496 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2501 my $service = "pvevm:$vmid";
2503 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2505 print "Executing HA migrate for VM $vmid to node $target\n";
2507 PVE
::Tools
::run_command
($cmd);
2512 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2519 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2522 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2527 __PACKAGE__-
>register_method({
2529 path
=> '{vmid}/monitor',
2533 description
=> "Execute Qemu monitor commands.",
2535 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2538 additionalProperties
=> 0,
2540 node
=> get_standard_option
('pve-node'),
2541 vmid
=> get_standard_option
('pve-vmid'),
2544 description
=> "The monitor command.",
2548 returns
=> { type
=> 'string'},
2552 my $vmid = $param->{vmid
};
2554 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2558 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2560 $res = "ERROR: $@" if $@;
2565 __PACKAGE__-
>register_method({
2566 name
=> 'resize_vm',
2567 path
=> '{vmid}/resize',
2571 description
=> "Extend volume size.",
2573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2576 additionalProperties
=> 0,
2578 node
=> get_standard_option
('pve-node'),
2579 vmid
=> get_standard_option
('pve-vmid'),
2580 skiplock
=> get_standard_option
('skiplock'),
2583 description
=> "The disk you want to resize.",
2584 enum
=> [PVE
::QemuServer
::disknames
()],
2588 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2589 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.",
2593 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2599 returns
=> { type
=> 'null'},
2603 my $rpcenv = PVE
::RPCEnvironment
::get
();
2605 my $authuser = $rpcenv->get_user();
2607 my $node = extract_param
($param, 'node');
2609 my $vmid = extract_param
($param, 'vmid');
2611 my $digest = extract_param
($param, 'digest');
2613 my $disk = extract_param
($param, 'disk');
2615 my $sizestr = extract_param
($param, 'size');
2617 my $skiplock = extract_param
($param, 'skiplock');
2618 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2619 if $skiplock && $authuser ne 'root@pam';
2621 my $storecfg = PVE
::Storage
::config
();
2623 my $updatefn = sub {
2625 my $conf = PVE
::QemuServer
::load_config
($vmid);
2627 die "checksum missmatch (file change by other user?)\n"
2628 if $digest && $digest ne $conf->{digest
};
2629 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2631 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2633 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2635 my $volid = $drive->{file
};
2637 die "disk '$disk' has no associated volume\n" if !$volid;
2639 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2641 die "you can't online resize a virtio windows bootdisk\n"
2642 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2644 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2646 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2648 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2650 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2651 my ($ext, $newsize, $unit) = ($1, $2, $4);
2654 $newsize = $newsize * 1024;
2655 } elsif ($unit eq 'M') {
2656 $newsize = $newsize * 1024 * 1024;
2657 } elsif ($unit eq 'G') {
2658 $newsize = $newsize * 1024 * 1024 * 1024;
2659 } elsif ($unit eq 'T') {
2660 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2663 $newsize += $size if $ext;
2664 $newsize = int($newsize);
2666 die "unable to skrink disk size\n" if $newsize < $size;
2668 return if $size == $newsize;
2670 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2672 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2674 $drive->{size
} = $newsize;
2675 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2677 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2680 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2684 __PACKAGE__-
>register_method({
2685 name
=> 'snapshot_list',
2686 path
=> '{vmid}/snapshot',
2688 description
=> "List all snapshots.",
2690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2693 protected
=> 1, # qemu pid files are only readable by root
2695 additionalProperties
=> 0,
2697 vmid
=> get_standard_option
('pve-vmid'),
2698 node
=> get_standard_option
('pve-node'),
2707 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2712 my $vmid = $param->{vmid
};
2714 my $conf = PVE
::QemuServer
::load_config
($vmid);
2715 my $snaphash = $conf->{snapshots
} || {};
2719 foreach my $name (keys %$snaphash) {
2720 my $d = $snaphash->{$name};
2723 snaptime
=> $d->{snaptime
} || 0,
2724 vmstate
=> $d->{vmstate
} ?
1 : 0,
2725 description
=> $d->{description
} || '',
2727 $item->{parent
} = $d->{parent
} if $d->{parent
};
2728 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2732 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2733 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2734 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2736 push @$res, $current;
2741 __PACKAGE__-
>register_method({
2743 path
=> '{vmid}/snapshot',
2747 description
=> "Snapshot a VM.",
2749 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2752 additionalProperties
=> 0,
2754 node
=> get_standard_option
('pve-node'),
2755 vmid
=> get_standard_option
('pve-vmid'),
2756 snapname
=> get_standard_option
('pve-snapshot-name'),
2760 description
=> "Save the vmstate",
2765 description
=> "Freeze the filesystem",
2770 description
=> "A textual description or comment.",
2776 description
=> "the task ID.",
2781 my $rpcenv = PVE
::RPCEnvironment
::get
();
2783 my $authuser = $rpcenv->get_user();
2785 my $node = extract_param
($param, 'node');
2787 my $vmid = extract_param
($param, 'vmid');
2789 my $snapname = extract_param
($param, 'snapname');
2791 die "unable to use snapshot name 'current' (reserved name)\n"
2792 if $snapname eq 'current';
2795 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2796 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2797 $param->{freezefs
}, $param->{description
});
2800 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2803 __PACKAGE__-
>register_method({
2804 name
=> 'snapshot_cmd_idx',
2805 path
=> '{vmid}/snapshot/{snapname}',
2812 additionalProperties
=> 0,
2814 vmid
=> get_standard_option
('pve-vmid'),
2815 node
=> get_standard_option
('pve-node'),
2816 snapname
=> get_standard_option
('pve-snapshot-name'),
2825 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2832 push @$res, { cmd
=> 'rollback' };
2833 push @$res, { cmd
=> 'config' };
2838 __PACKAGE__-
>register_method({
2839 name
=> 'update_snapshot_config',
2840 path
=> '{vmid}/snapshot/{snapname}/config',
2844 description
=> "Update snapshot metadata.",
2846 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2849 additionalProperties
=> 0,
2851 node
=> get_standard_option
('pve-node'),
2852 vmid
=> get_standard_option
('pve-vmid'),
2853 snapname
=> get_standard_option
('pve-snapshot-name'),
2857 description
=> "A textual description or comment.",
2861 returns
=> { type
=> 'null' },
2865 my $rpcenv = PVE
::RPCEnvironment
::get
();
2867 my $authuser = $rpcenv->get_user();
2869 my $vmid = extract_param
($param, 'vmid');
2871 my $snapname = extract_param
($param, 'snapname');
2873 return undef if !defined($param->{description
});
2875 my $updatefn = sub {
2877 my $conf = PVE
::QemuServer
::load_config
($vmid);
2879 PVE
::QemuServer
::check_lock
($conf);
2881 my $snap = $conf->{snapshots
}->{$snapname};
2883 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2885 $snap->{description
} = $param->{description
} if defined($param->{description
});
2887 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2890 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2895 __PACKAGE__-
>register_method({
2896 name
=> 'get_snapshot_config',
2897 path
=> '{vmid}/snapshot/{snapname}/config',
2900 description
=> "Get snapshot configuration",
2902 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2905 additionalProperties
=> 0,
2907 node
=> get_standard_option
('pve-node'),
2908 vmid
=> get_standard_option
('pve-vmid'),
2909 snapname
=> get_standard_option
('pve-snapshot-name'),
2912 returns
=> { type
=> "object" },
2916 my $rpcenv = PVE
::RPCEnvironment
::get
();
2918 my $authuser = $rpcenv->get_user();
2920 my $vmid = extract_param
($param, 'vmid');
2922 my $snapname = extract_param
($param, 'snapname');
2924 my $conf = PVE
::QemuServer
::load_config
($vmid);
2926 my $snap = $conf->{snapshots
}->{$snapname};
2928 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2933 __PACKAGE__-
>register_method({
2935 path
=> '{vmid}/snapshot/{snapname}/rollback',
2939 description
=> "Rollback VM state to specified snapshot.",
2941 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2944 additionalProperties
=> 0,
2946 node
=> get_standard_option
('pve-node'),
2947 vmid
=> get_standard_option
('pve-vmid'),
2948 snapname
=> get_standard_option
('pve-snapshot-name'),
2953 description
=> "the task ID.",
2958 my $rpcenv = PVE
::RPCEnvironment
::get
();
2960 my $authuser = $rpcenv->get_user();
2962 my $node = extract_param
($param, 'node');
2964 my $vmid = extract_param
($param, 'vmid');
2966 my $snapname = extract_param
($param, 'snapname');
2969 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2970 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2973 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2976 __PACKAGE__-
>register_method({
2977 name
=> 'delsnapshot',
2978 path
=> '{vmid}/snapshot/{snapname}',
2982 description
=> "Delete a VM snapshot.",
2984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2987 additionalProperties
=> 0,
2989 node
=> get_standard_option
('pve-node'),
2990 vmid
=> get_standard_option
('pve-vmid'),
2991 snapname
=> get_standard_option
('pve-snapshot-name'),
2995 description
=> "For removal from config file, even if removing disk snapshots fails.",
3001 description
=> "the task ID.",
3006 my $rpcenv = PVE
::RPCEnvironment
::get
();
3008 my $authuser = $rpcenv->get_user();
3010 my $node = extract_param
($param, 'node');
3012 my $vmid = extract_param
($param, 'vmid');
3014 my $snapname = extract_param
($param, 'snapname');
3017 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3018 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3021 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3024 __PACKAGE__-
>register_method({
3026 path
=> '{vmid}/template',
3030 description
=> "Create a Template.",
3032 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3033 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3036 additionalProperties
=> 0,
3038 node
=> get_standard_option
('pve-node'),
3039 vmid
=> get_standard_option
('pve-vmid'),
3043 description
=> "If you want to convert only 1 disk to base image.",
3044 enum
=> [PVE
::QemuServer
::disknames
()],
3049 returns
=> { type
=> 'null'},
3053 my $rpcenv = PVE
::RPCEnvironment
::get
();
3055 my $authuser = $rpcenv->get_user();
3057 my $node = extract_param
($param, 'node');
3059 my $vmid = extract_param
($param, 'vmid');
3061 my $disk = extract_param
($param, 'disk');
3063 my $updatefn = sub {
3065 my $conf = PVE
::QemuServer
::load_config
($vmid);
3067 PVE
::QemuServer
::check_lock
($conf);
3069 die "unable to create template, because VM contains snapshots\n"
3070 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3072 die "you can't convert a template to a template\n"
3073 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3075 die "you can't convert a VM to template if VM is running\n"
3076 if PVE
::QemuServer
::check_running
($vmid);
3079 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3082 $conf->{template
} = 1;
3083 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3085 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3088 PVE
::QemuServer
::lock_config
($vmid, $updatefn);