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 $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1383 my $title = "VM $vmid - $conf->{'name'}",
1385 my $port = PVE
::QemuServer
::spice_port
($vmid);
1387 my ($ticket, undef, $remote_viewer_config) =
1388 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1390 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1391 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1393 return $remote_viewer_config;
1396 __PACKAGE__-
>register_method({
1398 path
=> '{vmid}/status',
1401 description
=> "Directory index",
1406 additionalProperties
=> 0,
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1417 subdir
=> { type
=> 'string' },
1420 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1426 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1429 { subdir
=> 'current' },
1430 { subdir
=> 'start' },
1431 { subdir
=> 'stop' },
1437 my $vm_is_ha_managed = sub {
1440 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1441 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1447 __PACKAGE__-
>register_method({
1448 name
=> 'vm_status',
1449 path
=> '{vmid}/status/current',
1452 protected
=> 1, # qemu pid files are only readable by root
1453 description
=> "Get virtual machine status.",
1455 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1458 additionalProperties
=> 0,
1460 node
=> get_standard_option
('pve-node'),
1461 vmid
=> get_standard_option
('pve-vmid'),
1464 returns
=> { type
=> 'object' },
1469 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1471 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1472 my $status = $vmstatus->{$param->{vmid
}};
1474 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1476 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1481 __PACKAGE__-
>register_method({
1483 path
=> '{vmid}/status/start',
1487 description
=> "Start virtual machine.",
1489 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid'),
1496 skiplock
=> get_standard_option
('skiplock'),
1497 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1498 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1499 machine
=> get_standard_option
('pve-qm-machine'),
1508 my $rpcenv = PVE
::RPCEnvironment
::get
();
1510 my $authuser = $rpcenv->get_user();
1512 my $node = extract_param
($param, 'node');
1514 my $vmid = extract_param
($param, 'vmid');
1516 my $machine = extract_param
($param, 'machine');
1518 my $stateuri = extract_param
($param, 'stateuri');
1519 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1520 if $stateuri && $authuser ne 'root@pam';
1522 my $skiplock = extract_param
($param, 'skiplock');
1523 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1524 if $skiplock && $authuser ne 'root@pam';
1526 my $migratedfrom = extract_param
($param, 'migratedfrom');
1527 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1528 if $migratedfrom && $authuser ne 'root@pam';
1530 # read spice ticket from STDIN
1532 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1533 if (defined(my $line = <>)) {
1535 $spice_ticket = $line;
1539 my $storecfg = PVE
::Storage
::config
();
1541 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1542 $rpcenv->{type
} ne 'ha') {
1547 my $service = "pvevm:$vmid";
1549 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1551 print "Executing HA start for VM $vmid\n";
1553 PVE
::Tools
::run_command
($cmd);
1558 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1565 syslog
('info', "start VM $vmid: $upid\n");
1567 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1568 $machine, $spice_ticket);
1573 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1577 __PACKAGE__-
>register_method({
1579 path
=> '{vmid}/status/stop',
1583 description
=> "Stop virtual machine.",
1585 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1588 additionalProperties
=> 0,
1590 node
=> get_standard_option
('pve-node'),
1591 vmid
=> get_standard_option
('pve-vmid'),
1592 skiplock
=> get_standard_option
('skiplock'),
1593 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1595 description
=> "Wait maximal timeout seconds.",
1601 description
=> "Do not decativate storage volumes.",
1614 my $rpcenv = PVE
::RPCEnvironment
::get
();
1616 my $authuser = $rpcenv->get_user();
1618 my $node = extract_param
($param, 'node');
1620 my $vmid = extract_param
($param, 'vmid');
1622 my $skiplock = extract_param
($param, 'skiplock');
1623 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1624 if $skiplock && $authuser ne 'root@pam';
1626 my $keepActive = extract_param
($param, 'keepActive');
1627 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1628 if $keepActive && $authuser ne 'root@pam';
1630 my $migratedfrom = extract_param
($param, 'migratedfrom');
1631 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1632 if $migratedfrom && $authuser ne 'root@pam';
1635 my $storecfg = PVE
::Storage
::config
();
1637 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1642 my $service = "pvevm:$vmid";
1644 my $cmd = ['clusvcadm', '-d', $service];
1646 print "Executing HA stop for VM $vmid\n";
1648 PVE
::Tools
::run_command
($cmd);
1653 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1659 syslog
('info', "stop VM $vmid: $upid\n");
1661 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1662 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1667 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1671 __PACKAGE__-
>register_method({
1673 path
=> '{vmid}/status/reset',
1677 description
=> "Reset virtual machine.",
1679 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1682 additionalProperties
=> 0,
1684 node
=> get_standard_option
('pve-node'),
1685 vmid
=> get_standard_option
('pve-vmid'),
1686 skiplock
=> get_standard_option
('skiplock'),
1695 my $rpcenv = PVE
::RPCEnvironment
::get
();
1697 my $authuser = $rpcenv->get_user();
1699 my $node = extract_param
($param, 'node');
1701 my $vmid = extract_param
($param, 'vmid');
1703 my $skiplock = extract_param
($param, 'skiplock');
1704 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1705 if $skiplock && $authuser ne 'root@pam';
1707 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1712 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1717 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1720 __PACKAGE__-
>register_method({
1721 name
=> 'vm_shutdown',
1722 path
=> '{vmid}/status/shutdown',
1726 description
=> "Shutdown virtual machine.",
1728 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1731 additionalProperties
=> 0,
1733 node
=> get_standard_option
('pve-node'),
1734 vmid
=> get_standard_option
('pve-vmid'),
1735 skiplock
=> get_standard_option
('skiplock'),
1737 description
=> "Wait maximal timeout seconds.",
1743 description
=> "Make sure the VM stops.",
1749 description
=> "Do not decativate storage volumes.",
1762 my $rpcenv = PVE
::RPCEnvironment
::get
();
1764 my $authuser = $rpcenv->get_user();
1766 my $node = extract_param
($param, 'node');
1768 my $vmid = extract_param
($param, 'vmid');
1770 my $skiplock = extract_param
($param, 'skiplock');
1771 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1772 if $skiplock && $authuser ne 'root@pam';
1774 my $keepActive = extract_param
($param, 'keepActive');
1775 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1776 if $keepActive && $authuser ne 'root@pam';
1778 my $storecfg = PVE
::Storage
::config
();
1783 syslog
('info', "shutdown VM $vmid: $upid\n");
1785 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1786 1, $param->{forceStop
}, $keepActive);
1791 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1794 __PACKAGE__-
>register_method({
1795 name
=> 'vm_suspend',
1796 path
=> '{vmid}/status/suspend',
1800 description
=> "Suspend virtual machine.",
1802 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1805 additionalProperties
=> 0,
1807 node
=> get_standard_option
('pve-node'),
1808 vmid
=> get_standard_option
('pve-vmid'),
1809 skiplock
=> get_standard_option
('skiplock'),
1818 my $rpcenv = PVE
::RPCEnvironment
::get
();
1820 my $authuser = $rpcenv->get_user();
1822 my $node = extract_param
($param, 'node');
1824 my $vmid = extract_param
($param, 'vmid');
1826 my $skiplock = extract_param
($param, 'skiplock');
1827 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1828 if $skiplock && $authuser ne 'root@pam';
1830 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1835 syslog
('info', "suspend VM $vmid: $upid\n");
1837 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1842 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1845 __PACKAGE__-
>register_method({
1846 name
=> 'vm_resume',
1847 path
=> '{vmid}/status/resume',
1851 description
=> "Resume virtual machine.",
1853 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1856 additionalProperties
=> 0,
1858 node
=> get_standard_option
('pve-node'),
1859 vmid
=> get_standard_option
('pve-vmid'),
1860 skiplock
=> get_standard_option
('skiplock'),
1869 my $rpcenv = PVE
::RPCEnvironment
::get
();
1871 my $authuser = $rpcenv->get_user();
1873 my $node = extract_param
($param, 'node');
1875 my $vmid = extract_param
($param, 'vmid');
1877 my $skiplock = extract_param
($param, 'skiplock');
1878 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1879 if $skiplock && $authuser ne 'root@pam';
1881 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1886 syslog
('info', "resume VM $vmid: $upid\n");
1888 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1893 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1896 __PACKAGE__-
>register_method({
1897 name
=> 'vm_sendkey',
1898 path
=> '{vmid}/sendkey',
1902 description
=> "Send key event to virtual machine.",
1904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1907 additionalProperties
=> 0,
1909 node
=> get_standard_option
('pve-node'),
1910 vmid
=> get_standard_option
('pve-vmid'),
1911 skiplock
=> get_standard_option
('skiplock'),
1913 description
=> "The key (qemu monitor encoding).",
1918 returns
=> { type
=> 'null'},
1922 my $rpcenv = PVE
::RPCEnvironment
::get
();
1924 my $authuser = $rpcenv->get_user();
1926 my $node = extract_param
($param, 'node');
1928 my $vmid = extract_param
($param, 'vmid');
1930 my $skiplock = extract_param
($param, 'skiplock');
1931 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1932 if $skiplock && $authuser ne 'root@pam';
1934 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1939 __PACKAGE__-
>register_method({
1940 name
=> 'vm_feature',
1941 path
=> '{vmid}/feature',
1945 description
=> "Check if feature for virtual machine is available.",
1947 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1950 additionalProperties
=> 0,
1952 node
=> get_standard_option
('pve-node'),
1953 vmid
=> get_standard_option
('pve-vmid'),
1955 description
=> "Feature to check.",
1957 enum
=> [ 'snapshot', 'clone', 'copy' ],
1959 snapname
=> get_standard_option
('pve-snapshot-name', {
1967 hasFeature
=> { type
=> 'boolean' },
1970 items
=> { type
=> 'string' },
1977 my $node = extract_param
($param, 'node');
1979 my $vmid = extract_param
($param, 'vmid');
1981 my $snapname = extract_param
($param, 'snapname');
1983 my $feature = extract_param
($param, 'feature');
1985 my $running = PVE
::QemuServer
::check_running
($vmid);
1987 my $conf = PVE
::QemuServer
::load_config
($vmid);
1990 my $snap = $conf->{snapshots
}->{$snapname};
1991 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1994 my $storecfg = PVE
::Storage
::config
();
1996 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1997 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2000 hasFeature
=> $hasFeature,
2001 nodes
=> [ keys %$nodelist ],
2005 __PACKAGE__-
>register_method({
2007 path
=> '{vmid}/clone',
2011 description
=> "Create a copy of virtual machine/template.",
2013 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2014 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2015 "'Datastore.AllocateSpace' on any used storage.",
2018 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2020 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2021 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2026 additionalProperties
=> 0,
2028 node
=> get_standard_option
('pve-node'),
2029 vmid
=> get_standard_option
('pve-vmid'),
2030 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2033 type
=> 'string', format
=> 'dns-name',
2034 description
=> "Set a name for the new VM.",
2039 description
=> "Description for the new VM.",
2043 type
=> 'string', format
=> 'pve-poolid',
2044 description
=> "Add the new VM to the specified pool.",
2046 snapname
=> get_standard_option
('pve-snapshot-name', {
2050 storage
=> get_standard_option
('pve-storage-id', {
2051 description
=> "Target storage for full clone.",
2056 description
=> "Target format for file storage.",
2060 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2065 description
=> "Create a full copy of all disk. This is always done when " .
2066 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2069 target
=> get_standard_option
('pve-node', {
2070 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2081 my $rpcenv = PVE
::RPCEnvironment
::get
();
2083 my $authuser = $rpcenv->get_user();
2085 my $node = extract_param
($param, 'node');
2087 my $vmid = extract_param
($param, 'vmid');
2089 my $newid = extract_param
($param, 'newid');
2091 my $pool = extract_param
($param, 'pool');
2093 if (defined($pool)) {
2094 $rpcenv->check_pool_exist($pool);
2097 my $snapname = extract_param
($param, 'snapname');
2099 my $storage = extract_param
($param, 'storage');
2101 my $format = extract_param
($param, 'format');
2103 my $target = extract_param
($param, 'target');
2105 my $localnode = PVE
::INotify
::nodename
();
2107 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2109 PVE
::Cluster
::check_node_exists
($target) if $target;
2111 my $storecfg = PVE
::Storage
::config
();
2114 # check if storage is enabled on local node
2115 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2117 # check if storage is available on target node
2118 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2119 # clone only works if target storage is shared
2120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2121 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2125 PVE
::Cluster
::check_cfs_quorum
();
2127 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2129 # exclusive lock if VM is running - else shared lock is enough;
2130 my $shared_lock = $running ?
0 : 1;
2134 # do all tests after lock
2135 # we also try to do all tests before we fork the worker
2137 my $conf = PVE
::QemuServer
::load_config
($vmid);
2139 PVE
::QemuServer
::check_lock
($conf);
2141 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2143 die "unexpected state change\n" if $verify_running != $running;
2145 die "snapshot '$snapname' does not exist\n"
2146 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2148 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2150 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2152 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2154 my $conffile = PVE
::QemuServer
::config_file
($newid);
2156 die "unable to create VM $newid: config file already exists\n"
2159 my $newconf = { lock => 'clone' };
2163 foreach my $opt (keys %$oldconf) {
2164 my $value = $oldconf->{$opt};
2166 # do not copy snapshot related info
2167 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2168 $opt eq 'vmstate' || $opt eq 'snapstate';
2170 # always change MAC! address
2171 if ($opt =~ m/^net(\d+)$/) {
2172 my $net = PVE
::QemuServer
::parse_net
($value);
2173 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2174 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2175 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2176 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2177 die "unable to parse drive options for '$opt'\n" if !$drive;
2178 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2179 $newconf->{$opt} = $value; # simply copy configuration
2181 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2182 die "Full clone feature is not available"
2183 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2186 $drives->{$opt} = $drive;
2187 push @$vollist, $drive->{file
};
2190 # copy everything else
2191 $newconf->{$opt} = $value;
2195 delete $newconf->{template
};
2197 if ($param->{name
}) {
2198 $newconf->{name
} = $param->{name
};
2200 if ($oldconf->{name
}) {
2201 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2203 $newconf->{name
} = "Copy-of-VM-$vmid";
2207 if ($param->{description
}) {
2208 $newconf->{description
} = $param->{description
};
2211 # create empty/temp config - this fails if VM already exists on other node
2212 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2217 my $newvollist = [];
2220 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2222 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2224 foreach my $opt (keys %$drives) {
2225 my $drive = $drives->{$opt};
2227 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2228 $newid, $storage, $format, $drive->{full
}, $newvollist);
2230 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2232 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2235 delete $newconf->{lock};
2236 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2239 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2240 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2242 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2243 die "Failed to move config to node '$target' - rename failed: $!\n"
2244 if !rename($conffile, $newconffile);
2247 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2252 sleep 1; # some storage like rbd need to wait before release volume - really?
2254 foreach my $volid (@$newvollist) {
2255 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2258 die "clone failed: $err";
2264 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2267 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2268 # Aquire exclusive lock lock for $newid
2269 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2274 __PACKAGE__-
>register_method({
2275 name
=> 'move_vm_disk',
2276 path
=> '{vmid}/move_disk',
2280 description
=> "Move volume to different storage.",
2282 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2283 "and 'Datastore.AllocateSpace' permissions on the storage.",
2286 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2287 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2291 additionalProperties
=> 0,
2293 node
=> get_standard_option
('pve-node'),
2294 vmid
=> get_standard_option
('pve-vmid'),
2297 description
=> "The disk you want to move.",
2298 enum
=> [ PVE
::QemuServer
::disknames
() ],
2300 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2303 description
=> "Target Format.",
2304 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2309 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2315 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2323 description
=> "the task ID.",
2328 my $rpcenv = PVE
::RPCEnvironment
::get
();
2330 my $authuser = $rpcenv->get_user();
2332 my $node = extract_param
($param, 'node');
2334 my $vmid = extract_param
($param, 'vmid');
2336 my $digest = extract_param
($param, 'digest');
2338 my $disk = extract_param
($param, 'disk');
2340 my $storeid = extract_param
($param, 'storage');
2342 my $format = extract_param
($param, 'format');
2344 my $storecfg = PVE
::Storage
::config
();
2346 my $updatefn = sub {
2348 my $conf = PVE
::QemuServer
::load_config
($vmid);
2350 die "checksum missmatch (file change by other user?)\n"
2351 if $digest && $digest ne $conf->{digest
};
2353 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2355 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2357 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2359 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2362 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2363 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2367 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2368 (!$format || !$oldfmt || $oldfmt eq $format);
2370 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2372 my $running = PVE
::QemuServer
::check_running
($vmid);
2374 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2378 my $newvollist = [];
2381 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2383 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2384 $vmid, $storeid, $format, 1, $newvollist);
2386 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2388 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2390 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2393 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2394 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2401 foreach my $volid (@$newvollist) {
2402 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2405 die "storage migration failed: $err";
2408 if ($param->{delete}) {
2409 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2410 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2411 if ($used_paths->{$path}){
2412 warn "volume $old_volid have snapshots. Can't delete it";
2413 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2414 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2416 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2422 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2425 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2428 __PACKAGE__-
>register_method({
2429 name
=> 'migrate_vm',
2430 path
=> '{vmid}/migrate',
2434 description
=> "Migrate virtual machine. Creates a new migration task.",
2436 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2439 additionalProperties
=> 0,
2441 node
=> get_standard_option
('pve-node'),
2442 vmid
=> get_standard_option
('pve-vmid'),
2443 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2446 description
=> "Use online/live migration.",
2451 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2458 description
=> "the task ID.",
2463 my $rpcenv = PVE
::RPCEnvironment
::get
();
2465 my $authuser = $rpcenv->get_user();
2467 my $target = extract_param
($param, 'target');
2469 my $localnode = PVE
::INotify
::nodename
();
2470 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2472 PVE
::Cluster
::check_cfs_quorum
();
2474 PVE
::Cluster
::check_node_exists
($target);
2476 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2478 my $vmid = extract_param
($param, 'vmid');
2480 raise_param_exc
({ force
=> "Only root may use this option." })
2481 if $param->{force
} && $authuser ne 'root@pam';
2484 my $conf = PVE
::QemuServer
::load_config
($vmid);
2486 # try to detect errors early
2488 PVE
::QemuServer
::check_lock
($conf);
2490 if (PVE
::QemuServer
::check_running
($vmid)) {
2491 die "cant migrate running VM without --online\n"
2492 if !$param->{online
};
2495 my $storecfg = PVE
::Storage
::config
();
2496 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2498 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2503 my $service = "pvevm:$vmid";
2505 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2507 print "Executing HA migrate for VM $vmid to node $target\n";
2509 PVE
::Tools
::run_command
($cmd);
2514 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2521 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2524 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2529 __PACKAGE__-
>register_method({
2531 path
=> '{vmid}/monitor',
2535 description
=> "Execute Qemu monitor commands.",
2537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid'),
2546 description
=> "The monitor command.",
2550 returns
=> { type
=> 'string'},
2554 my $vmid = $param->{vmid
};
2556 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2560 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2562 $res = "ERROR: $@" if $@;
2567 __PACKAGE__-
>register_method({
2568 name
=> 'resize_vm',
2569 path
=> '{vmid}/resize',
2573 description
=> "Extend volume size.",
2575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2578 additionalProperties
=> 0,
2580 node
=> get_standard_option
('pve-node'),
2581 vmid
=> get_standard_option
('pve-vmid'),
2582 skiplock
=> get_standard_option
('skiplock'),
2585 description
=> "The disk you want to resize.",
2586 enum
=> [PVE
::QemuServer
::disknames
()],
2590 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2591 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.",
2595 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2601 returns
=> { type
=> 'null'},
2605 my $rpcenv = PVE
::RPCEnvironment
::get
();
2607 my $authuser = $rpcenv->get_user();
2609 my $node = extract_param
($param, 'node');
2611 my $vmid = extract_param
($param, 'vmid');
2613 my $digest = extract_param
($param, 'digest');
2615 my $disk = extract_param
($param, 'disk');
2617 my $sizestr = extract_param
($param, 'size');
2619 my $skiplock = extract_param
($param, 'skiplock');
2620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2621 if $skiplock && $authuser ne 'root@pam';
2623 my $storecfg = PVE
::Storage
::config
();
2625 my $updatefn = sub {
2627 my $conf = PVE
::QemuServer
::load_config
($vmid);
2629 die "checksum missmatch (file change by other user?)\n"
2630 if $digest && $digest ne $conf->{digest
};
2631 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2633 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2635 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2637 my $volid = $drive->{file
};
2639 die "disk '$disk' has no associated volume\n" if !$volid;
2641 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2643 die "you can't online resize a virtio windows bootdisk\n"
2644 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2646 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2648 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2650 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2652 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2653 my ($ext, $newsize, $unit) = ($1, $2, $4);
2656 $newsize = $newsize * 1024;
2657 } elsif ($unit eq 'M') {
2658 $newsize = $newsize * 1024 * 1024;
2659 } elsif ($unit eq 'G') {
2660 $newsize = $newsize * 1024 * 1024 * 1024;
2661 } elsif ($unit eq 'T') {
2662 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2665 $newsize += $size if $ext;
2666 $newsize = int($newsize);
2668 die "unable to skrink disk size\n" if $newsize < $size;
2670 return if $size == $newsize;
2672 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2674 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2676 $drive->{size
} = $newsize;
2677 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2679 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2682 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2686 __PACKAGE__-
>register_method({
2687 name
=> 'snapshot_list',
2688 path
=> '{vmid}/snapshot',
2690 description
=> "List all snapshots.",
2692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2695 protected
=> 1, # qemu pid files are only readable by root
2697 additionalProperties
=> 0,
2699 vmid
=> get_standard_option
('pve-vmid'),
2700 node
=> get_standard_option
('pve-node'),
2709 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2714 my $vmid = $param->{vmid
};
2716 my $conf = PVE
::QemuServer
::load_config
($vmid);
2717 my $snaphash = $conf->{snapshots
} || {};
2721 foreach my $name (keys %$snaphash) {
2722 my $d = $snaphash->{$name};
2725 snaptime
=> $d->{snaptime
} || 0,
2726 vmstate
=> $d->{vmstate
} ?
1 : 0,
2727 description
=> $d->{description
} || '',
2729 $item->{parent
} = $d->{parent
} if $d->{parent
};
2730 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2734 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2735 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2736 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2738 push @$res, $current;
2743 __PACKAGE__-
>register_method({
2745 path
=> '{vmid}/snapshot',
2749 description
=> "Snapshot a VM.",
2751 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2754 additionalProperties
=> 0,
2756 node
=> get_standard_option
('pve-node'),
2757 vmid
=> get_standard_option
('pve-vmid'),
2758 snapname
=> get_standard_option
('pve-snapshot-name'),
2762 description
=> "Save the vmstate",
2767 description
=> "Freeze the filesystem",
2772 description
=> "A textual description or comment.",
2778 description
=> "the task ID.",
2783 my $rpcenv = PVE
::RPCEnvironment
::get
();
2785 my $authuser = $rpcenv->get_user();
2787 my $node = extract_param
($param, 'node');
2789 my $vmid = extract_param
($param, 'vmid');
2791 my $snapname = extract_param
($param, 'snapname');
2793 die "unable to use snapshot name 'current' (reserved name)\n"
2794 if $snapname eq 'current';
2797 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2798 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2799 $param->{freezefs
}, $param->{description
});
2802 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2805 __PACKAGE__-
>register_method({
2806 name
=> 'snapshot_cmd_idx',
2807 path
=> '{vmid}/snapshot/{snapname}',
2814 additionalProperties
=> 0,
2816 vmid
=> get_standard_option
('pve-vmid'),
2817 node
=> get_standard_option
('pve-node'),
2818 snapname
=> get_standard_option
('pve-snapshot-name'),
2827 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2834 push @$res, { cmd
=> 'rollback' };
2835 push @$res, { cmd
=> 'config' };
2840 __PACKAGE__-
>register_method({
2841 name
=> 'update_snapshot_config',
2842 path
=> '{vmid}/snapshot/{snapname}/config',
2846 description
=> "Update snapshot metadata.",
2848 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2851 additionalProperties
=> 0,
2853 node
=> get_standard_option
('pve-node'),
2854 vmid
=> get_standard_option
('pve-vmid'),
2855 snapname
=> get_standard_option
('pve-snapshot-name'),
2859 description
=> "A textual description or comment.",
2863 returns
=> { type
=> 'null' },
2867 my $rpcenv = PVE
::RPCEnvironment
::get
();
2869 my $authuser = $rpcenv->get_user();
2871 my $vmid = extract_param
($param, 'vmid');
2873 my $snapname = extract_param
($param, 'snapname');
2875 return undef if !defined($param->{description
});
2877 my $updatefn = sub {
2879 my $conf = PVE
::QemuServer
::load_config
($vmid);
2881 PVE
::QemuServer
::check_lock
($conf);
2883 my $snap = $conf->{snapshots
}->{$snapname};
2885 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2887 $snap->{description
} = $param->{description
} if defined($param->{description
});
2889 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2892 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2897 __PACKAGE__-
>register_method({
2898 name
=> 'get_snapshot_config',
2899 path
=> '{vmid}/snapshot/{snapname}/config',
2902 description
=> "Get snapshot configuration",
2904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2907 additionalProperties
=> 0,
2909 node
=> get_standard_option
('pve-node'),
2910 vmid
=> get_standard_option
('pve-vmid'),
2911 snapname
=> get_standard_option
('pve-snapshot-name'),
2914 returns
=> { type
=> "object" },
2918 my $rpcenv = PVE
::RPCEnvironment
::get
();
2920 my $authuser = $rpcenv->get_user();
2922 my $vmid = extract_param
($param, 'vmid');
2924 my $snapname = extract_param
($param, 'snapname');
2926 my $conf = PVE
::QemuServer
::load_config
($vmid);
2928 my $snap = $conf->{snapshots
}->{$snapname};
2930 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2935 __PACKAGE__-
>register_method({
2937 path
=> '{vmid}/snapshot/{snapname}/rollback',
2941 description
=> "Rollback VM state to specified snapshot.",
2943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2946 additionalProperties
=> 0,
2948 node
=> get_standard_option
('pve-node'),
2949 vmid
=> get_standard_option
('pve-vmid'),
2950 snapname
=> get_standard_option
('pve-snapshot-name'),
2955 description
=> "the task ID.",
2960 my $rpcenv = PVE
::RPCEnvironment
::get
();
2962 my $authuser = $rpcenv->get_user();
2964 my $node = extract_param
($param, 'node');
2966 my $vmid = extract_param
($param, 'vmid');
2968 my $snapname = extract_param
($param, 'snapname');
2971 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2972 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2975 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2978 __PACKAGE__-
>register_method({
2979 name
=> 'delsnapshot',
2980 path
=> '{vmid}/snapshot/{snapname}',
2984 description
=> "Delete a VM snapshot.",
2986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2989 additionalProperties
=> 0,
2991 node
=> get_standard_option
('pve-node'),
2992 vmid
=> get_standard_option
('pve-vmid'),
2993 snapname
=> get_standard_option
('pve-snapshot-name'),
2997 description
=> "For removal from config file, even if removing disk snapshots fails.",
3003 description
=> "the task ID.",
3008 my $rpcenv = PVE
::RPCEnvironment
::get
();
3010 my $authuser = $rpcenv->get_user();
3012 my $node = extract_param
($param, 'node');
3014 my $vmid = extract_param
($param, 'vmid');
3016 my $snapname = extract_param
($param, 'snapname');
3019 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3020 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3023 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3026 __PACKAGE__-
>register_method({
3028 path
=> '{vmid}/template',
3032 description
=> "Create a Template.",
3034 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3035 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3038 additionalProperties
=> 0,
3040 node
=> get_standard_option
('pve-node'),
3041 vmid
=> get_standard_option
('pve-vmid'),
3045 description
=> "If you want to convert only 1 disk to base image.",
3046 enum
=> [PVE
::QemuServer
::disknames
()],
3051 returns
=> { type
=> 'null'},
3055 my $rpcenv = PVE
::RPCEnvironment
::get
();
3057 my $authuser = $rpcenv->get_user();
3059 my $node = extract_param
($param, 'node');
3061 my $vmid = extract_param
($param, 'vmid');
3063 my $disk = extract_param
($param, 'disk');
3065 my $updatefn = sub {
3067 my $conf = PVE
::QemuServer
::load_config
($vmid);
3069 PVE
::QemuServer
::check_lock
($conf);
3071 die "unable to create template, because VM contains snapshots\n"
3072 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3074 die "you can't convert a template to a template\n"
3075 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3077 die "you can't convert a VM to template if VM is running\n"
3078 if PVE
::QemuServer
::check_running
($vmid);
3081 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3084 $conf->{template
} = 1;
3085 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3087 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3090 PVE
::QemuServer
::lock_config
($vmid, $updatefn);