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 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2414 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2417 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2420 __PACKAGE__-
>register_method({
2421 name
=> 'migrate_vm',
2422 path
=> '{vmid}/migrate',
2426 description
=> "Migrate virtual machine. Creates a new migration task.",
2428 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2431 additionalProperties
=> 0,
2433 node
=> get_standard_option
('pve-node'),
2434 vmid
=> get_standard_option
('pve-vmid'),
2435 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2438 description
=> "Use online/live migration.",
2443 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2450 description
=> "the task ID.",
2455 my $rpcenv = PVE
::RPCEnvironment
::get
();
2457 my $authuser = $rpcenv->get_user();
2459 my $target = extract_param
($param, 'target');
2461 my $localnode = PVE
::INotify
::nodename
();
2462 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2464 PVE
::Cluster
::check_cfs_quorum
();
2466 PVE
::Cluster
::check_node_exists
($target);
2468 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2470 my $vmid = extract_param
($param, 'vmid');
2472 raise_param_exc
({ force
=> "Only root may use this option." })
2473 if $param->{force
} && $authuser ne 'root@pam';
2476 my $conf = PVE
::QemuServer
::load_config
($vmid);
2478 # try to detect errors early
2480 PVE
::QemuServer
::check_lock
($conf);
2482 if (PVE
::QemuServer
::check_running
($vmid)) {
2483 die "cant migrate running VM without --online\n"
2484 if !$param->{online
};
2487 my $storecfg = PVE
::Storage
::config
();
2488 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2490 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2495 my $service = "pvevm:$vmid";
2497 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2499 print "Executing HA migrate for VM $vmid to node $target\n";
2501 PVE
::Tools
::run_command
($cmd);
2506 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2513 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2516 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2521 __PACKAGE__-
>register_method({
2523 path
=> '{vmid}/monitor',
2527 description
=> "Execute Qemu monitor commands.",
2529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2532 additionalProperties
=> 0,
2534 node
=> get_standard_option
('pve-node'),
2535 vmid
=> get_standard_option
('pve-vmid'),
2538 description
=> "The monitor command.",
2542 returns
=> { type
=> 'string'},
2546 my $vmid = $param->{vmid
};
2548 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2552 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2554 $res = "ERROR: $@" if $@;
2559 __PACKAGE__-
>register_method({
2560 name
=> 'resize_vm',
2561 path
=> '{vmid}/resize',
2565 description
=> "Extend volume size.",
2567 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2570 additionalProperties
=> 0,
2572 node
=> get_standard_option
('pve-node'),
2573 vmid
=> get_standard_option
('pve-vmid'),
2574 skiplock
=> get_standard_option
('skiplock'),
2577 description
=> "The disk you want to resize.",
2578 enum
=> [PVE
::QemuServer
::disknames
()],
2582 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2583 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.",
2587 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2593 returns
=> { type
=> 'null'},
2597 my $rpcenv = PVE
::RPCEnvironment
::get
();
2599 my $authuser = $rpcenv->get_user();
2601 my $node = extract_param
($param, 'node');
2603 my $vmid = extract_param
($param, 'vmid');
2605 my $digest = extract_param
($param, 'digest');
2607 my $disk = extract_param
($param, 'disk');
2609 my $sizestr = extract_param
($param, 'size');
2611 my $skiplock = extract_param
($param, 'skiplock');
2612 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2613 if $skiplock && $authuser ne 'root@pam';
2615 my $storecfg = PVE
::Storage
::config
();
2617 my $updatefn = sub {
2619 my $conf = PVE
::QemuServer
::load_config
($vmid);
2621 die "checksum missmatch (file change by other user?)\n"
2622 if $digest && $digest ne $conf->{digest
};
2623 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2625 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2627 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2629 my $volid = $drive->{file
};
2631 die "disk '$disk' has no associated volume\n" if !$volid;
2633 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2635 die "you can't online resize a virtio windows bootdisk\n"
2636 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2638 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2640 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2642 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2644 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2645 my ($ext, $newsize, $unit) = ($1, $2, $4);
2648 $newsize = $newsize * 1024;
2649 } elsif ($unit eq 'M') {
2650 $newsize = $newsize * 1024 * 1024;
2651 } elsif ($unit eq 'G') {
2652 $newsize = $newsize * 1024 * 1024 * 1024;
2653 } elsif ($unit eq 'T') {
2654 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2657 $newsize += $size if $ext;
2658 $newsize = int($newsize);
2660 die "unable to skrink disk size\n" if $newsize < $size;
2662 return if $size == $newsize;
2664 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2666 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2668 $drive->{size
} = $newsize;
2669 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2671 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2674 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2678 __PACKAGE__-
>register_method({
2679 name
=> 'snapshot_list',
2680 path
=> '{vmid}/snapshot',
2682 description
=> "List all snapshots.",
2684 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2687 protected
=> 1, # qemu pid files are only readable by root
2689 additionalProperties
=> 0,
2691 vmid
=> get_standard_option
('pve-vmid'),
2692 node
=> get_standard_option
('pve-node'),
2701 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2706 my $vmid = $param->{vmid
};
2708 my $conf = PVE
::QemuServer
::load_config
($vmid);
2709 my $snaphash = $conf->{snapshots
} || {};
2713 foreach my $name (keys %$snaphash) {
2714 my $d = $snaphash->{$name};
2717 snaptime
=> $d->{snaptime
} || 0,
2718 vmstate
=> $d->{vmstate
} ?
1 : 0,
2719 description
=> $d->{description
} || '',
2721 $item->{parent
} = $d->{parent
} if $d->{parent
};
2722 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2726 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2727 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2728 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2730 push @$res, $current;
2735 __PACKAGE__-
>register_method({
2737 path
=> '{vmid}/snapshot',
2741 description
=> "Snapshot a VM.",
2743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2746 additionalProperties
=> 0,
2748 node
=> get_standard_option
('pve-node'),
2749 vmid
=> get_standard_option
('pve-vmid'),
2750 snapname
=> get_standard_option
('pve-snapshot-name'),
2754 description
=> "Save the vmstate",
2759 description
=> "Freeze the filesystem",
2764 description
=> "A textual description or comment.",
2770 description
=> "the task ID.",
2775 my $rpcenv = PVE
::RPCEnvironment
::get
();
2777 my $authuser = $rpcenv->get_user();
2779 my $node = extract_param
($param, 'node');
2781 my $vmid = extract_param
($param, 'vmid');
2783 my $snapname = extract_param
($param, 'snapname');
2785 die "unable to use snapshot name 'current' (reserved name)\n"
2786 if $snapname eq 'current';
2789 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2790 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2791 $param->{freezefs
}, $param->{description
});
2794 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2797 __PACKAGE__-
>register_method({
2798 name
=> 'snapshot_cmd_idx',
2799 path
=> '{vmid}/snapshot/{snapname}',
2806 additionalProperties
=> 0,
2808 vmid
=> get_standard_option
('pve-vmid'),
2809 node
=> get_standard_option
('pve-node'),
2810 snapname
=> get_standard_option
('pve-snapshot-name'),
2819 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2826 push @$res, { cmd
=> 'rollback' };
2827 push @$res, { cmd
=> 'config' };
2832 __PACKAGE__-
>register_method({
2833 name
=> 'update_snapshot_config',
2834 path
=> '{vmid}/snapshot/{snapname}/config',
2838 description
=> "Update snapshot metadata.",
2840 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2843 additionalProperties
=> 0,
2845 node
=> get_standard_option
('pve-node'),
2846 vmid
=> get_standard_option
('pve-vmid'),
2847 snapname
=> get_standard_option
('pve-snapshot-name'),
2851 description
=> "A textual description or comment.",
2855 returns
=> { type
=> 'null' },
2859 my $rpcenv = PVE
::RPCEnvironment
::get
();
2861 my $authuser = $rpcenv->get_user();
2863 my $vmid = extract_param
($param, 'vmid');
2865 my $snapname = extract_param
($param, 'snapname');
2867 return undef if !defined($param->{description
});
2869 my $updatefn = sub {
2871 my $conf = PVE
::QemuServer
::load_config
($vmid);
2873 PVE
::QemuServer
::check_lock
($conf);
2875 my $snap = $conf->{snapshots
}->{$snapname};
2877 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2879 $snap->{description
} = $param->{description
} if defined($param->{description
});
2881 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2884 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2889 __PACKAGE__-
>register_method({
2890 name
=> 'get_snapshot_config',
2891 path
=> '{vmid}/snapshot/{snapname}/config',
2894 description
=> "Get snapshot configuration",
2896 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2899 additionalProperties
=> 0,
2901 node
=> get_standard_option
('pve-node'),
2902 vmid
=> get_standard_option
('pve-vmid'),
2903 snapname
=> get_standard_option
('pve-snapshot-name'),
2906 returns
=> { type
=> "object" },
2910 my $rpcenv = PVE
::RPCEnvironment
::get
();
2912 my $authuser = $rpcenv->get_user();
2914 my $vmid = extract_param
($param, 'vmid');
2916 my $snapname = extract_param
($param, 'snapname');
2918 my $conf = PVE
::QemuServer
::load_config
($vmid);
2920 my $snap = $conf->{snapshots
}->{$snapname};
2922 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2927 __PACKAGE__-
>register_method({
2929 path
=> '{vmid}/snapshot/{snapname}/rollback',
2933 description
=> "Rollback VM state to specified snapshot.",
2935 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2938 additionalProperties
=> 0,
2940 node
=> get_standard_option
('pve-node'),
2941 vmid
=> get_standard_option
('pve-vmid'),
2942 snapname
=> get_standard_option
('pve-snapshot-name'),
2947 description
=> "the task ID.",
2952 my $rpcenv = PVE
::RPCEnvironment
::get
();
2954 my $authuser = $rpcenv->get_user();
2956 my $node = extract_param
($param, 'node');
2958 my $vmid = extract_param
($param, 'vmid');
2960 my $snapname = extract_param
($param, 'snapname');
2963 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2964 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2967 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2970 __PACKAGE__-
>register_method({
2971 name
=> 'delsnapshot',
2972 path
=> '{vmid}/snapshot/{snapname}',
2976 description
=> "Delete a VM snapshot.",
2978 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2981 additionalProperties
=> 0,
2983 node
=> get_standard_option
('pve-node'),
2984 vmid
=> get_standard_option
('pve-vmid'),
2985 snapname
=> get_standard_option
('pve-snapshot-name'),
2989 description
=> "For removal from config file, even if removing disk snapshots fails.",
2995 description
=> "the task ID.",
3000 my $rpcenv = PVE
::RPCEnvironment
::get
();
3002 my $authuser = $rpcenv->get_user();
3004 my $node = extract_param
($param, 'node');
3006 my $vmid = extract_param
($param, 'vmid');
3008 my $snapname = extract_param
($param, 'snapname');
3011 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3012 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3015 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3018 __PACKAGE__-
>register_method({
3020 path
=> '{vmid}/template',
3024 description
=> "Create a Template.",
3026 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3027 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3030 additionalProperties
=> 0,
3032 node
=> get_standard_option
('pve-node'),
3033 vmid
=> get_standard_option
('pve-vmid'),
3037 description
=> "If you want to convert only 1 disk to base image.",
3038 enum
=> [PVE
::QemuServer
::disknames
()],
3043 returns
=> { type
=> 'null'},
3047 my $rpcenv = PVE
::RPCEnvironment
::get
();
3049 my $authuser = $rpcenv->get_user();
3051 my $node = extract_param
($param, 'node');
3053 my $vmid = extract_param
($param, 'vmid');
3055 my $disk = extract_param
($param, 'disk');
3057 my $updatefn = sub {
3059 my $conf = PVE
::QemuServer
::load_config
($vmid);
3061 PVE
::QemuServer
::check_lock
($conf);
3063 die "unable to create template, because VM contains snapshots\n"
3064 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3066 die "you can't convert a template to a template\n"
3067 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3069 die "you can't convert a VM to template if VM is running\n"
3070 if PVE
::QemuServer
::check_running
($vmid);
3073 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3076 $conf->{template
} = 1;
3077 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3079 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3082 PVE
::QemuServer
::lock_config
($vmid, $updatefn);