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' },
497 { subdir
=> 'sendkey' },
503 __PACKAGE__-
>register_method({
505 path
=> '{vmid}/rrd',
507 protected
=> 1, # fixme: can we avoid that?
509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
511 description
=> "Read VM RRD statistics (returns PNG)",
513 additionalProperties
=> 0,
515 node
=> get_standard_option
('pve-node'),
516 vmid
=> get_standard_option
('pve-vmid'),
518 description
=> "Specify the time frame you are interested in.",
520 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
523 description
=> "The list of datasources you want to display.",
524 type
=> 'string', format
=> 'pve-configid-list',
527 description
=> "The RRD consolidation function",
529 enum
=> [ 'AVERAGE', 'MAX' ],
537 filename
=> { type
=> 'string' },
543 return PVE
::Cluster
::create_rrd_graph
(
544 "pve2-vm/$param->{vmid}", $param->{timeframe
},
545 $param->{ds
}, $param->{cf
});
549 __PACKAGE__-
>register_method({
551 path
=> '{vmid}/rrddata',
553 protected
=> 1, # fixme: can we avoid that?
555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
557 description
=> "Read VM RRD statistics",
559 additionalProperties
=> 0,
561 node
=> get_standard_option
('pve-node'),
562 vmid
=> get_standard_option
('pve-vmid'),
564 description
=> "Specify the time frame you are interested in.",
566 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
569 description
=> "The RRD consolidation function",
571 enum
=> [ 'AVERAGE', 'MAX' ],
586 return PVE
::Cluster
::create_rrd_data
(
587 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
591 __PACKAGE__-
>register_method({
593 path
=> '{vmid}/config',
596 description
=> "Get virtual machine configuration.",
598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
604 vmid
=> get_standard_option
('pve-vmid'),
612 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
619 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
621 delete $conf->{snapshots
};
626 my $vm_is_volid_owner = sub {
627 my ($storecfg, $vmid, $volid) =@_;
629 if ($volid !~ m
|^/|) {
631 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
632 if ($owner && ($owner == $vmid)) {
640 my $test_deallocate_drive = sub {
641 my ($storecfg, $vmid, $key, $drive, $force) = @_;
643 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
644 my $volid = $drive->{file
};
645 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
646 if ($force || $key =~ m/^unused/) {
647 my $sid = PVE
::Storage
::parse_volume_id
($volid);
656 my $delete_drive = sub {
657 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
659 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
660 my $volid = $drive->{file
};
662 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
663 if ($force || $key =~ m/^unused/) {
665 # check if the disk is really unused
666 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
667 my $path = PVE
::Storage
::path
($storecfg, $volid);
669 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
670 if $used_paths->{$path};
672 PVE
::Storage
::vdisk_free
($storecfg, $volid);
676 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
681 delete $conf->{$key};
684 my $vmconfig_delete_option = sub {
685 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
687 return if !defined($conf->{$opt});
689 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
692 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
694 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
695 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
696 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
700 my $unplugwarning = "";
701 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
702 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
703 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
704 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
705 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
706 $unplugwarning = "<br>verify that your guest support acpi hotplug";
709 if ($opt eq 'tablet') {
710 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
712 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
716 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
717 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
719 delete $conf->{$opt};
722 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
725 my $safe_num_ne = sub {
728 return 0 if !defined($a) && !defined($b);
729 return 1 if !defined($a);
730 return 1 if !defined($b);
735 my $vmconfig_update_disk = sub {
736 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
738 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
740 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
741 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
743 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
748 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
750 my $media = $drive->{media
} || 'disk';
751 my $oldmedia = $old_drive->{media
} || 'disk';
752 die "unable to change media type\n" if $media ne $oldmedia;
754 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
755 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
757 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
758 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
761 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
762 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
763 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
764 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
765 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
766 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
767 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
768 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
769 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
770 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
771 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
772 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
773 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
774 ($drive->{mbps
} || 0)*1024*1024,
775 ($drive->{mbps_rd
} || 0)*1024*1024,
776 ($drive->{mbps_wr
} || 0)*1024*1024,
778 $drive->{iops_rd
} || 0,
779 $drive->{iops_wr
} || 0,
780 ($drive->{mbps_max
} || 0)*1024*1024,
781 ($drive->{mbps_rd_max
} || 0)*1024*1024,
782 ($drive->{mbps_wr_max
} || 0)*1024*1024,
783 $drive->{iops_max
} || 0,
784 $drive->{iops_rd_max
} || 0,
785 $drive->{iops_wr_max
} || 0)
786 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
791 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
792 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
794 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
795 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
797 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
799 if (PVE
::QemuServer
::check_running
($vmid)) {
800 if ($drive->{file
} eq 'none') {
801 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
803 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
804 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
805 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
809 } else { # hotplug new disks
811 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
815 my $vmconfig_update_net = sub {
816 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
818 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
819 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
820 my $newnet = PVE
::QemuServer
::parse_net
($value);
822 if($oldnet->{model
} ne $newnet->{model
}){
823 #if model change, we try to hot-unplug
824 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
827 if($newnet->{bridge
} && $oldnet->{bridge
}){
828 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
830 if($newnet->{rate
} ne $oldnet->{rate
}){
831 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
834 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
835 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
836 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
840 #if bridge/nat mode change, we try to hot-unplug
841 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
846 $conf->{$opt} = $value;
847 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
848 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
850 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
852 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
855 # POST/PUT {vmid}/config implementation
857 # The original API used PUT (idempotent) an we assumed that all operations
858 # are fast. But it turned out that almost any configuration change can
859 # involve hot-plug actions, or disk alloc/free. Such actions can take long
860 # time to complete and have side effects (not idempotent).
862 # The new implementation uses POST and forks a worker process. We added
863 # a new option 'background_delay'. If specified we wait up to
864 # 'background_delay' second for the worker task to complete. It returns null
865 # if the task is finished within that time, else we return the UPID.
867 my $update_vm_api = sub {
868 my ($param, $sync) = @_;
870 my $rpcenv = PVE
::RPCEnvironment
::get
();
872 my $authuser = $rpcenv->get_user();
874 my $node = extract_param
($param, 'node');
876 my $vmid = extract_param
($param, 'vmid');
878 my $digest = extract_param
($param, 'digest');
880 my $background_delay = extract_param
($param, 'background_delay');
882 my @paramarr = (); # used for log message
883 foreach my $key (keys %$param) {
884 push @paramarr, "-$key", $param->{$key};
887 my $skiplock = extract_param
($param, 'skiplock');
888 raise_param_exc
({ skiplock
=> "Only root may use this option." })
889 if $skiplock && $authuser ne 'root@pam';
891 my $delete_str = extract_param
($param, 'delete');
893 my $force = extract_param
($param, 'force');
895 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
897 my $storecfg = PVE
::Storage
::config
();
899 my $defaults = PVE
::QemuServer
::load_defaults
();
901 &$resolve_cdrom_alias($param);
903 # now try to verify all parameters
906 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
907 $opt = 'ide2' if $opt eq 'cdrom';
908 raise_param_exc
({ delete => "you can't use '-$opt' and " .
909 "-delete $opt' at the same time" })
910 if defined($param->{$opt});
912 if (!PVE
::QemuServer
::option_exists
($opt)) {
913 raise_param_exc
({ delete => "unknown option '$opt'" });
919 foreach my $opt (keys %$param) {
920 if (PVE
::QemuServer
::valid_drivename
($opt)) {
922 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
923 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
924 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
925 } elsif ($opt =~ m/^net(\d+)$/) {
927 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
928 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
932 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
934 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
936 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
940 my $conf = PVE
::QemuServer
::load_config
($vmid);
942 die "checksum missmatch (file change by other user?)\n"
943 if $digest && $digest ne $conf->{digest
};
945 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
947 if ($param->{memory
} || defined($param->{balloon
})) {
948 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
949 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
951 die "balloon value too large (must be smaller than assigned memory)\n"
952 if $balloon && $balloon > $maxmem;
955 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
959 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
961 foreach my $opt (@delete) { # delete
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
966 my $running = PVE
::QemuServer
::check_running
($vmid);
968 foreach my $opt (keys %$param) { # add/change
970 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
972 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
974 if (PVE
::QemuServer
::valid_drivename
($opt)) {
976 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
977 $opt, $param->{$opt}, $force);
979 } elsif ($opt =~ m/^net(\d+)$/) { #nics
981 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
982 $opt, $param->{$opt});
986 if($opt eq 'tablet' && $param->{$opt} == 1){
987 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
988 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
989 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
992 if($opt eq 'cores' && $conf->{maxcpus
}){
993 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
996 $conf->{$opt} = $param->{$opt};
997 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1001 # allow manual ballooning if shares is set to zero
1002 if ($running && defined($param->{balloon
}) &&
1003 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1004 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1005 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1013 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1015 if ($background_delay) {
1017 # Note: It would be better to do that in the Event based HTTPServer
1018 # to avoid blocking call to sleep.
1020 my $end_time = time() + $background_delay;
1022 my $task = PVE
::Tools
::upid_decode
($upid);
1025 while (time() < $end_time) {
1026 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1028 sleep(1); # this gets interrupted when child process ends
1032 my $status = PVE
::Tools
::upid_read_status
($upid);
1033 return undef if $status eq 'OK';
1042 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1045 my $vm_config_perm_list = [
1050 'VM.Config.Network',
1052 'VM.Config.Options',
1055 __PACKAGE__-
>register_method({
1056 name
=> 'update_vm_async',
1057 path
=> '{vmid}/config',
1061 description
=> "Set virtual machine options (asynchrounous API).",
1063 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1066 additionalProperties
=> 0,
1067 properties
=> PVE
::QemuServer
::json_config_properties
(
1069 node
=> get_standard_option
('pve-node'),
1070 vmid
=> get_standard_option
('pve-vmid'),
1071 skiplock
=> get_standard_option
('skiplock'),
1073 type
=> 'string', format
=> 'pve-configid-list',
1074 description
=> "A list of settings you want to delete.",
1079 description
=> $opt_force_description,
1081 requires
=> 'delete',
1085 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1089 background_delay
=> {
1091 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1102 code
=> $update_vm_api,
1105 __PACKAGE__-
>register_method({
1106 name
=> 'update_vm',
1107 path
=> '{vmid}/config',
1111 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1113 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1116 additionalProperties
=> 0,
1117 properties
=> PVE
::QemuServer
::json_config_properties
(
1119 node
=> get_standard_option
('pve-node'),
1120 vmid
=> get_standard_option
('pve-vmid'),
1121 skiplock
=> get_standard_option
('skiplock'),
1123 type
=> 'string', format
=> 'pve-configid-list',
1124 description
=> "A list of settings you want to delete.",
1129 description
=> $opt_force_description,
1131 requires
=> 'delete',
1135 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1141 returns
=> { type
=> 'null' },
1144 &$update_vm_api($param, 1);
1150 __PACKAGE__-
>register_method({
1151 name
=> 'destroy_vm',
1156 description
=> "Destroy the vm (also delete all used/owned volumes).",
1158 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1161 additionalProperties
=> 0,
1163 node
=> get_standard_option
('pve-node'),
1164 vmid
=> get_standard_option
('pve-vmid'),
1165 skiplock
=> get_standard_option
('skiplock'),
1174 my $rpcenv = PVE
::RPCEnvironment
::get
();
1176 my $authuser = $rpcenv->get_user();
1178 my $vmid = $param->{vmid
};
1180 my $skiplock = $param->{skiplock
};
1181 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1182 if $skiplock && $authuser ne 'root@pam';
1185 my $conf = PVE
::QemuServer
::load_config
($vmid);
1187 my $storecfg = PVE
::Storage
::config
();
1189 my $delVMfromPoolFn = sub {
1190 my $usercfg = cfs_read_file
("user.cfg");
1191 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1192 if (my $data = $usercfg->{pools
}->{$pool}) {
1193 delete $data->{vms
}->{$vmid};
1194 delete $usercfg->{vms
}->{$vmid};
1195 cfs_write_file
("user.cfg", $usercfg);
1203 syslog
('info', "destroy VM $vmid: $upid\n");
1205 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1207 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1210 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1213 __PACKAGE__-
>register_method({
1215 path
=> '{vmid}/unlink',
1219 description
=> "Unlink/delete disk images.",
1221 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1224 additionalProperties
=> 0,
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid'),
1229 type
=> 'string', format
=> 'pve-configid-list',
1230 description
=> "A list of disk IDs you want to delete.",
1234 description
=> $opt_force_description,
1239 returns
=> { type
=> 'null'},
1243 $param->{delete} = extract_param
($param, 'idlist');
1245 __PACKAGE__-
>update_vm($param);
1252 __PACKAGE__-
>register_method({
1254 path
=> '{vmid}/vncproxy',
1258 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1260 description
=> "Creates a TCP VNC proxy connections.",
1262 additionalProperties
=> 0,
1264 node
=> get_standard_option
('pve-node'),
1265 vmid
=> get_standard_option
('pve-vmid'),
1269 additionalProperties
=> 0,
1271 user
=> { type
=> 'string' },
1272 ticket
=> { type
=> 'string' },
1273 cert
=> { type
=> 'string' },
1274 port
=> { type
=> 'integer' },
1275 upid
=> { type
=> 'string' },
1281 my $rpcenv = PVE
::RPCEnvironment
::get
();
1283 my $authuser = $rpcenv->get_user();
1285 my $vmid = $param->{vmid
};
1286 my $node = $param->{node
};
1288 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1290 my $authpath = "/vms/$vmid";
1292 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1294 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1297 my $port = PVE
::Tools
::next_vnc_port
();
1302 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1303 $remip = PVE
::Cluster
::remote_node_ip
($node);
1304 # NOTE: kvm VNC traffic is already TLS encrypted
1305 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1313 syslog
('info', "starting vnc proxy $upid\n");
1317 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1319 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1320 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1321 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1322 '-timeout', $timeout, '-authpath', $authpath,
1323 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1326 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1328 my $qmstr = join(' ', @$qmcmd);
1330 # also redirect stderr (else we get RFB protocol errors)
1331 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1334 PVE
::Tools
::run_command
($cmd);
1339 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1341 PVE
::Tools
::wait_for_vnc_port
($port);
1352 __PACKAGE__-
>register_method({
1353 name
=> 'spiceproxy',
1354 path
=> '{vmid}/spiceproxy',
1359 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1361 description
=> "Returns a SPICE configuration to connect to the VM.",
1363 additionalProperties
=> 0,
1365 node
=> get_standard_option
('pve-node'),
1366 vmid
=> get_standard_option
('pve-vmid'),
1367 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1370 returns
=> get_standard_option
('remote-viewer-config'),
1374 my $rpcenv = PVE
::RPCEnvironment
::get
();
1376 my $authuser = $rpcenv->get_user();
1378 my $vmid = $param->{vmid
};
1379 my $node = $param->{node
};
1380 my $proxy = $param->{proxy
};
1382 my $title = "VM $vmid";
1384 my $port = PVE
::QemuServer
::spice_port
($vmid);
1386 my ($ticket, undef, $remote_viewer_config) =
1387 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1389 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1390 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1392 return $remote_viewer_config;
1395 __PACKAGE__-
>register_method({
1397 path
=> '{vmid}/status',
1400 description
=> "Directory index",
1405 additionalProperties
=> 0,
1407 node
=> get_standard_option
('pve-node'),
1408 vmid
=> get_standard_option
('pve-vmid'),
1416 subdir
=> { type
=> 'string' },
1419 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1425 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1428 { subdir
=> 'current' },
1429 { subdir
=> 'start' },
1430 { subdir
=> 'stop' },
1436 my $vm_is_ha_managed = sub {
1439 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1440 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1446 __PACKAGE__-
>register_method({
1447 name
=> 'vm_status',
1448 path
=> '{vmid}/status/current',
1451 protected
=> 1, # qemu pid files are only readable by root
1452 description
=> "Get virtual machine status.",
1454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1457 additionalProperties
=> 0,
1459 node
=> get_standard_option
('pve-node'),
1460 vmid
=> get_standard_option
('pve-vmid'),
1463 returns
=> { type
=> 'object' },
1468 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1470 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1471 my $status = $vmstatus->{$param->{vmid
}};
1473 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1475 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1480 __PACKAGE__-
>register_method({
1482 path
=> '{vmid}/status/start',
1486 description
=> "Start virtual machine.",
1488 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 vmid
=> get_standard_option
('pve-vmid'),
1495 skiplock
=> get_standard_option
('skiplock'),
1496 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1497 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1498 machine
=> get_standard_option
('pve-qm-machine'),
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $node = extract_param
($param, 'node');
1513 my $vmid = extract_param
($param, 'vmid');
1515 my $machine = extract_param
($param, 'machine');
1517 my $stateuri = extract_param
($param, 'stateuri');
1518 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1519 if $stateuri && $authuser ne 'root@pam';
1521 my $skiplock = extract_param
($param, 'skiplock');
1522 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1523 if $skiplock && $authuser ne 'root@pam';
1525 my $migratedfrom = extract_param
($param, 'migratedfrom');
1526 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1527 if $migratedfrom && $authuser ne 'root@pam';
1529 # read spice ticket from STDIN
1531 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1532 if (defined(my $line = <>)) {
1534 $spice_ticket = $line;
1538 my $storecfg = PVE
::Storage
::config
();
1540 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1541 $rpcenv->{type
} ne 'ha') {
1546 my $service = "pvevm:$vmid";
1548 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1550 print "Executing HA start for VM $vmid\n";
1552 PVE
::Tools
::run_command
($cmd);
1557 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1564 syslog
('info', "start VM $vmid: $upid\n");
1566 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1567 $machine, $spice_ticket);
1572 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1576 __PACKAGE__-
>register_method({
1578 path
=> '{vmid}/status/stop',
1582 description
=> "Stop virtual machine.",
1584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1591 skiplock
=> get_standard_option
('skiplock'),
1592 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1594 description
=> "Wait maximal timeout seconds.",
1600 description
=> "Do not decativate storage volumes.",
1613 my $rpcenv = PVE
::RPCEnvironment
::get
();
1615 my $authuser = $rpcenv->get_user();
1617 my $node = extract_param
($param, 'node');
1619 my $vmid = extract_param
($param, 'vmid');
1621 my $skiplock = extract_param
($param, 'skiplock');
1622 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1623 if $skiplock && $authuser ne 'root@pam';
1625 my $keepActive = extract_param
($param, 'keepActive');
1626 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1627 if $keepActive && $authuser ne 'root@pam';
1629 my $migratedfrom = extract_param
($param, 'migratedfrom');
1630 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1631 if $migratedfrom && $authuser ne 'root@pam';
1634 my $storecfg = PVE
::Storage
::config
();
1636 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1641 my $service = "pvevm:$vmid";
1643 my $cmd = ['clusvcadm', '-d', $service];
1645 print "Executing HA stop for VM $vmid\n";
1647 PVE
::Tools
::run_command
($cmd);
1652 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1658 syslog
('info', "stop VM $vmid: $upid\n");
1660 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1661 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1666 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1670 __PACKAGE__-
>register_method({
1672 path
=> '{vmid}/status/reset',
1676 description
=> "Reset virtual machine.",
1678 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1681 additionalProperties
=> 0,
1683 node
=> get_standard_option
('pve-node'),
1684 vmid
=> get_standard_option
('pve-vmid'),
1685 skiplock
=> get_standard_option
('skiplock'),
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $node = extract_param
($param, 'node');
1700 my $vmid = extract_param
($param, 'vmid');
1702 my $skiplock = extract_param
($param, 'skiplock');
1703 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1704 if $skiplock && $authuser ne 'root@pam';
1706 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1711 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1716 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1719 __PACKAGE__-
>register_method({
1720 name
=> 'vm_shutdown',
1721 path
=> '{vmid}/status/shutdown',
1725 description
=> "Shutdown virtual machine.",
1727 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1730 additionalProperties
=> 0,
1732 node
=> get_standard_option
('pve-node'),
1733 vmid
=> get_standard_option
('pve-vmid'),
1734 skiplock
=> get_standard_option
('skiplock'),
1736 description
=> "Wait maximal timeout seconds.",
1742 description
=> "Make sure the VM stops.",
1748 description
=> "Do not decativate storage volumes.",
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $node = extract_param
($param, 'node');
1767 my $vmid = extract_param
($param, 'vmid');
1769 my $skiplock = extract_param
($param, 'skiplock');
1770 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1771 if $skiplock && $authuser ne 'root@pam';
1773 my $keepActive = extract_param
($param, 'keepActive');
1774 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1775 if $keepActive && $authuser ne 'root@pam';
1777 my $storecfg = PVE
::Storage
::config
();
1782 syslog
('info', "shutdown VM $vmid: $upid\n");
1784 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1785 1, $param->{forceStop
}, $keepActive);
1790 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1793 __PACKAGE__-
>register_method({
1794 name
=> 'vm_suspend',
1795 path
=> '{vmid}/status/suspend',
1799 description
=> "Suspend virtual machine.",
1801 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1804 additionalProperties
=> 0,
1806 node
=> get_standard_option
('pve-node'),
1807 vmid
=> get_standard_option
('pve-vmid'),
1808 skiplock
=> get_standard_option
('skiplock'),
1817 my $rpcenv = PVE
::RPCEnvironment
::get
();
1819 my $authuser = $rpcenv->get_user();
1821 my $node = extract_param
($param, 'node');
1823 my $vmid = extract_param
($param, 'vmid');
1825 my $skiplock = extract_param
($param, 'skiplock');
1826 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1827 if $skiplock && $authuser ne 'root@pam';
1829 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1834 syslog
('info', "suspend VM $vmid: $upid\n");
1836 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1841 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1844 __PACKAGE__-
>register_method({
1845 name
=> 'vm_resume',
1846 path
=> '{vmid}/status/resume',
1850 description
=> "Resume virtual machine.",
1852 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1855 additionalProperties
=> 0,
1857 node
=> get_standard_option
('pve-node'),
1858 vmid
=> get_standard_option
('pve-vmid'),
1859 skiplock
=> get_standard_option
('skiplock'),
1868 my $rpcenv = PVE
::RPCEnvironment
::get
();
1870 my $authuser = $rpcenv->get_user();
1872 my $node = extract_param
($param, 'node');
1874 my $vmid = extract_param
($param, 'vmid');
1876 my $skiplock = extract_param
($param, 'skiplock');
1877 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1878 if $skiplock && $authuser ne 'root@pam';
1880 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1885 syslog
('info', "resume VM $vmid: $upid\n");
1887 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1892 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1895 __PACKAGE__-
>register_method({
1896 name
=> 'vm_sendkey',
1897 path
=> '{vmid}/sendkey',
1901 description
=> "Send key event to virtual machine.",
1903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1906 additionalProperties
=> 0,
1908 node
=> get_standard_option
('pve-node'),
1909 vmid
=> get_standard_option
('pve-vmid'),
1910 skiplock
=> get_standard_option
('skiplock'),
1912 description
=> "The key (qemu monitor encoding).",
1917 returns
=> { type
=> 'null'},
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1938 __PACKAGE__-
>register_method({
1939 name
=> 'vm_feature',
1940 path
=> '{vmid}/feature',
1944 description
=> "Check if feature for virtual machine is available.",
1946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1949 additionalProperties
=> 0,
1951 node
=> get_standard_option
('pve-node'),
1952 vmid
=> get_standard_option
('pve-vmid'),
1954 description
=> "Feature to check.",
1956 enum
=> [ 'snapshot', 'clone', 'copy' ],
1958 snapname
=> get_standard_option
('pve-snapshot-name', {
1966 hasFeature
=> { type
=> 'boolean' },
1969 items
=> { type
=> 'string' },
1976 my $node = extract_param
($param, 'node');
1978 my $vmid = extract_param
($param, 'vmid');
1980 my $snapname = extract_param
($param, 'snapname');
1982 my $feature = extract_param
($param, 'feature');
1984 my $running = PVE
::QemuServer
::check_running
($vmid);
1986 my $conf = PVE
::QemuServer
::load_config
($vmid);
1989 my $snap = $conf->{snapshots
}->{$snapname};
1990 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1993 my $storecfg = PVE
::Storage
::config
();
1995 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1996 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1999 hasFeature
=> $hasFeature,
2000 nodes
=> [ keys %$nodelist ],
2004 __PACKAGE__-
>register_method({
2006 path
=> '{vmid}/clone',
2010 description
=> "Create a copy of virtual machine/template.",
2012 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2013 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2014 "'Datastore.AllocateSpace' on any used storage.",
2017 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2019 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2020 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2025 additionalProperties
=> 0,
2027 node
=> get_standard_option
('pve-node'),
2028 vmid
=> get_standard_option
('pve-vmid'),
2029 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2032 type
=> 'string', format
=> 'dns-name',
2033 description
=> "Set a name for the new VM.",
2038 description
=> "Description for the new VM.",
2042 type
=> 'string', format
=> 'pve-poolid',
2043 description
=> "Add the new VM to the specified pool.",
2045 snapname
=> get_standard_option
('pve-snapshot-name', {
2049 storage
=> get_standard_option
('pve-storage-id', {
2050 description
=> "Target storage for full clone.",
2055 description
=> "Target format for file storage.",
2059 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2064 description
=> "Create a full copy of all disk. This is always done when " .
2065 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2068 target
=> get_standard_option
('pve-node', {
2069 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2080 my $rpcenv = PVE
::RPCEnvironment
::get
();
2082 my $authuser = $rpcenv->get_user();
2084 my $node = extract_param
($param, 'node');
2086 my $vmid = extract_param
($param, 'vmid');
2088 my $newid = extract_param
($param, 'newid');
2090 my $pool = extract_param
($param, 'pool');
2092 if (defined($pool)) {
2093 $rpcenv->check_pool_exist($pool);
2096 my $snapname = extract_param
($param, 'snapname');
2098 my $storage = extract_param
($param, 'storage');
2100 my $format = extract_param
($param, 'format');
2102 my $target = extract_param
($param, 'target');
2104 my $localnode = PVE
::INotify
::nodename
();
2106 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2108 PVE
::Cluster
::check_node_exists
($target) if $target;
2110 my $storecfg = PVE
::Storage
::config
();
2113 # check if storage is enabled on local node
2114 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2116 # check if storage is available on target node
2117 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2118 # clone only works if target storage is shared
2119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2120 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2124 PVE
::Cluster
::check_cfs_quorum
();
2126 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2128 # exclusive lock if VM is running - else shared lock is enough;
2129 my $shared_lock = $running ?
0 : 1;
2133 # do all tests after lock
2134 # we also try to do all tests before we fork the worker
2136 my $conf = PVE
::QemuServer
::load_config
($vmid);
2138 PVE
::QemuServer
::check_lock
($conf);
2140 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2142 die "unexpected state change\n" if $verify_running != $running;
2144 die "snapshot '$snapname' does not exist\n"
2145 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2147 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2149 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2151 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2153 my $conffile = PVE
::QemuServer
::config_file
($newid);
2155 die "unable to create VM $newid: config file already exists\n"
2158 my $newconf = { lock => 'clone' };
2162 foreach my $opt (keys %$oldconf) {
2163 my $value = $oldconf->{$opt};
2165 # do not copy snapshot related info
2166 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2167 $opt eq 'vmstate' || $opt eq 'snapstate';
2169 # always change MAC! address
2170 if ($opt =~ m/^net(\d+)$/) {
2171 my $net = PVE
::QemuServer
::parse_net
($value);
2172 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2173 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2174 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2175 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2176 die "unable to parse drive options for '$opt'\n" if !$drive;
2177 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2178 $newconf->{$opt} = $value; # simply copy configuration
2180 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2181 die "Full clone feature is not available"
2182 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2185 $drives->{$opt} = $drive;
2186 push @$vollist, $drive->{file
};
2189 # copy everything else
2190 $newconf->{$opt} = $value;
2194 delete $newconf->{template
};
2196 if ($param->{name
}) {
2197 $newconf->{name
} = $param->{name
};
2199 if ($oldconf->{name
}) {
2200 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2202 $newconf->{name
} = "Copy-of-VM-$vmid";
2206 if ($param->{description
}) {
2207 $newconf->{description
} = $param->{description
};
2210 # create empty/temp config - this fails if VM already exists on other node
2211 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2216 my $newvollist = [];
2219 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2221 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2223 foreach my $opt (keys %$drives) {
2224 my $drive = $drives->{$opt};
2226 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2227 $newid, $storage, $format, $drive->{full
}, $newvollist);
2229 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2231 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2234 delete $newconf->{lock};
2235 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2238 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2239 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2241 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2242 die "Failed to move config to node '$target' - rename failed: $!\n"
2243 if !rename($conffile, $newconffile);
2246 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2251 sleep 1; # some storage like rbd need to wait before release volume - really?
2253 foreach my $volid (@$newvollist) {
2254 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2257 die "clone failed: $err";
2263 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2266 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2267 # Aquire exclusive lock lock for $newid
2268 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2273 __PACKAGE__-
>register_method({
2274 name
=> 'move_vm_disk',
2275 path
=> '{vmid}/move_disk',
2279 description
=> "Move volume to different storage.",
2281 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2282 "and 'Datastore.AllocateSpace' permissions on the storage.",
2285 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2286 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2290 additionalProperties
=> 0,
2292 node
=> get_standard_option
('pve-node'),
2293 vmid
=> get_standard_option
('pve-vmid'),
2296 description
=> "The disk you want to move.",
2297 enum
=> [ PVE
::QemuServer
::disknames
() ],
2299 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2302 description
=> "Target Format.",
2303 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2308 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2314 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2322 description
=> "the task ID.",
2327 my $rpcenv = PVE
::RPCEnvironment
::get
();
2329 my $authuser = $rpcenv->get_user();
2331 my $node = extract_param
($param, 'node');
2333 my $vmid = extract_param
($param, 'vmid');
2335 my $digest = extract_param
($param, 'digest');
2337 my $disk = extract_param
($param, 'disk');
2339 my $storeid = extract_param
($param, 'storage');
2341 my $format = extract_param
($param, 'format');
2343 my $storecfg = PVE
::Storage
::config
();
2345 my $updatefn = sub {
2347 my $conf = PVE
::QemuServer
::load_config
($vmid);
2349 die "checksum missmatch (file change by other user?)\n"
2350 if $digest && $digest ne $conf->{digest
};
2352 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2354 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2356 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2358 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2361 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2362 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2366 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2367 (!$format || !$oldfmt || $oldfmt eq $format);
2369 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2371 my $running = PVE
::QemuServer
::check_running
($vmid);
2373 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2377 my $newvollist = [];
2380 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2382 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2383 $vmid, $storeid, $format, 1, $newvollist);
2385 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2387 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2389 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2392 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2393 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2400 foreach my $volid (@$newvollist) {
2401 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2404 die "storage migration failed: $err";
2407 if ($param->{delete}) {
2408 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2413 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2416 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2419 __PACKAGE__-
>register_method({
2420 name
=> 'migrate_vm',
2421 path
=> '{vmid}/migrate',
2425 description
=> "Migrate virtual machine. Creates a new migration task.",
2427 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2430 additionalProperties
=> 0,
2432 node
=> get_standard_option
('pve-node'),
2433 vmid
=> get_standard_option
('pve-vmid'),
2434 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2437 description
=> "Use online/live migration.",
2442 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2449 description
=> "the task ID.",
2454 my $rpcenv = PVE
::RPCEnvironment
::get
();
2456 my $authuser = $rpcenv->get_user();
2458 my $target = extract_param
($param, 'target');
2460 my $localnode = PVE
::INotify
::nodename
();
2461 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2463 PVE
::Cluster
::check_cfs_quorum
();
2465 PVE
::Cluster
::check_node_exists
($target);
2467 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2469 my $vmid = extract_param
($param, 'vmid');
2471 raise_param_exc
({ force
=> "Only root may use this option." })
2472 if $param->{force
} && $authuser ne 'root@pam';
2475 my $conf = PVE
::QemuServer
::load_config
($vmid);
2477 # try to detect errors early
2479 PVE
::QemuServer
::check_lock
($conf);
2481 if (PVE
::QemuServer
::check_running
($vmid)) {
2482 die "cant migrate running VM without --online\n"
2483 if !$param->{online
};
2486 my $storecfg = PVE
::Storage
::config
();
2487 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2489 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2494 my $service = "pvevm:$vmid";
2496 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2498 print "Executing HA migrate for VM $vmid to node $target\n";
2500 PVE
::Tools
::run_command
($cmd);
2505 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2512 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2515 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2520 __PACKAGE__-
>register_method({
2522 path
=> '{vmid}/monitor',
2526 description
=> "Execute Qemu monitor commands.",
2528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2531 additionalProperties
=> 0,
2533 node
=> get_standard_option
('pve-node'),
2534 vmid
=> get_standard_option
('pve-vmid'),
2537 description
=> "The monitor command.",
2541 returns
=> { type
=> 'string'},
2545 my $vmid = $param->{vmid
};
2547 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2551 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2553 $res = "ERROR: $@" if $@;
2558 __PACKAGE__-
>register_method({
2559 name
=> 'resize_vm',
2560 path
=> '{vmid}/resize',
2564 description
=> "Extend volume size.",
2566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2569 additionalProperties
=> 0,
2571 node
=> get_standard_option
('pve-node'),
2572 vmid
=> get_standard_option
('pve-vmid'),
2573 skiplock
=> get_standard_option
('skiplock'),
2576 description
=> "The disk you want to resize.",
2577 enum
=> [PVE
::QemuServer
::disknames
()],
2581 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2582 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.",
2586 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2592 returns
=> { type
=> 'null'},
2596 my $rpcenv = PVE
::RPCEnvironment
::get
();
2598 my $authuser = $rpcenv->get_user();
2600 my $node = extract_param
($param, 'node');
2602 my $vmid = extract_param
($param, 'vmid');
2604 my $digest = extract_param
($param, 'digest');
2606 my $disk = extract_param
($param, 'disk');
2608 my $sizestr = extract_param
($param, 'size');
2610 my $skiplock = extract_param
($param, 'skiplock');
2611 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2612 if $skiplock && $authuser ne 'root@pam';
2614 my $storecfg = PVE
::Storage
::config
();
2616 my $updatefn = sub {
2618 my $conf = PVE
::QemuServer
::load_config
($vmid);
2620 die "checksum missmatch (file change by other user?)\n"
2621 if $digest && $digest ne $conf->{digest
};
2622 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2624 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2626 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2628 my $volid = $drive->{file
};
2630 die "disk '$disk' has no associated volume\n" if !$volid;
2632 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2634 die "you can't online resize a virtio windows bootdisk\n"
2635 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2637 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2639 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2641 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2643 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2644 my ($ext, $newsize, $unit) = ($1, $2, $4);
2647 $newsize = $newsize * 1024;
2648 } elsif ($unit eq 'M') {
2649 $newsize = $newsize * 1024 * 1024;
2650 } elsif ($unit eq 'G') {
2651 $newsize = $newsize * 1024 * 1024 * 1024;
2652 } elsif ($unit eq 'T') {
2653 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2656 $newsize += $size if $ext;
2657 $newsize = int($newsize);
2659 die "unable to skrink disk size\n" if $newsize < $size;
2661 return if $size == $newsize;
2663 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2665 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2667 $drive->{size
} = $newsize;
2668 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2670 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2673 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2677 __PACKAGE__-
>register_method({
2678 name
=> 'snapshot_list',
2679 path
=> '{vmid}/snapshot',
2681 description
=> "List all snapshots.",
2683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2686 protected
=> 1, # qemu pid files are only readable by root
2688 additionalProperties
=> 0,
2690 vmid
=> get_standard_option
('pve-vmid'),
2691 node
=> get_standard_option
('pve-node'),
2700 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2705 my $vmid = $param->{vmid
};
2707 my $conf = PVE
::QemuServer
::load_config
($vmid);
2708 my $snaphash = $conf->{snapshots
} || {};
2712 foreach my $name (keys %$snaphash) {
2713 my $d = $snaphash->{$name};
2716 snaptime
=> $d->{snaptime
} || 0,
2717 vmstate
=> $d->{vmstate
} ?
1 : 0,
2718 description
=> $d->{description
} || '',
2720 $item->{parent
} = $d->{parent
} if $d->{parent
};
2721 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2725 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2726 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2727 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2729 push @$res, $current;
2734 __PACKAGE__-
>register_method({
2736 path
=> '{vmid}/snapshot',
2740 description
=> "Snapshot a VM.",
2742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2745 additionalProperties
=> 0,
2747 node
=> get_standard_option
('pve-node'),
2748 vmid
=> get_standard_option
('pve-vmid'),
2749 snapname
=> get_standard_option
('pve-snapshot-name'),
2753 description
=> "Save the vmstate",
2758 description
=> "Freeze the filesystem",
2763 description
=> "A textual description or comment.",
2769 description
=> "the task ID.",
2774 my $rpcenv = PVE
::RPCEnvironment
::get
();
2776 my $authuser = $rpcenv->get_user();
2778 my $node = extract_param
($param, 'node');
2780 my $vmid = extract_param
($param, 'vmid');
2782 my $snapname = extract_param
($param, 'snapname');
2784 die "unable to use snapshot name 'current' (reserved name)\n"
2785 if $snapname eq 'current';
2788 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2789 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2790 $param->{freezefs
}, $param->{description
});
2793 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2796 __PACKAGE__-
>register_method({
2797 name
=> 'snapshot_cmd_idx',
2798 path
=> '{vmid}/snapshot/{snapname}',
2805 additionalProperties
=> 0,
2807 vmid
=> get_standard_option
('pve-vmid'),
2808 node
=> get_standard_option
('pve-node'),
2809 snapname
=> get_standard_option
('pve-snapshot-name'),
2818 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2825 push @$res, { cmd
=> 'rollback' };
2826 push @$res, { cmd
=> 'config' };
2831 __PACKAGE__-
>register_method({
2832 name
=> 'update_snapshot_config',
2833 path
=> '{vmid}/snapshot/{snapname}/config',
2837 description
=> "Update snapshot metadata.",
2839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2842 additionalProperties
=> 0,
2844 node
=> get_standard_option
('pve-node'),
2845 vmid
=> get_standard_option
('pve-vmid'),
2846 snapname
=> get_standard_option
('pve-snapshot-name'),
2850 description
=> "A textual description or comment.",
2854 returns
=> { type
=> 'null' },
2858 my $rpcenv = PVE
::RPCEnvironment
::get
();
2860 my $authuser = $rpcenv->get_user();
2862 my $vmid = extract_param
($param, 'vmid');
2864 my $snapname = extract_param
($param, 'snapname');
2866 return undef if !defined($param->{description
});
2868 my $updatefn = sub {
2870 my $conf = PVE
::QemuServer
::load_config
($vmid);
2872 PVE
::QemuServer
::check_lock
($conf);
2874 my $snap = $conf->{snapshots
}->{$snapname};
2876 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2878 $snap->{description
} = $param->{description
} if defined($param->{description
});
2880 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2883 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2888 __PACKAGE__-
>register_method({
2889 name
=> 'get_snapshot_config',
2890 path
=> '{vmid}/snapshot/{snapname}/config',
2893 description
=> "Get snapshot configuration",
2895 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2898 additionalProperties
=> 0,
2900 node
=> get_standard_option
('pve-node'),
2901 vmid
=> get_standard_option
('pve-vmid'),
2902 snapname
=> get_standard_option
('pve-snapshot-name'),
2905 returns
=> { type
=> "object" },
2909 my $rpcenv = PVE
::RPCEnvironment
::get
();
2911 my $authuser = $rpcenv->get_user();
2913 my $vmid = extract_param
($param, 'vmid');
2915 my $snapname = extract_param
($param, 'snapname');
2917 my $conf = PVE
::QemuServer
::load_config
($vmid);
2919 my $snap = $conf->{snapshots
}->{$snapname};
2921 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2926 __PACKAGE__-
>register_method({
2928 path
=> '{vmid}/snapshot/{snapname}/rollback',
2932 description
=> "Rollback VM state to specified snapshot.",
2934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2937 additionalProperties
=> 0,
2939 node
=> get_standard_option
('pve-node'),
2940 vmid
=> get_standard_option
('pve-vmid'),
2941 snapname
=> get_standard_option
('pve-snapshot-name'),
2946 description
=> "the task ID.",
2951 my $rpcenv = PVE
::RPCEnvironment
::get
();
2953 my $authuser = $rpcenv->get_user();
2955 my $node = extract_param
($param, 'node');
2957 my $vmid = extract_param
($param, 'vmid');
2959 my $snapname = extract_param
($param, 'snapname');
2962 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2963 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2966 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2969 __PACKAGE__-
>register_method({
2970 name
=> 'delsnapshot',
2971 path
=> '{vmid}/snapshot/{snapname}',
2975 description
=> "Delete a VM snapshot.",
2977 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2980 additionalProperties
=> 0,
2982 node
=> get_standard_option
('pve-node'),
2983 vmid
=> get_standard_option
('pve-vmid'),
2984 snapname
=> get_standard_option
('pve-snapshot-name'),
2988 description
=> "For removal from config file, even if removing disk snapshots fails.",
2994 description
=> "the task ID.",
2999 my $rpcenv = PVE
::RPCEnvironment
::get
();
3001 my $authuser = $rpcenv->get_user();
3003 my $node = extract_param
($param, 'node');
3005 my $vmid = extract_param
($param, 'vmid');
3007 my $snapname = extract_param
($param, 'snapname');
3010 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3011 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3014 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3017 __PACKAGE__-
>register_method({
3019 path
=> '{vmid}/template',
3023 description
=> "Create a Template.",
3025 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3026 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3029 additionalProperties
=> 0,
3031 node
=> get_standard_option
('pve-node'),
3032 vmid
=> get_standard_option
('pve-vmid'),
3036 description
=> "If you want to convert only 1 disk to base image.",
3037 enum
=> [PVE
::QemuServer
::disknames
()],
3042 returns
=> { type
=> 'null'},
3046 my $rpcenv = PVE
::RPCEnvironment
::get
();
3048 my $authuser = $rpcenv->get_user();
3050 my $node = extract_param
($param, 'node');
3052 my $vmid = extract_param
($param, 'vmid');
3054 my $disk = extract_param
($param, 'disk');
3056 my $updatefn = sub {
3058 my $conf = PVE
::QemuServer
::load_config
($vmid);
3060 PVE
::QemuServer
::check_lock
($conf);
3062 die "unable to create template, because VM contains snapshots\n"
3063 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3065 die "you can't convert a template to a template\n"
3066 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3068 die "you can't convert a VM to template if VM is running\n"
3069 if PVE
::QemuServer
::check_running
($vmid);
3072 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3075 $conf->{template
} = 1;
3076 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3078 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3081 PVE
::QemuServer
::lock_config
($vmid, $updatefn);