1 package PVE
::API2
::Qemu
;
8 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
10 use PVE
::Tools
qw(extract_param);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
18 use PVE
::AccessControl
;
22 use Data
::Dumper
; # fixme: remove
24 use base
qw(PVE::RESTHandler);
26 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
28 my $resolve_cdrom_alias = sub {
31 if (my $value = $param->{cdrom
}) {
32 $value .= ",media=cdrom" if $value !~ m/media=/;
33 $param->{ide2
} = $value;
34 delete $param->{cdrom
};
39 my $check_storage_access = sub {
40 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
42 PVE
::QemuServer
::foreach_drive
($settings, sub {
43 my ($ds, $drive) = @_;
45 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
47 my $volid = $drive->{file
};
49 if (!$volid || $volid eq 'none') {
51 } elsif ($isCDROM && ($volid eq 'cdrom')) {
52 $rpcenv->check($authuser, "/", ['Sys.Console']);
53 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
54 my ($storeid, $size) = ($2 || $default_storage, $3);
55 die "no storage ID specified (and no default storage)\n" if !$storeid;
56 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
58 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
63 my $check_storage_access_clone = sub {
64 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
68 PVE
::QemuServer
::foreach_drive
($conf, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
75 return if !$volid || $volid eq 'none';
78 if ($volid eq 'cdrom') {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
81 # we simply allow access
82 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
83 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
84 $sharedvm = 0 if !$scfg->{shared
};
88 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
89 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
90 $sharedvm = 0 if !$scfg->{shared
};
92 $sid = $storage if $storage;
93 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
100 # Note: $pool is only needed when creating a VM, because pool permissions
101 # are automatically inherited if VM already exists inside a pool.
102 my $create_disks = sub {
103 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
108 PVE
::QemuServer
::foreach_drive
($settings, sub {
109 my ($ds, $disk) = @_;
111 my $volid = $disk->{file
};
113 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
114 delete $disk->{size
};
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
116 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
117 my ($storeid, $size) = ($2 || $default_storage, $3);
118 die "no storage ID specified (and no default storage)\n" if !$storeid;
119 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
120 my $fmt = $disk->{format
} || $defformat;
121 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
122 $fmt, undef, $size*1024*1024);
123 $disk->{file
} = $volid;
124 $disk->{size
} = $size*1024*1024*1024;
125 push @$vollist, $volid;
126 delete $disk->{format
}; # no longer needed
127 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
130 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
132 my $volid_is_new = 1;
135 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
136 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
141 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
143 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
145 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
147 die "volume $volid does not exists\n" if !$size;
149 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
380 my $restorefn = sub {
382 # fixme: this test does not work if VM exists on other node!
384 die "unable to restore vm $vmid: config file already exists\n"
387 die "unable to restore vm $vmid: vm is running\n"
388 if PVE
::QemuServer
::check_running
($vmid);
392 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
395 unique
=> $unique });
397 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
400 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
406 die "unable to create vm $vmid: config file already exists\n"
417 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
419 # try to be smart about bootdisk
420 my @disks = PVE
::QemuServer
::disknames
();
422 foreach my $ds (reverse @disks) {
423 next if !$conf->{$ds};
424 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
425 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
429 if (!$conf->{bootdisk
} && $firstdisk) {
430 $conf->{bootdisk
} = $firstdisk;
433 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
439 foreach my $volid (@$vollist) {
440 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
443 die "create failed - $err";
446 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
449 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
452 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
455 __PACKAGE__-
>register_method({
460 description
=> "Directory index",
465 additionalProperties
=> 0,
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid'),
476 subdir
=> { type
=> 'string' },
479 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
485 { subdir
=> 'config' },
486 { subdir
=> 'status' },
487 { subdir
=> 'unlink' },
488 { subdir
=> 'vncproxy' },
489 { subdir
=> 'migrate' },
490 { subdir
=> 'resize' },
491 { subdir
=> 'move' },
493 { subdir
=> 'rrddata' },
494 { subdir
=> 'monitor' },
495 { subdir
=> 'snapshot' },
496 { subdir
=> 'spiceproxy' },
497 { subdir
=> 'sendkey' },
503 __PACKAGE__-
>register_method({
505 path
=> '{vmid}/rrd',
507 protected
=> 1, # fixme: can we avoid that?
509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
511 description
=> "Read VM RRD statistics (returns PNG)",
513 additionalProperties
=> 0,
515 node
=> get_standard_option
('pve-node'),
516 vmid
=> get_standard_option
('pve-vmid'),
518 description
=> "Specify the time frame you are interested in.",
520 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
523 description
=> "The list of datasources you want to display.",
524 type
=> 'string', format
=> 'pve-configid-list',
527 description
=> "The RRD consolidation function",
529 enum
=> [ 'AVERAGE', 'MAX' ],
537 filename
=> { type
=> 'string' },
543 return PVE
::Cluster
::create_rrd_graph
(
544 "pve2-vm/$param->{vmid}", $param->{timeframe
},
545 $param->{ds
}, $param->{cf
});
549 __PACKAGE__-
>register_method({
551 path
=> '{vmid}/rrddata',
553 protected
=> 1, # fixme: can we avoid that?
555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
557 description
=> "Read VM RRD statistics",
559 additionalProperties
=> 0,
561 node
=> get_standard_option
('pve-node'),
562 vmid
=> get_standard_option
('pve-vmid'),
564 description
=> "Specify the time frame you are interested in.",
566 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
569 description
=> "The RRD consolidation function",
571 enum
=> [ 'AVERAGE', 'MAX' ],
586 return PVE
::Cluster
::create_rrd_data
(
587 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
591 __PACKAGE__-
>register_method({
593 path
=> '{vmid}/config',
596 description
=> "Get virtual machine configuration.",
598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
604 vmid
=> get_standard_option
('pve-vmid'),
612 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
619 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
621 delete $conf->{snapshots
};
626 my $vm_is_volid_owner = sub {
627 my ($storecfg, $vmid, $volid) =@_;
629 if ($volid !~ m
|^/|) {
631 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
632 if ($owner && ($owner == $vmid)) {
640 my $test_deallocate_drive = sub {
641 my ($storecfg, $vmid, $key, $drive, $force) = @_;
643 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
644 my $volid = $drive->{file
};
645 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
646 if ($force || $key =~ m/^unused/) {
647 my $sid = PVE
::Storage
::parse_volume_id
($volid);
656 my $delete_drive = sub {
657 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
659 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
660 my $volid = $drive->{file
};
662 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
663 if ($force || $key =~ m/^unused/) {
665 # check if the disk is really unused
666 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
667 my $path = PVE
::Storage
::path
($storecfg, $volid);
669 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
670 if $used_paths->{$path};
672 PVE
::Storage
::vdisk_free
($storecfg, $volid);
676 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
681 delete $conf->{$key};
684 my $vmconfig_delete_option = sub {
685 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
687 return if !defined($conf->{$opt});
689 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
692 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
694 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
695 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
696 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
700 my $unplugwarning = "";
701 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
702 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
703 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
704 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
705 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
706 $unplugwarning = "<br>verify that your guest support acpi hotplug";
709 if ($opt eq 'tablet') {
710 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
712 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
716 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
717 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
719 delete $conf->{$opt};
722 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
725 my $safe_num_ne = sub {
728 return 0 if !defined($a) && !defined($b);
729 return 1 if !defined($a);
730 return 1 if !defined($b);
735 my $vmconfig_update_disk = sub {
736 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
738 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
740 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
741 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
743 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
748 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
750 my $media = $drive->{media
} || 'disk';
751 my $oldmedia = $old_drive->{media
} || 'disk';
752 die "unable to change media type\n" if $media ne $oldmedia;
754 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
755 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
757 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
758 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
761 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
762 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
763 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
764 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
765 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
766 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
}) ||
767 &$safe_num_ne($drive->{mbps_max
}, $old_drive->{mbps_max
}) ||
768 &$safe_num_ne($drive->{mbps_rd_max
}, $old_drive->{mbps_rd_max
}) ||
769 &$safe_num_ne($drive->{mbps_wr_max
}, $old_drive->{mbps_wr_max
}) ||
770 &$safe_num_ne($drive->{iops_max
}, $old_drive->{iops_max
}) ||
771 &$safe_num_ne($drive->{iops_rd_max
}, $old_drive->{iops_rd_max
}) ||
772 &$safe_num_ne($drive->{iops_wr_max
}, $old_drive->{iops_wr_max
})) {
773 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
774 ($drive->{mbps
} || 0)*1024*1024,
775 ($drive->{mbps_rd
} || 0)*1024*1024,
776 ($drive->{mbps_wr
} || 0)*1024*1024,
778 $drive->{iops_rd
} || 0,
779 $drive->{iops_wr
} || 0,
780 ($drive->{mbps_max
} || 0)*1024*1024,
781 ($drive->{mbps_rd_max
} || 0)*1024*1024,
782 ($drive->{mbps_wr_max
} || 0)*1024*1024,
783 $drive->{iops_max
} || 0,
784 $drive->{iops_rd_max
} || 0,
785 $drive->{iops_wr_max
} || 0)
786 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
791 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
792 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
794 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
795 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
797 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
799 if (PVE
::QemuServer
::check_running
($vmid)) {
800 if ($drive->{file
} eq 'none') {
801 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
803 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
804 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
805 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
809 } else { # hotplug new disks
811 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
815 my $vmconfig_update_net = sub {
816 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
818 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
819 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
820 my $newnet = PVE
::QemuServer
::parse_net
($value);
822 if($oldnet->{model
} ne $newnet->{model
}){
823 #if model change, we try to hot-unplug
824 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
827 if($newnet->{bridge
} && $oldnet->{bridge
}){
828 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
830 if($newnet->{rate
} ne $oldnet->{rate
}){
831 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
834 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
835 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
836 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
840 #if bridge/nat mode change, we try to hot-unplug
841 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
846 $conf->{$opt} = $value;
847 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
848 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
850 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
852 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
855 # POST/PUT {vmid}/config implementation
857 # The original API used PUT (idempotent) an we assumed that all operations
858 # are fast. But it turned out that almost any configuration change can
859 # involve hot-plug actions, or disk alloc/free. Such actions can take long
860 # time to complete and have side effects (not idempotent).
862 # The new implementation uses POST and forks a worker process. We added
863 # a new option 'background_delay'. If specified we wait up to
864 # 'background_delay' second for the worker task to complete. It returns null
865 # if the task is finished within that time, else we return the UPID.
867 my $update_vm_api = sub {
868 my ($param, $sync) = @_;
870 my $rpcenv = PVE
::RPCEnvironment
::get
();
872 my $authuser = $rpcenv->get_user();
874 my $node = extract_param
($param, 'node');
876 my $vmid = extract_param
($param, 'vmid');
878 my $digest = extract_param
($param, 'digest');
880 my $background_delay = extract_param
($param, 'background_delay');
882 my @paramarr = (); # used for log message
883 foreach my $key (keys %$param) {
884 push @paramarr, "-$key", $param->{$key};
887 my $skiplock = extract_param
($param, 'skiplock');
888 raise_param_exc
({ skiplock
=> "Only root may use this option." })
889 if $skiplock && $authuser ne 'root@pam';
891 my $delete_str = extract_param
($param, 'delete');
893 my $force = extract_param
($param, 'force');
895 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
897 my $storecfg = PVE
::Storage
::config
();
899 my $defaults = PVE
::QemuServer
::load_defaults
();
901 &$resolve_cdrom_alias($param);
903 # now try to verify all parameters
906 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
907 $opt = 'ide2' if $opt eq 'cdrom';
908 raise_param_exc
({ delete => "you can't use '-$opt' and " .
909 "-delete $opt' at the same time" })
910 if defined($param->{$opt});
912 if (!PVE
::QemuServer
::option_exists
($opt)) {
913 raise_param_exc
({ delete => "unknown option '$opt'" });
919 foreach my $opt (keys %$param) {
920 if (PVE
::QemuServer
::valid_drivename
($opt)) {
922 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
923 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
924 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
925 } elsif ($opt =~ m/^net(\d+)$/) {
927 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
928 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
932 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
934 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
936 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
940 my $conf = PVE
::QemuServer
::load_config
($vmid);
942 die "checksum missmatch (file change by other user?)\n"
943 if $digest && $digest ne $conf->{digest
};
945 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
947 if ($param->{memory
} || defined($param->{balloon
})) {
948 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
949 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
951 die "balloon value too large (must be smaller than assigned memory)\n"
952 if $balloon && $balloon > $maxmem;
955 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
959 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
961 foreach my $opt (@delete) { # delete
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
966 my $running = PVE
::QemuServer
::check_running
($vmid);
968 foreach my $opt (keys %$param) { # add/change
970 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
972 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
974 if (PVE
::QemuServer
::valid_drivename
($opt)) {
976 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
977 $opt, $param->{$opt}, $force);
979 } elsif ($opt =~ m/^net(\d+)$/) { #nics
981 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
982 $opt, $param->{$opt});
986 if($opt eq 'tablet' && $param->{$opt} == 1){
987 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
988 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
989 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
992 if($opt eq 'cores' && $conf->{maxcpus
}){
993 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
996 $conf->{$opt} = $param->{$opt};
997 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1001 # allow manual ballooning if shares is set to zero
1002 if ($running && defined($param->{balloon
}) &&
1003 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1004 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1005 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1013 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1015 if ($background_delay) {
1017 # Note: It would be better to do that in the Event based HTTPServer
1018 # to avoid blocking call to sleep.
1020 my $end_time = time() + $background_delay;
1022 my $task = PVE
::Tools
::upid_decode
($upid);
1025 while (time() < $end_time) {
1026 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1028 sleep(1); # this gets interrupted when child process ends
1032 my $status = PVE
::Tools
::upid_read_status
($upid);
1033 return undef if $status eq 'OK';
1042 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1045 my $vm_config_perm_list = [
1050 'VM.Config.Network',
1052 'VM.Config.Options',
1055 __PACKAGE__-
>register_method({
1056 name
=> 'update_vm_async',
1057 path
=> '{vmid}/config',
1061 description
=> "Set virtual machine options (asynchrounous API).",
1063 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1066 additionalProperties
=> 0,
1067 properties
=> PVE
::QemuServer
::json_config_properties
(
1069 node
=> get_standard_option
('pve-node'),
1070 vmid
=> get_standard_option
('pve-vmid'),
1071 skiplock
=> get_standard_option
('skiplock'),
1073 type
=> 'string', format
=> 'pve-configid-list',
1074 description
=> "A list of settings you want to delete.",
1079 description
=> $opt_force_description,
1081 requires
=> 'delete',
1085 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1089 background_delay
=> {
1091 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1102 code
=> $update_vm_api,
1105 __PACKAGE__-
>register_method({
1106 name
=> 'update_vm',
1107 path
=> '{vmid}/config',
1111 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1113 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1116 additionalProperties
=> 0,
1117 properties
=> PVE
::QemuServer
::json_config_properties
(
1119 node
=> get_standard_option
('pve-node'),
1120 vmid
=> get_standard_option
('pve-vmid'),
1121 skiplock
=> get_standard_option
('skiplock'),
1123 type
=> 'string', format
=> 'pve-configid-list',
1124 description
=> "A list of settings you want to delete.",
1129 description
=> $opt_force_description,
1131 requires
=> 'delete',
1135 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1141 returns
=> { type
=> 'null' },
1144 &$update_vm_api($param, 1);
1150 __PACKAGE__-
>register_method({
1151 name
=> 'destroy_vm',
1156 description
=> "Destroy the vm (also delete all used/owned volumes).",
1158 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1161 additionalProperties
=> 0,
1163 node
=> get_standard_option
('pve-node'),
1164 vmid
=> get_standard_option
('pve-vmid'),
1165 skiplock
=> get_standard_option
('skiplock'),
1174 my $rpcenv = PVE
::RPCEnvironment
::get
();
1176 my $authuser = $rpcenv->get_user();
1178 my $vmid = $param->{vmid
};
1180 my $skiplock = $param->{skiplock
};
1181 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1182 if $skiplock && $authuser ne 'root@pam';
1185 my $conf = PVE
::QemuServer
::load_config
($vmid);
1187 my $storecfg = PVE
::Storage
::config
();
1189 my $delVMfromPoolFn = sub {
1190 my $usercfg = cfs_read_file
("user.cfg");
1191 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1192 if (my $data = $usercfg->{pools
}->{$pool}) {
1193 delete $data->{vms
}->{$vmid};
1194 delete $usercfg->{vms
}->{$vmid};
1195 cfs_write_file
("user.cfg", $usercfg);
1203 syslog
('info', "destroy VM $vmid: $upid\n");
1205 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1207 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1210 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1213 __PACKAGE__-
>register_method({
1215 path
=> '{vmid}/unlink',
1219 description
=> "Unlink/delete disk images.",
1221 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1224 additionalProperties
=> 0,
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid'),
1229 type
=> 'string', format
=> 'pve-configid-list',
1230 description
=> "A list of disk IDs you want to delete.",
1234 description
=> $opt_force_description,
1239 returns
=> { type
=> 'null'},
1243 $param->{delete} = extract_param
($param, 'idlist');
1245 __PACKAGE__-
>update_vm($param);
1252 __PACKAGE__-
>register_method({
1254 path
=> '{vmid}/vncproxy',
1258 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1260 description
=> "Creates a TCP VNC proxy connections.",
1262 additionalProperties
=> 0,
1264 node
=> get_standard_option
('pve-node'),
1265 vmid
=> get_standard_option
('pve-vmid'),
1269 additionalProperties
=> 0,
1271 user
=> { type
=> 'string' },
1272 ticket
=> { type
=> 'string' },
1273 cert
=> { type
=> 'string' },
1274 port
=> { type
=> 'integer' },
1275 upid
=> { type
=> 'string' },
1281 my $rpcenv = PVE
::RPCEnvironment
::get
();
1283 my $authuser = $rpcenv->get_user();
1285 my $vmid = $param->{vmid
};
1286 my $node = $param->{node
};
1288 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1290 my $authpath = "/vms/$vmid";
1292 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1294 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1297 my $port = PVE
::Tools
::next_vnc_port
();
1302 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1303 $remip = PVE
::Cluster
::remote_node_ip
($node);
1304 # NOTE: kvm VNC traffic is already TLS encrypted
1305 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1313 syslog
('info', "starting vnc proxy $upid\n");
1317 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1319 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1320 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1321 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1322 '-timeout', $timeout, '-authpath', $authpath,
1323 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1326 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1328 my $qmstr = join(' ', @$qmcmd);
1330 # also redirect stderr (else we get RFB protocol errors)
1331 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1334 PVE
::Tools
::run_command
($cmd);
1339 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1341 PVE
::Tools
::wait_for_vnc_port
($port);
1352 __PACKAGE__-
>register_method({
1353 name
=> 'spiceproxy',
1354 path
=> '{vmid}/spiceproxy',
1359 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1361 description
=> "Returns a SPICE configuration to connect to the VM.",
1363 additionalProperties
=> 0,
1365 node
=> get_standard_option
('pve-node'),
1366 vmid
=> get_standard_option
('pve-vmid'),
1367 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1370 returns
=> get_standard_option
('remote-viewer-config'),
1374 my $rpcenv = PVE
::RPCEnvironment
::get
();
1376 my $authuser = $rpcenv->get_user();
1378 my $vmid = $param->{vmid
};
1379 my $node = $param->{node
};
1380 my $proxy = $param->{proxy
};
1382 my $title = "VM $vmid";
1384 my $port = PVE
::QemuServer
::spice_port
($vmid);
1386 my ($ticket, undef, $remote_viewer_config) =
1387 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1389 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1390 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1392 return $remote_viewer_config;
1395 __PACKAGE__-
>register_method({
1397 path
=> '{vmid}/status',
1400 description
=> "Directory index",
1405 additionalProperties
=> 0,
1407 node
=> get_standard_option
('pve-node'),
1408 vmid
=> get_standard_option
('pve-vmid'),
1416 subdir
=> { type
=> 'string' },
1419 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1425 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1428 { subdir
=> 'current' },
1429 { subdir
=> 'start' },
1430 { subdir
=> 'stop' },
1436 my $vm_is_ha_managed = sub {
1439 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1440 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1446 __PACKAGE__-
>register_method({
1447 name
=> 'vm_status',
1448 path
=> '{vmid}/status/current',
1451 protected
=> 1, # qemu pid files are only readable by root
1452 description
=> "Get virtual machine status.",
1454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1457 additionalProperties
=> 0,
1459 node
=> get_standard_option
('pve-node'),
1460 vmid
=> get_standard_option
('pve-vmid'),
1463 returns
=> { type
=> 'object' },
1468 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1470 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1471 my $status = $vmstatus->{$param->{vmid
}};
1473 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1475 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1480 __PACKAGE__-
>register_method({
1482 path
=> '{vmid}/status/start',
1486 description
=> "Start virtual machine.",
1488 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 vmid
=> get_standard_option
('pve-vmid'),
1495 skiplock
=> get_standard_option
('skiplock'),
1496 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1497 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1498 machine
=> get_standard_option
('pve-qm-machine'),
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $node = extract_param
($param, 'node');
1513 my $vmid = extract_param
($param, 'vmid');
1515 my $machine = extract_param
($param, 'machine');
1517 my $stateuri = extract_param
($param, 'stateuri');
1518 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1519 if $stateuri && $authuser ne 'root@pam';
1521 my $skiplock = extract_param
($param, 'skiplock');
1522 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1523 if $skiplock && $authuser ne 'root@pam';
1525 my $migratedfrom = extract_param
($param, 'migratedfrom');
1526 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1527 if $migratedfrom && $authuser ne 'root@pam';
1529 # read spice ticket from STDIN
1531 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1532 if (defined(my $line = <>)) {
1534 $spice_ticket = $line;
1538 my $storecfg = PVE
::Storage
::config
();
1540 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1541 $rpcenv->{type
} ne 'ha') {
1546 my $service = "pvevm:$vmid";
1548 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1550 print "Executing HA start for VM $vmid\n";
1552 PVE
::Tools
::run_command
($cmd);
1557 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1564 syslog
('info', "start VM $vmid: $upid\n");
1566 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1567 $machine, $spice_ticket);
1572 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1576 __PACKAGE__-
>register_method({
1578 path
=> '{vmid}/status/stop',
1582 description
=> "Stop virtual machine.",
1584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1591 skiplock
=> get_standard_option
('skiplock'),
1592 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1594 description
=> "Wait maximal timeout seconds.",
1600 description
=> "Do not decativate storage volumes.",
1613 my $rpcenv = PVE
::RPCEnvironment
::get
();
1615 my $authuser = $rpcenv->get_user();
1617 my $node = extract_param
($param, 'node');
1619 my $vmid = extract_param
($param, 'vmid');
1621 my $skiplock = extract_param
($param, 'skiplock');
1622 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1623 if $skiplock && $authuser ne 'root@pam';
1625 my $keepActive = extract_param
($param, 'keepActive');
1626 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1627 if $keepActive && $authuser ne 'root@pam';
1629 my $migratedfrom = extract_param
($param, 'migratedfrom');
1630 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1631 if $migratedfrom && $authuser ne 'root@pam';
1634 my $storecfg = PVE
::Storage
::config
();
1636 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1641 my $service = "pvevm:$vmid";
1643 my $cmd = ['clusvcadm', '-d', $service];
1645 print "Executing HA stop for VM $vmid\n";
1647 PVE
::Tools
::run_command
($cmd);
1652 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1658 syslog
('info', "stop VM $vmid: $upid\n");
1660 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1661 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1666 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1670 __PACKAGE__-
>register_method({
1672 path
=> '{vmid}/status/reset',
1676 description
=> "Reset virtual machine.",
1678 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1681 additionalProperties
=> 0,
1683 node
=> get_standard_option
('pve-node'),
1684 vmid
=> get_standard_option
('pve-vmid'),
1685 skiplock
=> get_standard_option
('skiplock'),
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $node = extract_param
($param, 'node');
1700 my $vmid = extract_param
($param, 'vmid');
1702 my $skiplock = extract_param
($param, 'skiplock');
1703 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1704 if $skiplock && $authuser ne 'root@pam';
1706 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1711 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1716 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1719 __PACKAGE__-
>register_method({
1720 name
=> 'vm_shutdown',
1721 path
=> '{vmid}/status/shutdown',
1725 description
=> "Shutdown virtual machine.",
1727 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1730 additionalProperties
=> 0,
1732 node
=> get_standard_option
('pve-node'),
1733 vmid
=> get_standard_option
('pve-vmid'),
1734 skiplock
=> get_standard_option
('skiplock'),
1736 description
=> "Wait maximal timeout seconds.",
1742 description
=> "Make sure the VM stops.",
1748 description
=> "Do not decativate storage volumes.",
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $node = extract_param
($param, 'node');
1767 my $vmid = extract_param
($param, 'vmid');
1769 my $skiplock = extract_param
($param, 'skiplock');
1770 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1771 if $skiplock && $authuser ne 'root@pam';
1773 my $keepActive = extract_param
($param, 'keepActive');
1774 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1775 if $keepActive && $authuser ne 'root@pam';
1777 my $storecfg = PVE
::Storage
::config
();
1782 syslog
('info', "shutdown VM $vmid: $upid\n");
1784 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1785 1, $param->{forceStop
}, $keepActive);
1790 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1793 __PACKAGE__-
>register_method({
1794 name
=> 'vm_suspend',
1795 path
=> '{vmid}/status/suspend',
1799 description
=> "Suspend virtual machine.",
1801 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1804 additionalProperties
=> 0,
1806 node
=> get_standard_option
('pve-node'),
1807 vmid
=> get_standard_option
('pve-vmid'),
1808 skiplock
=> get_standard_option
('skiplock'),
1817 my $rpcenv = PVE
::RPCEnvironment
::get
();
1819 my $authuser = $rpcenv->get_user();
1821 my $node = extract_param
($param, 'node');
1823 my $vmid = extract_param
($param, 'vmid');
1825 my $skiplock = extract_param
($param, 'skiplock');
1826 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1827 if $skiplock && $authuser ne 'root@pam';
1829 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1834 syslog
('info', "suspend VM $vmid: $upid\n");
1836 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1841 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1844 __PACKAGE__-
>register_method({
1845 name
=> 'vm_resume',
1846 path
=> '{vmid}/status/resume',
1850 description
=> "Resume virtual machine.",
1852 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1855 additionalProperties
=> 0,
1857 node
=> get_standard_option
('pve-node'),
1858 vmid
=> get_standard_option
('pve-vmid'),
1859 skiplock
=> get_standard_option
('skiplock'),
1868 my $rpcenv = PVE
::RPCEnvironment
::get
();
1870 my $authuser = $rpcenv->get_user();
1872 my $node = extract_param
($param, 'node');
1874 my $vmid = extract_param
($param, 'vmid');
1876 my $skiplock = extract_param
($param, 'skiplock');
1877 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1878 if $skiplock && $authuser ne 'root@pam';
1880 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1885 syslog
('info', "resume VM $vmid: $upid\n");
1887 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1892 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1895 __PACKAGE__-
>register_method({
1896 name
=> 'vm_sendkey',
1897 path
=> '{vmid}/sendkey',
1901 description
=> "Send key event to virtual machine.",
1903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1906 additionalProperties
=> 0,
1908 node
=> get_standard_option
('pve-node'),
1909 vmid
=> get_standard_option
('pve-vmid'),
1910 skiplock
=> get_standard_option
('skiplock'),
1912 description
=> "The key (qemu monitor encoding).",
1917 returns
=> { type
=> 'null'},
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1938 __PACKAGE__-
>register_method({
1939 name
=> 'vm_feature',
1940 path
=> '{vmid}/feature',
1944 description
=> "Check if feature for virtual machine is available.",
1946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1949 additionalProperties
=> 0,
1951 node
=> get_standard_option
('pve-node'),
1952 vmid
=> get_standard_option
('pve-vmid'),
1954 description
=> "Feature to check.",
1956 enum
=> [ 'snapshot', 'clone', 'copy' ],
1958 snapname
=> get_standard_option
('pve-snapshot-name', {
1966 hasFeature
=> { type
=> 'boolean' },
1969 items
=> { type
=> 'string' },
1976 my $node = extract_param
($param, 'node');
1978 my $vmid = extract_param
($param, 'vmid');
1980 my $snapname = extract_param
($param, 'snapname');
1982 my $feature = extract_param
($param, 'feature');
1984 my $running = PVE
::QemuServer
::check_running
($vmid);
1986 my $conf = PVE
::QemuServer
::load_config
($vmid);
1989 my $snap = $conf->{snapshots
}->{$snapname};
1990 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1993 my $storecfg = PVE
::Storage
::config
();
1995 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1996 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1999 hasFeature
=> $hasFeature,
2000 nodes
=> [ keys %$nodelist ],
2004 __PACKAGE__-
>register_method({
2006 path
=> '{vmid}/clone',
2010 description
=> "Create a copy of virtual machine/template.",
2012 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2013 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2014 "'Datastore.AllocateSpace' on any used storage.",
2017 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2019 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2020 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2025 additionalProperties
=> 0,
2027 node
=> get_standard_option
('pve-node'),
2028 vmid
=> get_standard_option
('pve-vmid'),
2029 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2032 type
=> 'string', format
=> 'dns-name',
2033 description
=> "Set a name for the new VM.",
2038 description
=> "Description for the new VM.",
2042 type
=> 'string', format
=> 'pve-poolid',
2043 description
=> "Add the new VM to the specified pool.",
2045 snapname
=> get_standard_option
('pve-snapshot-name', {
2049 storage
=> get_standard_option
('pve-storage-id', {
2050 description
=> "Target storage for full clone.",
2055 description
=> "Target format for file storage.",
2059 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2064 description
=> "Create a full copy of all disk. This is always done when " .
2065 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2068 target
=> get_standard_option
('pve-node', {
2069 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2080 my $rpcenv = PVE
::RPCEnvironment
::get
();
2082 my $authuser = $rpcenv->get_user();
2084 my $node = extract_param
($param, 'node');
2086 my $vmid = extract_param
($param, 'vmid');
2088 my $newid = extract_param
($param, 'newid');
2090 my $pool = extract_param
($param, 'pool');
2092 if (defined($pool)) {
2093 $rpcenv->check_pool_exist($pool);
2096 my $snapname = extract_param
($param, 'snapname');
2098 my $storage = extract_param
($param, 'storage');
2100 my $format = extract_param
($param, 'format');
2102 my $target = extract_param
($param, 'target');
2104 my $localnode = PVE
::INotify
::nodename
();
2106 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2108 PVE
::Cluster
::check_node_exists
($target) if $target;
2110 my $storecfg = PVE
::Storage
::config
();
2113 # check if storage is enabled on local node
2114 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2116 # check if storage is available on target node
2117 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2118 # clone only works if target storage is shared
2119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2120 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2124 PVE
::Cluster
::check_cfs_quorum
();
2126 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2128 # exclusive lock if VM is running - else shared lock is enough;
2129 my $shared_lock = $running ?
0 : 1;
2133 # do all tests after lock
2134 # we also try to do all tests before we fork the worker
2136 my $conf = PVE
::QemuServer
::load_config
($vmid);
2138 PVE
::QemuServer
::check_lock
($conf);
2140 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2142 die "unexpected state change\n" if $verify_running != $running;
2144 die "snapshot '$snapname' does not exist\n"
2145 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2147 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2149 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2151 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2153 my $conffile = PVE
::QemuServer
::config_file
($newid);
2155 die "unable to create VM $newid: config file already exists\n"
2158 my $newconf = { lock => 'clone' };
2162 foreach my $opt (keys %$oldconf) {
2163 my $value = $oldconf->{$opt};
2165 # do not copy snapshot related info
2166 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2167 $opt eq 'vmstate' || $opt eq 'snapstate';
2169 # always change MAC! address
2170 if ($opt =~ m/^net(\d+)$/) {
2171 my $net = PVE
::QemuServer
::parse_net
($value);
2172 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2173 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2174 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2175 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2176 die "unable to parse drive options for '$opt'\n" if !$drive;
2177 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2178 $newconf->{$opt} = $value; # simply copy configuration
2180 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2181 die "Full clone feature is not available"
2182 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2185 $drives->{$opt} = $drive;
2186 push @$vollist, $drive->{file
};
2189 # copy everything else
2190 $newconf->{$opt} = $value;
2194 delete $newconf->{template
};
2196 if ($param->{name
}) {
2197 $newconf->{name
} = $param->{name
};
2199 if ($oldconf->{name
}) {
2200 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2202 $newconf->{name
} = "Copy-of-VM-$vmid";
2206 if ($param->{description
}) {
2207 $newconf->{description
} = $param->{description
};
2210 # create empty/temp config - this fails if VM already exists on other node
2211 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2216 my $newvollist = [];
2219 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2221 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2223 foreach my $opt (keys %$drives) {
2224 my $drive = $drives->{$opt};
2226 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2227 $newid, $storage, $format, $drive->{full
}, $newvollist);
2229 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2231 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2234 delete $newconf->{lock};
2235 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2238 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2239 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2241 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2242 die "Failed to move config to node '$target' - rename failed: $!\n"
2243 if !rename($conffile, $newconffile);
2246 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2251 sleep 1; # some storage like rbd need to wait before release volume - really?
2253 foreach my $volid (@$newvollist) {
2254 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2257 die "clone failed: $err";
2263 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2266 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2267 # Aquire exclusive lock lock for $newid
2268 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2273 __PACKAGE__-
>register_method({
2274 name
=> 'move_vm_disk',
2275 path
=> '{vmid}/move_disk',
2279 description
=> "Move volume to different storage.",
2281 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2282 "and 'Datastore.AllocateSpace' permissions on the storage.",
2285 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2286 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2290 additionalProperties
=> 0,
2292 node
=> get_standard_option
('pve-node'),
2293 vmid
=> get_standard_option
('pve-vmid'),
2296 description
=> "The disk you want to move.",
2297 enum
=> [ PVE
::QemuServer
::disknames
() ],
2299 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2302 description
=> "Target Format.",
2303 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2308 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2314 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2322 description
=> "the task ID.",
2327 my $rpcenv = PVE
::RPCEnvironment
::get
();
2329 my $authuser = $rpcenv->get_user();
2331 my $node = extract_param
($param, 'node');
2333 my $vmid = extract_param
($param, 'vmid');
2335 my $digest = extract_param
($param, 'digest');
2337 my $disk = extract_param
($param, 'disk');
2339 my $storeid = extract_param
($param, 'storage');
2341 my $format = extract_param
($param, 'format');
2343 my $storecfg = PVE
::Storage
::config
();
2345 my $updatefn = sub {
2347 my $conf = PVE
::QemuServer
::load_config
($vmid);
2349 die "checksum missmatch (file change by other user?)\n"
2350 if $digest && $digest ne $conf->{digest
};
2352 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2354 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2356 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2358 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2361 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2362 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2366 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2367 (!$format || !$oldfmt || $oldfmt eq $format);
2369 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2371 my $running = PVE
::QemuServer
::check_running
($vmid);
2373 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2377 my $newvollist = [];
2380 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2382 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2383 $vmid, $storeid, $format, 1, $newvollist);
2385 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2387 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2389 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2393 foreach my $volid (@$newvollist) {
2394 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2397 die "storage migration failed: $err";
2400 if ($param->{delete}) {
2401 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2406 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2409 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2412 __PACKAGE__-
>register_method({
2413 name
=> 'migrate_vm',
2414 path
=> '{vmid}/migrate',
2418 description
=> "Migrate virtual machine. Creates a new migration task.",
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2423 additionalProperties
=> 0,
2425 node
=> get_standard_option
('pve-node'),
2426 vmid
=> get_standard_option
('pve-vmid'),
2427 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2430 description
=> "Use online/live migration.",
2435 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2442 description
=> "the task ID.",
2447 my $rpcenv = PVE
::RPCEnvironment
::get
();
2449 my $authuser = $rpcenv->get_user();
2451 my $target = extract_param
($param, 'target');
2453 my $localnode = PVE
::INotify
::nodename
();
2454 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2456 PVE
::Cluster
::check_cfs_quorum
();
2458 PVE
::Cluster
::check_node_exists
($target);
2460 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2462 my $vmid = extract_param
($param, 'vmid');
2464 raise_param_exc
({ force
=> "Only root may use this option." })
2465 if $param->{force
} && $authuser ne 'root@pam';
2468 my $conf = PVE
::QemuServer
::load_config
($vmid);
2470 # try to detect errors early
2472 PVE
::QemuServer
::check_lock
($conf);
2474 if (PVE
::QemuServer
::check_running
($vmid)) {
2475 die "cant migrate running VM without --online\n"
2476 if !$param->{online
};
2479 my $storecfg = PVE
::Storage
::config
();
2480 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2482 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2487 my $service = "pvevm:$vmid";
2489 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2491 print "Executing HA migrate for VM $vmid to node $target\n";
2493 PVE
::Tools
::run_command
($cmd);
2498 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2505 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2508 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2513 __PACKAGE__-
>register_method({
2515 path
=> '{vmid}/monitor',
2519 description
=> "Execute Qemu monitor commands.",
2521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2524 additionalProperties
=> 0,
2526 node
=> get_standard_option
('pve-node'),
2527 vmid
=> get_standard_option
('pve-vmid'),
2530 description
=> "The monitor command.",
2534 returns
=> { type
=> 'string'},
2538 my $vmid = $param->{vmid
};
2540 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2544 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2546 $res = "ERROR: $@" if $@;
2551 __PACKAGE__-
>register_method({
2552 name
=> 'resize_vm',
2553 path
=> '{vmid}/resize',
2557 description
=> "Extend volume size.",
2559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2562 additionalProperties
=> 0,
2564 node
=> get_standard_option
('pve-node'),
2565 vmid
=> get_standard_option
('pve-vmid'),
2566 skiplock
=> get_standard_option
('skiplock'),
2569 description
=> "The disk you want to resize.",
2570 enum
=> [PVE
::QemuServer
::disknames
()],
2574 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2575 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.",
2579 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2585 returns
=> { type
=> 'null'},
2589 my $rpcenv = PVE
::RPCEnvironment
::get
();
2591 my $authuser = $rpcenv->get_user();
2593 my $node = extract_param
($param, 'node');
2595 my $vmid = extract_param
($param, 'vmid');
2597 my $digest = extract_param
($param, 'digest');
2599 my $disk = extract_param
($param, 'disk');
2601 my $sizestr = extract_param
($param, 'size');
2603 my $skiplock = extract_param
($param, 'skiplock');
2604 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2605 if $skiplock && $authuser ne 'root@pam';
2607 my $storecfg = PVE
::Storage
::config
();
2609 my $updatefn = sub {
2611 my $conf = PVE
::QemuServer
::load_config
($vmid);
2613 die "checksum missmatch (file change by other user?)\n"
2614 if $digest && $digest ne $conf->{digest
};
2615 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2617 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2619 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2621 my $volid = $drive->{file
};
2623 die "disk '$disk' has no associated volume\n" if !$volid;
2625 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2627 die "you can't online resize a virtio windows bootdisk\n"
2628 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2630 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2632 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2634 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2636 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2637 my ($ext, $newsize, $unit) = ($1, $2, $4);
2640 $newsize = $newsize * 1024;
2641 } elsif ($unit eq 'M') {
2642 $newsize = $newsize * 1024 * 1024;
2643 } elsif ($unit eq 'G') {
2644 $newsize = $newsize * 1024 * 1024 * 1024;
2645 } elsif ($unit eq 'T') {
2646 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2649 $newsize += $size if $ext;
2650 $newsize = int($newsize);
2652 die "unable to skrink disk size\n" if $newsize < $size;
2654 return if $size == $newsize;
2656 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2658 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2660 $drive->{size
} = $newsize;
2661 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2663 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2666 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2670 __PACKAGE__-
>register_method({
2671 name
=> 'snapshot_list',
2672 path
=> '{vmid}/snapshot',
2674 description
=> "List all snapshots.",
2676 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2679 protected
=> 1, # qemu pid files are only readable by root
2681 additionalProperties
=> 0,
2683 vmid
=> get_standard_option
('pve-vmid'),
2684 node
=> get_standard_option
('pve-node'),
2693 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2698 my $vmid = $param->{vmid
};
2700 my $conf = PVE
::QemuServer
::load_config
($vmid);
2701 my $snaphash = $conf->{snapshots
} || {};
2705 foreach my $name (keys %$snaphash) {
2706 my $d = $snaphash->{$name};
2709 snaptime
=> $d->{snaptime
} || 0,
2710 vmstate
=> $d->{vmstate
} ?
1 : 0,
2711 description
=> $d->{description
} || '',
2713 $item->{parent
} = $d->{parent
} if $d->{parent
};
2714 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2718 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2719 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2720 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2722 push @$res, $current;
2727 __PACKAGE__-
>register_method({
2729 path
=> '{vmid}/snapshot',
2733 description
=> "Snapshot a VM.",
2735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2738 additionalProperties
=> 0,
2740 node
=> get_standard_option
('pve-node'),
2741 vmid
=> get_standard_option
('pve-vmid'),
2742 snapname
=> get_standard_option
('pve-snapshot-name'),
2746 description
=> "Save the vmstate",
2751 description
=> "Freeze the filesystem",
2756 description
=> "A textual description or comment.",
2762 description
=> "the task ID.",
2767 my $rpcenv = PVE
::RPCEnvironment
::get
();
2769 my $authuser = $rpcenv->get_user();
2771 my $node = extract_param
($param, 'node');
2773 my $vmid = extract_param
($param, 'vmid');
2775 my $snapname = extract_param
($param, 'snapname');
2777 die "unable to use snapshot name 'current' (reserved name)\n"
2778 if $snapname eq 'current';
2781 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2782 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2783 $param->{freezefs
}, $param->{description
});
2786 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2789 __PACKAGE__-
>register_method({
2790 name
=> 'snapshot_cmd_idx',
2791 path
=> '{vmid}/snapshot/{snapname}',
2798 additionalProperties
=> 0,
2800 vmid
=> get_standard_option
('pve-vmid'),
2801 node
=> get_standard_option
('pve-node'),
2802 snapname
=> get_standard_option
('pve-snapshot-name'),
2811 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2818 push @$res, { cmd
=> 'rollback' };
2819 push @$res, { cmd
=> 'config' };
2824 __PACKAGE__-
>register_method({
2825 name
=> 'update_snapshot_config',
2826 path
=> '{vmid}/snapshot/{snapname}/config',
2830 description
=> "Update snapshot metadata.",
2832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2835 additionalProperties
=> 0,
2837 node
=> get_standard_option
('pve-node'),
2838 vmid
=> get_standard_option
('pve-vmid'),
2839 snapname
=> get_standard_option
('pve-snapshot-name'),
2843 description
=> "A textual description or comment.",
2847 returns
=> { type
=> 'null' },
2851 my $rpcenv = PVE
::RPCEnvironment
::get
();
2853 my $authuser = $rpcenv->get_user();
2855 my $vmid = extract_param
($param, 'vmid');
2857 my $snapname = extract_param
($param, 'snapname');
2859 return undef if !defined($param->{description
});
2861 my $updatefn = sub {
2863 my $conf = PVE
::QemuServer
::load_config
($vmid);
2865 PVE
::QemuServer
::check_lock
($conf);
2867 my $snap = $conf->{snapshots
}->{$snapname};
2869 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2871 $snap->{description
} = $param->{description
} if defined($param->{description
});
2873 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2876 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2881 __PACKAGE__-
>register_method({
2882 name
=> 'get_snapshot_config',
2883 path
=> '{vmid}/snapshot/{snapname}/config',
2886 description
=> "Get snapshot configuration",
2888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2891 additionalProperties
=> 0,
2893 node
=> get_standard_option
('pve-node'),
2894 vmid
=> get_standard_option
('pve-vmid'),
2895 snapname
=> get_standard_option
('pve-snapshot-name'),
2898 returns
=> { type
=> "object" },
2902 my $rpcenv = PVE
::RPCEnvironment
::get
();
2904 my $authuser = $rpcenv->get_user();
2906 my $vmid = extract_param
($param, 'vmid');
2908 my $snapname = extract_param
($param, 'snapname');
2910 my $conf = PVE
::QemuServer
::load_config
($vmid);
2912 my $snap = $conf->{snapshots
}->{$snapname};
2914 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2919 __PACKAGE__-
>register_method({
2921 path
=> '{vmid}/snapshot/{snapname}/rollback',
2925 description
=> "Rollback VM state to specified snapshot.",
2927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2930 additionalProperties
=> 0,
2932 node
=> get_standard_option
('pve-node'),
2933 vmid
=> get_standard_option
('pve-vmid'),
2934 snapname
=> get_standard_option
('pve-snapshot-name'),
2939 description
=> "the task ID.",
2944 my $rpcenv = PVE
::RPCEnvironment
::get
();
2946 my $authuser = $rpcenv->get_user();
2948 my $node = extract_param
($param, 'node');
2950 my $vmid = extract_param
($param, 'vmid');
2952 my $snapname = extract_param
($param, 'snapname');
2955 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2956 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2959 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2962 __PACKAGE__-
>register_method({
2963 name
=> 'delsnapshot',
2964 path
=> '{vmid}/snapshot/{snapname}',
2968 description
=> "Delete a VM snapshot.",
2970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2973 additionalProperties
=> 0,
2975 node
=> get_standard_option
('pve-node'),
2976 vmid
=> get_standard_option
('pve-vmid'),
2977 snapname
=> get_standard_option
('pve-snapshot-name'),
2981 description
=> "For removal from config file, even if removing disk snapshots fails.",
2987 description
=> "the task ID.",
2992 my $rpcenv = PVE
::RPCEnvironment
::get
();
2994 my $authuser = $rpcenv->get_user();
2996 my $node = extract_param
($param, 'node');
2998 my $vmid = extract_param
($param, 'vmid');
3000 my $snapname = extract_param
($param, 'snapname');
3003 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3004 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3007 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3010 __PACKAGE__-
>register_method({
3012 path
=> '{vmid}/template',
3016 description
=> "Create a Template.",
3018 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3019 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3022 additionalProperties
=> 0,
3024 node
=> get_standard_option
('pve-node'),
3025 vmid
=> get_standard_option
('pve-vmid'),
3029 description
=> "If you want to convert only 1 disk to base image.",
3030 enum
=> [PVE
::QemuServer
::disknames
()],
3035 returns
=> { type
=> 'null'},
3039 my $rpcenv = PVE
::RPCEnvironment
::get
();
3041 my $authuser = $rpcenv->get_user();
3043 my $node = extract_param
($param, 'node');
3045 my $vmid = extract_param
($param, 'vmid');
3047 my $disk = extract_param
($param, 'disk');
3049 my $updatefn = sub {
3051 my $conf = PVE
::QemuServer
::load_config
($vmid);
3053 PVE
::QemuServer
::check_lock
($conf);
3055 die "unable to create template, because VM contains snapshots\n"
3056 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3058 die "you can't convert a template to a template\n"
3059 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3061 die "you can't convert a VM to template if VM is running\n"
3062 if PVE
::QemuServer
::check_running
($vmid);
3065 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3068 $conf->{template
} = 1;
3069 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3071 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3074 PVE
::QemuServer
::lock_config
($vmid, $updatefn);