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 $conf->{$opt} = $param->{$opt};
993 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
997 # allow manual ballooning if shares is set to zero
998 if ($running && defined($param->{balloon
}) &&
999 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1000 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1001 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1009 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1011 if ($background_delay) {
1013 # Note: It would be better to do that in the Event based HTTPServer
1014 # to avoid blocking call to sleep.
1016 my $end_time = time() + $background_delay;
1018 my $task = PVE
::Tools
::upid_decode
($upid);
1021 while (time() < $end_time) {
1022 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1024 sleep(1); # this gets interrupted when child process ends
1028 my $status = PVE
::Tools
::upid_read_status
($upid);
1029 return undef if $status eq 'OK';
1038 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1041 my $vm_config_perm_list = [
1046 'VM.Config.Network',
1048 'VM.Config.Options',
1051 __PACKAGE__-
>register_method({
1052 name
=> 'update_vm_async',
1053 path
=> '{vmid}/config',
1057 description
=> "Set virtual machine options (asynchrounous API).",
1059 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1062 additionalProperties
=> 0,
1063 properties
=> PVE
::QemuServer
::json_config_properties
(
1065 node
=> get_standard_option
('pve-node'),
1066 vmid
=> get_standard_option
('pve-vmid'),
1067 skiplock
=> get_standard_option
('skiplock'),
1069 type
=> 'string', format
=> 'pve-configid-list',
1070 description
=> "A list of settings you want to delete.",
1075 description
=> $opt_force_description,
1077 requires
=> 'delete',
1081 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1085 background_delay
=> {
1087 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1098 code
=> $update_vm_api,
1101 __PACKAGE__-
>register_method({
1102 name
=> 'update_vm',
1103 path
=> '{vmid}/config',
1107 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1109 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1112 additionalProperties
=> 0,
1113 properties
=> PVE
::QemuServer
::json_config_properties
(
1115 node
=> get_standard_option
('pve-node'),
1116 vmid
=> get_standard_option
('pve-vmid'),
1117 skiplock
=> get_standard_option
('skiplock'),
1119 type
=> 'string', format
=> 'pve-configid-list',
1120 description
=> "A list of settings you want to delete.",
1125 description
=> $opt_force_description,
1127 requires
=> 'delete',
1131 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1137 returns
=> { type
=> 'null' },
1140 &$update_vm_api($param, 1);
1146 __PACKAGE__-
>register_method({
1147 name
=> 'destroy_vm',
1152 description
=> "Destroy the vm (also delete all used/owned volumes).",
1154 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1157 additionalProperties
=> 0,
1159 node
=> get_standard_option
('pve-node'),
1160 vmid
=> get_standard_option
('pve-vmid'),
1161 skiplock
=> get_standard_option
('skiplock'),
1170 my $rpcenv = PVE
::RPCEnvironment
::get
();
1172 my $authuser = $rpcenv->get_user();
1174 my $vmid = $param->{vmid
};
1176 my $skiplock = $param->{skiplock
};
1177 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1178 if $skiplock && $authuser ne 'root@pam';
1181 my $conf = PVE
::QemuServer
::load_config
($vmid);
1183 my $storecfg = PVE
::Storage
::config
();
1185 my $delVMfromPoolFn = sub {
1186 my $usercfg = cfs_read_file
("user.cfg");
1187 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1188 if (my $data = $usercfg->{pools
}->{$pool}) {
1189 delete $data->{vms
}->{$vmid};
1190 delete $usercfg->{vms
}->{$vmid};
1191 cfs_write_file
("user.cfg", $usercfg);
1199 syslog
('info', "destroy VM $vmid: $upid\n");
1201 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1203 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1206 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1209 __PACKAGE__-
>register_method({
1211 path
=> '{vmid}/unlink',
1215 description
=> "Unlink/delete disk images.",
1217 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1220 additionalProperties
=> 0,
1222 node
=> get_standard_option
('pve-node'),
1223 vmid
=> get_standard_option
('pve-vmid'),
1225 type
=> 'string', format
=> 'pve-configid-list',
1226 description
=> "A list of disk IDs you want to delete.",
1230 description
=> $opt_force_description,
1235 returns
=> { type
=> 'null'},
1239 $param->{delete} = extract_param
($param, 'idlist');
1241 __PACKAGE__-
>update_vm($param);
1248 __PACKAGE__-
>register_method({
1250 path
=> '{vmid}/vncproxy',
1254 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1256 description
=> "Creates a TCP VNC proxy connections.",
1258 additionalProperties
=> 0,
1260 node
=> get_standard_option
('pve-node'),
1261 vmid
=> get_standard_option
('pve-vmid'),
1265 additionalProperties
=> 0,
1267 user
=> { type
=> 'string' },
1268 ticket
=> { type
=> 'string' },
1269 cert
=> { type
=> 'string' },
1270 port
=> { type
=> 'integer' },
1271 upid
=> { type
=> 'string' },
1277 my $rpcenv = PVE
::RPCEnvironment
::get
();
1279 my $authuser = $rpcenv->get_user();
1281 my $vmid = $param->{vmid
};
1282 my $node = $param->{node
};
1284 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1286 my $authpath = "/vms/$vmid";
1288 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1290 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1293 my $port = PVE
::Tools
::next_vnc_port
();
1298 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1299 $remip = PVE
::Cluster
::remote_node_ip
($node);
1300 # NOTE: kvm VNC traffic is already TLS encrypted
1301 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1309 syslog
('info', "starting vnc proxy $upid\n");
1313 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1315 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1316 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1317 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1318 '-timeout', $timeout, '-authpath', $authpath,
1319 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1322 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1324 my $qmstr = join(' ', @$qmcmd);
1326 # also redirect stderr (else we get RFB protocol errors)
1327 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1330 PVE
::Tools
::run_command
($cmd);
1335 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1337 PVE
::Tools
::wait_for_vnc_port
($port);
1348 __PACKAGE__-
>register_method({
1349 name
=> 'spiceproxy',
1350 path
=> '{vmid}/spiceproxy',
1355 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1357 description
=> "Returns a SPICE configuration to connect to the VM.",
1359 additionalProperties
=> 0,
1361 node
=> get_standard_option
('pve-node'),
1362 vmid
=> get_standard_option
('pve-vmid'),
1364 description
=> "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
1365 type
=> 'string', format
=> 'dns-name',
1371 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1372 additionalProperties
=> 1,
1374 type
=> { type
=> 'string' },
1375 password
=> { type
=> 'string' },
1376 proxy
=> { type
=> 'string' },
1377 host
=> { type
=> 'string' },
1378 'tls-port' => { type
=> 'integer' },
1384 my $rpcenv = PVE
::RPCEnvironment
::get
();
1386 my $authuser = $rpcenv->get_user();
1388 my $vmid = $param->{vmid
};
1389 my $node = $param->{node
};
1390 my $proxy = $param->{proxy
};
1392 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1396 my $port = PVE
::QemuServer
::spice_port
($vmid);
1397 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1398 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1401 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1406 my $filename = "/etc/pve/local/pve-ssl.pem";
1407 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1409 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1410 $cacert =~ s/\n/\\n/g;
1414 title
=> "VM $vmid",
1415 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1416 proxy
=> "http://$proxy:3128",
1417 'tls-port' => $port,
1418 'host-subject' => $subject,
1420 password
=> $ticket,
1421 'delete-this-file' => 1,
1425 __PACKAGE__-
>register_method({
1427 path
=> '{vmid}/status',
1430 description
=> "Directory index",
1435 additionalProperties
=> 0,
1437 node
=> get_standard_option
('pve-node'),
1438 vmid
=> get_standard_option
('pve-vmid'),
1446 subdir
=> { type
=> 'string' },
1449 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1455 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1458 { subdir
=> 'current' },
1459 { subdir
=> 'start' },
1460 { subdir
=> 'stop' },
1466 my $vm_is_ha_managed = sub {
1469 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1470 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1476 __PACKAGE__-
>register_method({
1477 name
=> 'vm_status',
1478 path
=> '{vmid}/status/current',
1481 protected
=> 1, # qemu pid files are only readable by root
1482 description
=> "Get virtual machine status.",
1484 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1487 additionalProperties
=> 0,
1489 node
=> get_standard_option
('pve-node'),
1490 vmid
=> get_standard_option
('pve-vmid'),
1493 returns
=> { type
=> 'object' },
1498 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1500 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1501 my $status = $vmstatus->{$param->{vmid
}};
1503 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1505 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1510 __PACKAGE__-
>register_method({
1512 path
=> '{vmid}/status/start',
1516 description
=> "Start virtual machine.",
1518 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1521 additionalProperties
=> 0,
1523 node
=> get_standard_option
('pve-node'),
1524 vmid
=> get_standard_option
('pve-vmid'),
1525 skiplock
=> get_standard_option
('skiplock'),
1526 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1527 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1528 machine
=> get_standard_option
('pve-qm-machine'),
1537 my $rpcenv = PVE
::RPCEnvironment
::get
();
1539 my $authuser = $rpcenv->get_user();
1541 my $node = extract_param
($param, 'node');
1543 my $vmid = extract_param
($param, 'vmid');
1545 my $machine = extract_param
($param, 'machine');
1547 my $stateuri = extract_param
($param, 'stateuri');
1548 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1549 if $stateuri && $authuser ne 'root@pam';
1551 my $skiplock = extract_param
($param, 'skiplock');
1552 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1553 if $skiplock && $authuser ne 'root@pam';
1555 my $migratedfrom = extract_param
($param, 'migratedfrom');
1556 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1557 if $migratedfrom && $authuser ne 'root@pam';
1559 # read spice ticket from STDIN
1561 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1562 if (defined(my $line = <>)) {
1564 $spice_ticket = $line;
1568 my $storecfg = PVE
::Storage
::config
();
1570 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1571 $rpcenv->{type
} ne 'ha') {
1576 my $service = "pvevm:$vmid";
1578 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1580 print "Executing HA start for VM $vmid\n";
1582 PVE
::Tools
::run_command
($cmd);
1587 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1594 syslog
('info', "start VM $vmid: $upid\n");
1596 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1597 $machine, $spice_ticket);
1602 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1606 __PACKAGE__-
>register_method({
1608 path
=> '{vmid}/status/stop',
1612 description
=> "Stop virtual machine.",
1614 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1617 additionalProperties
=> 0,
1619 node
=> get_standard_option
('pve-node'),
1620 vmid
=> get_standard_option
('pve-vmid'),
1621 skiplock
=> get_standard_option
('skiplock'),
1622 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1624 description
=> "Wait maximal timeout seconds.",
1630 description
=> "Do not decativate storage volumes.",
1643 my $rpcenv = PVE
::RPCEnvironment
::get
();
1645 my $authuser = $rpcenv->get_user();
1647 my $node = extract_param
($param, 'node');
1649 my $vmid = extract_param
($param, 'vmid');
1651 my $skiplock = extract_param
($param, 'skiplock');
1652 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1653 if $skiplock && $authuser ne 'root@pam';
1655 my $keepActive = extract_param
($param, 'keepActive');
1656 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1657 if $keepActive && $authuser ne 'root@pam';
1659 my $migratedfrom = extract_param
($param, 'migratedfrom');
1660 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1661 if $migratedfrom && $authuser ne 'root@pam';
1664 my $storecfg = PVE
::Storage
::config
();
1666 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1671 my $service = "pvevm:$vmid";
1673 my $cmd = ['clusvcadm', '-d', $service];
1675 print "Executing HA stop for VM $vmid\n";
1677 PVE
::Tools
::run_command
($cmd);
1682 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1688 syslog
('info', "stop VM $vmid: $upid\n");
1690 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1691 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1696 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1700 __PACKAGE__-
>register_method({
1702 path
=> '{vmid}/status/reset',
1706 description
=> "Reset virtual machine.",
1708 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1711 additionalProperties
=> 0,
1713 node
=> get_standard_option
('pve-node'),
1714 vmid
=> get_standard_option
('pve-vmid'),
1715 skiplock
=> get_standard_option
('skiplock'),
1724 my $rpcenv = PVE
::RPCEnvironment
::get
();
1726 my $authuser = $rpcenv->get_user();
1728 my $node = extract_param
($param, 'node');
1730 my $vmid = extract_param
($param, 'vmid');
1732 my $skiplock = extract_param
($param, 'skiplock');
1733 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1734 if $skiplock && $authuser ne 'root@pam';
1736 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1741 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1746 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1749 __PACKAGE__-
>register_method({
1750 name
=> 'vm_shutdown',
1751 path
=> '{vmid}/status/shutdown',
1755 description
=> "Shutdown virtual machine.",
1757 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1760 additionalProperties
=> 0,
1762 node
=> get_standard_option
('pve-node'),
1763 vmid
=> get_standard_option
('pve-vmid'),
1764 skiplock
=> get_standard_option
('skiplock'),
1766 description
=> "Wait maximal timeout seconds.",
1772 description
=> "Make sure the VM stops.",
1778 description
=> "Do not decativate storage volumes.",
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $node = extract_param
($param, 'node');
1797 my $vmid = extract_param
($param, 'vmid');
1799 my $skiplock = extract_param
($param, 'skiplock');
1800 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1801 if $skiplock && $authuser ne 'root@pam';
1803 my $keepActive = extract_param
($param, 'keepActive');
1804 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1805 if $keepActive && $authuser ne 'root@pam';
1807 my $storecfg = PVE
::Storage
::config
();
1812 syslog
('info', "shutdown VM $vmid: $upid\n");
1814 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1815 1, $param->{forceStop
}, $keepActive);
1820 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1823 __PACKAGE__-
>register_method({
1824 name
=> 'vm_suspend',
1825 path
=> '{vmid}/status/suspend',
1829 description
=> "Suspend virtual machine.",
1831 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid'),
1838 skiplock
=> get_standard_option
('skiplock'),
1847 my $rpcenv = PVE
::RPCEnvironment
::get
();
1849 my $authuser = $rpcenv->get_user();
1851 my $node = extract_param
($param, 'node');
1853 my $vmid = extract_param
($param, 'vmid');
1855 my $skiplock = extract_param
($param, 'skiplock');
1856 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1857 if $skiplock && $authuser ne 'root@pam';
1859 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1864 syslog
('info', "suspend VM $vmid: $upid\n");
1866 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1871 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1874 __PACKAGE__-
>register_method({
1875 name
=> 'vm_resume',
1876 path
=> '{vmid}/status/resume',
1880 description
=> "Resume virtual machine.",
1882 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1885 additionalProperties
=> 0,
1887 node
=> get_standard_option
('pve-node'),
1888 vmid
=> get_standard_option
('pve-vmid'),
1889 skiplock
=> get_standard_option
('skiplock'),
1898 my $rpcenv = PVE
::RPCEnvironment
::get
();
1900 my $authuser = $rpcenv->get_user();
1902 my $node = extract_param
($param, 'node');
1904 my $vmid = extract_param
($param, 'vmid');
1906 my $skiplock = extract_param
($param, 'skiplock');
1907 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1908 if $skiplock && $authuser ne 'root@pam';
1910 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1915 syslog
('info', "resume VM $vmid: $upid\n");
1917 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1922 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_sendkey',
1927 path
=> '{vmid}/sendkey',
1931 description
=> "Send key event to virtual machine.",
1933 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1936 additionalProperties
=> 0,
1938 node
=> get_standard_option
('pve-node'),
1939 vmid
=> get_standard_option
('pve-vmid'),
1940 skiplock
=> get_standard_option
('skiplock'),
1942 description
=> "The key (qemu monitor encoding).",
1947 returns
=> { type
=> 'null'},
1951 my $rpcenv = PVE
::RPCEnvironment
::get
();
1953 my $authuser = $rpcenv->get_user();
1955 my $node = extract_param
($param, 'node');
1957 my $vmid = extract_param
($param, 'vmid');
1959 my $skiplock = extract_param
($param, 'skiplock');
1960 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1961 if $skiplock && $authuser ne 'root@pam';
1963 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1968 __PACKAGE__-
>register_method({
1969 name
=> 'vm_feature',
1970 path
=> '{vmid}/feature',
1974 description
=> "Check if feature for virtual machine is available.",
1976 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1979 additionalProperties
=> 0,
1981 node
=> get_standard_option
('pve-node'),
1982 vmid
=> get_standard_option
('pve-vmid'),
1984 description
=> "Feature to check.",
1986 enum
=> [ 'snapshot', 'clone', 'copy' ],
1988 snapname
=> get_standard_option
('pve-snapshot-name', {
1996 hasFeature
=> { type
=> 'boolean' },
1999 items
=> { type
=> 'string' },
2006 my $node = extract_param
($param, 'node');
2008 my $vmid = extract_param
($param, 'vmid');
2010 my $snapname = extract_param
($param, 'snapname');
2012 my $feature = extract_param
($param, 'feature');
2014 my $running = PVE
::QemuServer
::check_running
($vmid);
2016 my $conf = PVE
::QemuServer
::load_config
($vmid);
2019 my $snap = $conf->{snapshots
}->{$snapname};
2020 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2023 my $storecfg = PVE
::Storage
::config
();
2025 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2026 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2029 hasFeature
=> $hasFeature,
2030 nodes
=> [ keys %$nodelist ],
2034 __PACKAGE__-
>register_method({
2036 path
=> '{vmid}/clone',
2040 description
=> "Create a copy of virtual machine/template.",
2042 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2043 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2044 "'Datastore.AllocateSpace' on any used storage.",
2047 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2049 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2050 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2055 additionalProperties
=> 0,
2057 node
=> get_standard_option
('pve-node'),
2058 vmid
=> get_standard_option
('pve-vmid'),
2059 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2062 type
=> 'string', format
=> 'dns-name',
2063 description
=> "Set a name for the new VM.",
2068 description
=> "Description for the new VM.",
2072 type
=> 'string', format
=> 'pve-poolid',
2073 description
=> "Add the new VM to the specified pool.",
2075 snapname
=> get_standard_option
('pve-snapshot-name', {
2079 storage
=> get_standard_option
('pve-storage-id', {
2080 description
=> "Target storage for full clone.",
2085 description
=> "Target format for file storage.",
2089 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2094 description
=> "Create a full copy of all disk. This is always done when " .
2095 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2098 target
=> get_standard_option
('pve-node', {
2099 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $newid = extract_param
($param, 'newid');
2120 my $pool = extract_param
($param, 'pool');
2122 if (defined($pool)) {
2123 $rpcenv->check_pool_exist($pool);
2126 my $snapname = extract_param
($param, 'snapname');
2128 my $storage = extract_param
($param, 'storage');
2130 my $format = extract_param
($param, 'format');
2132 my $target = extract_param
($param, 'target');
2134 my $localnode = PVE
::INotify
::nodename
();
2136 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2138 PVE
::Cluster
::check_node_exists
($target) if $target;
2140 my $storecfg = PVE
::Storage
::config
();
2143 # check if storage is enabled on local node
2144 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2146 # check if storage is available on target node
2147 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2148 # clone only works if target storage is shared
2149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2150 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2154 PVE
::Cluster
::check_cfs_quorum
();
2156 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2158 # exclusive lock if VM is running - else shared lock is enough;
2159 my $shared_lock = $running ?
0 : 1;
2163 # do all tests after lock
2164 # we also try to do all tests before we fork the worker
2166 my $conf = PVE
::QemuServer
::load_config
($vmid);
2168 PVE
::QemuServer
::check_lock
($conf);
2170 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2172 die "unexpected state change\n" if $verify_running != $running;
2174 die "snapshot '$snapname' does not exist\n"
2175 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2177 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2179 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2181 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2183 my $conffile = PVE
::QemuServer
::config_file
($newid);
2185 die "unable to create VM $newid: config file already exists\n"
2188 my $newconf = { lock => 'clone' };
2192 foreach my $opt (keys %$oldconf) {
2193 my $value = $oldconf->{$opt};
2195 # do not copy snapshot related info
2196 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2197 $opt eq 'vmstate' || $opt eq 'snapstate';
2199 # always change MAC! address
2200 if ($opt =~ m/^net(\d+)$/) {
2201 my $net = PVE
::QemuServer
::parse_net
($value);
2202 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2203 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2204 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2205 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2206 die "unable to parse drive options for '$opt'\n" if !$drive;
2207 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2208 $newconf->{$opt} = $value; # simply copy configuration
2210 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2211 die "Full clone feature is not available"
2212 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2215 $drives->{$opt} = $drive;
2216 push @$vollist, $drive->{file
};
2219 # copy everything else
2220 $newconf->{$opt} = $value;
2224 delete $newconf->{template
};
2226 if ($param->{name
}) {
2227 $newconf->{name
} = $param->{name
};
2229 if ($oldconf->{name
}) {
2230 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2232 $newconf->{name
} = "Copy-of-VM-$vmid";
2236 if ($param->{description
}) {
2237 $newconf->{description
} = $param->{description
};
2240 # create empty/temp config - this fails if VM already exists on other node
2241 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2246 my $newvollist = [];
2249 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2251 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2253 foreach my $opt (keys %$drives) {
2254 my $drive = $drives->{$opt};
2256 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2257 $newid, $storage, $format, $drive->{full
}, $newvollist);
2259 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2261 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2264 delete $newconf->{lock};
2265 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2268 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2269 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2271 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2272 die "Failed to move config to node '$target' - rename failed: $!\n"
2273 if !rename($conffile, $newconffile);
2276 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2281 sleep 1; # some storage like rbd need to wait before release volume - really?
2283 foreach my $volid (@$newvollist) {
2284 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2287 die "clone failed: $err";
2293 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2296 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2297 # Aquire exclusive lock lock for $newid
2298 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2303 __PACKAGE__-
>register_method({
2304 name
=> 'move_vm_disk',
2305 path
=> '{vmid}/move_disk',
2309 description
=> "Move volume to different storage.",
2311 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2312 "and 'Datastore.AllocateSpace' permissions on the storage.",
2315 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2316 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2320 additionalProperties
=> 0,
2322 node
=> get_standard_option
('pve-node'),
2323 vmid
=> get_standard_option
('pve-vmid'),
2326 description
=> "The disk you want to move.",
2327 enum
=> [ PVE
::QemuServer
::disknames
() ],
2329 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2332 description
=> "Target Format.",
2333 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2338 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2344 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2352 description
=> "the task ID.",
2357 my $rpcenv = PVE
::RPCEnvironment
::get
();
2359 my $authuser = $rpcenv->get_user();
2361 my $node = extract_param
($param, 'node');
2363 my $vmid = extract_param
($param, 'vmid');
2365 my $digest = extract_param
($param, 'digest');
2367 my $disk = extract_param
($param, 'disk');
2369 my $storeid = extract_param
($param, 'storage');
2371 my $format = extract_param
($param, 'format');
2373 my $storecfg = PVE
::Storage
::config
();
2375 my $updatefn = sub {
2377 my $conf = PVE
::QemuServer
::load_config
($vmid);
2379 die "checksum missmatch (file change by other user?)\n"
2380 if $digest && $digest ne $conf->{digest
};
2382 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2384 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2386 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2388 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2391 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2392 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2396 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2397 (!$format || !$oldfmt || $oldfmt eq $format);
2399 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2401 my $running = PVE
::QemuServer
::check_running
($vmid);
2403 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2407 my $newvollist = [];
2410 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2412 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2413 $vmid, $storeid, $format, 1, $newvollist);
2415 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2417 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2419 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2423 foreach my $volid (@$newvollist) {
2424 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2427 die "storage migration failed: $err";
2430 if ($param->{delete}) {
2431 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2436 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2439 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2442 __PACKAGE__-
>register_method({
2443 name
=> 'migrate_vm',
2444 path
=> '{vmid}/migrate',
2448 description
=> "Migrate virtual machine. Creates a new migration task.",
2450 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2453 additionalProperties
=> 0,
2455 node
=> get_standard_option
('pve-node'),
2456 vmid
=> get_standard_option
('pve-vmid'),
2457 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2460 description
=> "Use online/live migration.",
2465 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2472 description
=> "the task ID.",
2477 my $rpcenv = PVE
::RPCEnvironment
::get
();
2479 my $authuser = $rpcenv->get_user();
2481 my $target = extract_param
($param, 'target');
2483 my $localnode = PVE
::INotify
::nodename
();
2484 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2486 PVE
::Cluster
::check_cfs_quorum
();
2488 PVE
::Cluster
::check_node_exists
($target);
2490 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2492 my $vmid = extract_param
($param, 'vmid');
2494 raise_param_exc
({ force
=> "Only root may use this option." })
2495 if $param->{force
} && $authuser ne 'root@pam';
2498 my $conf = PVE
::QemuServer
::load_config
($vmid);
2500 # try to detect errors early
2502 PVE
::QemuServer
::check_lock
($conf);
2504 if (PVE
::QemuServer
::check_running
($vmid)) {
2505 die "cant migrate running VM without --online\n"
2506 if !$param->{online
};
2509 my $storecfg = PVE
::Storage
::config
();
2510 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2512 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2517 my $service = "pvevm:$vmid";
2519 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2521 print "Executing HA migrate for VM $vmid to node $target\n";
2523 PVE
::Tools
::run_command
($cmd);
2528 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2535 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2538 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2543 __PACKAGE__-
>register_method({
2545 path
=> '{vmid}/monitor',
2549 description
=> "Execute Qemu monitor commands.",
2551 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2554 additionalProperties
=> 0,
2556 node
=> get_standard_option
('pve-node'),
2557 vmid
=> get_standard_option
('pve-vmid'),
2560 description
=> "The monitor command.",
2564 returns
=> { type
=> 'string'},
2568 my $vmid = $param->{vmid
};
2570 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2574 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2576 $res = "ERROR: $@" if $@;
2581 __PACKAGE__-
>register_method({
2582 name
=> 'resize_vm',
2583 path
=> '{vmid}/resize',
2587 description
=> "Extend volume size.",
2589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2592 additionalProperties
=> 0,
2594 node
=> get_standard_option
('pve-node'),
2595 vmid
=> get_standard_option
('pve-vmid'),
2596 skiplock
=> get_standard_option
('skiplock'),
2599 description
=> "The disk you want to resize.",
2600 enum
=> [PVE
::QemuServer
::disknames
()],
2604 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2605 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.",
2609 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2615 returns
=> { type
=> 'null'},
2619 my $rpcenv = PVE
::RPCEnvironment
::get
();
2621 my $authuser = $rpcenv->get_user();
2623 my $node = extract_param
($param, 'node');
2625 my $vmid = extract_param
($param, 'vmid');
2627 my $digest = extract_param
($param, 'digest');
2629 my $disk = extract_param
($param, 'disk');
2631 my $sizestr = extract_param
($param, 'size');
2633 my $skiplock = extract_param
($param, 'skiplock');
2634 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2635 if $skiplock && $authuser ne 'root@pam';
2637 my $storecfg = PVE
::Storage
::config
();
2639 my $updatefn = sub {
2641 my $conf = PVE
::QemuServer
::load_config
($vmid);
2643 die "checksum missmatch (file change by other user?)\n"
2644 if $digest && $digest ne $conf->{digest
};
2645 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2647 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2649 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2651 my $volid = $drive->{file
};
2653 die "disk '$disk' has no associated volume\n" if !$volid;
2655 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2657 die "you can't online resize a virtio windows bootdisk\n"
2658 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2660 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2662 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2664 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2666 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2667 my ($ext, $newsize, $unit) = ($1, $2, $4);
2670 $newsize = $newsize * 1024;
2671 } elsif ($unit eq 'M') {
2672 $newsize = $newsize * 1024 * 1024;
2673 } elsif ($unit eq 'G') {
2674 $newsize = $newsize * 1024 * 1024 * 1024;
2675 } elsif ($unit eq 'T') {
2676 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2679 $newsize += $size if $ext;
2680 $newsize = int($newsize);
2682 die "unable to skrink disk size\n" if $newsize < $size;
2684 return if $size == $newsize;
2686 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2688 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2690 $drive->{size
} = $newsize;
2691 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2693 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2696 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2700 __PACKAGE__-
>register_method({
2701 name
=> 'snapshot_list',
2702 path
=> '{vmid}/snapshot',
2704 description
=> "List all snapshots.",
2706 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2709 protected
=> 1, # qemu pid files are only readable by root
2711 additionalProperties
=> 0,
2713 vmid
=> get_standard_option
('pve-vmid'),
2714 node
=> get_standard_option
('pve-node'),
2723 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2728 my $vmid = $param->{vmid
};
2730 my $conf = PVE
::QemuServer
::load_config
($vmid);
2731 my $snaphash = $conf->{snapshots
} || {};
2735 foreach my $name (keys %$snaphash) {
2736 my $d = $snaphash->{$name};
2739 snaptime
=> $d->{snaptime
} || 0,
2740 vmstate
=> $d->{vmstate
} ?
1 : 0,
2741 description
=> $d->{description
} || '',
2743 $item->{parent
} = $d->{parent
} if $d->{parent
};
2744 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2748 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2749 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2750 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2752 push @$res, $current;
2757 __PACKAGE__-
>register_method({
2759 path
=> '{vmid}/snapshot',
2763 description
=> "Snapshot a VM.",
2765 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2768 additionalProperties
=> 0,
2770 node
=> get_standard_option
('pve-node'),
2771 vmid
=> get_standard_option
('pve-vmid'),
2772 snapname
=> get_standard_option
('pve-snapshot-name'),
2776 description
=> "Save the vmstate",
2781 description
=> "Freeze the filesystem",
2786 description
=> "A textual description or comment.",
2792 description
=> "the task ID.",
2797 my $rpcenv = PVE
::RPCEnvironment
::get
();
2799 my $authuser = $rpcenv->get_user();
2801 my $node = extract_param
($param, 'node');
2803 my $vmid = extract_param
($param, 'vmid');
2805 my $snapname = extract_param
($param, 'snapname');
2807 die "unable to use snapshot name 'current' (reserved name)\n"
2808 if $snapname eq 'current';
2811 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2812 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2813 $param->{freezefs
}, $param->{description
});
2816 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2819 __PACKAGE__-
>register_method({
2820 name
=> 'snapshot_cmd_idx',
2821 path
=> '{vmid}/snapshot/{snapname}',
2828 additionalProperties
=> 0,
2830 vmid
=> get_standard_option
('pve-vmid'),
2831 node
=> get_standard_option
('pve-node'),
2832 snapname
=> get_standard_option
('pve-snapshot-name'),
2841 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2848 push @$res, { cmd
=> 'rollback' };
2849 push @$res, { cmd
=> 'config' };
2854 __PACKAGE__-
>register_method({
2855 name
=> 'update_snapshot_config',
2856 path
=> '{vmid}/snapshot/{snapname}/config',
2860 description
=> "Update snapshot metadata.",
2862 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2865 additionalProperties
=> 0,
2867 node
=> get_standard_option
('pve-node'),
2868 vmid
=> get_standard_option
('pve-vmid'),
2869 snapname
=> get_standard_option
('pve-snapshot-name'),
2873 description
=> "A textual description or comment.",
2877 returns
=> { type
=> 'null' },
2881 my $rpcenv = PVE
::RPCEnvironment
::get
();
2883 my $authuser = $rpcenv->get_user();
2885 my $vmid = extract_param
($param, 'vmid');
2887 my $snapname = extract_param
($param, 'snapname');
2889 return undef if !defined($param->{description
});
2891 my $updatefn = sub {
2893 my $conf = PVE
::QemuServer
::load_config
($vmid);
2895 PVE
::QemuServer
::check_lock
($conf);
2897 my $snap = $conf->{snapshots
}->{$snapname};
2899 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2901 $snap->{description
} = $param->{description
} if defined($param->{description
});
2903 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2906 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2911 __PACKAGE__-
>register_method({
2912 name
=> 'get_snapshot_config',
2913 path
=> '{vmid}/snapshot/{snapname}/config',
2916 description
=> "Get snapshot configuration",
2918 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2921 additionalProperties
=> 0,
2923 node
=> get_standard_option
('pve-node'),
2924 vmid
=> get_standard_option
('pve-vmid'),
2925 snapname
=> get_standard_option
('pve-snapshot-name'),
2928 returns
=> { type
=> "object" },
2932 my $rpcenv = PVE
::RPCEnvironment
::get
();
2934 my $authuser = $rpcenv->get_user();
2936 my $vmid = extract_param
($param, 'vmid');
2938 my $snapname = extract_param
($param, 'snapname');
2940 my $conf = PVE
::QemuServer
::load_config
($vmid);
2942 my $snap = $conf->{snapshots
}->{$snapname};
2944 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2949 __PACKAGE__-
>register_method({
2951 path
=> '{vmid}/snapshot/{snapname}/rollback',
2955 description
=> "Rollback VM state to specified snapshot.",
2957 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2960 additionalProperties
=> 0,
2962 node
=> get_standard_option
('pve-node'),
2963 vmid
=> get_standard_option
('pve-vmid'),
2964 snapname
=> get_standard_option
('pve-snapshot-name'),
2969 description
=> "the task ID.",
2974 my $rpcenv = PVE
::RPCEnvironment
::get
();
2976 my $authuser = $rpcenv->get_user();
2978 my $node = extract_param
($param, 'node');
2980 my $vmid = extract_param
($param, 'vmid');
2982 my $snapname = extract_param
($param, 'snapname');
2985 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2986 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2989 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2992 __PACKAGE__-
>register_method({
2993 name
=> 'delsnapshot',
2994 path
=> '{vmid}/snapshot/{snapname}',
2998 description
=> "Delete a VM snapshot.",
3000 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3003 additionalProperties
=> 0,
3005 node
=> get_standard_option
('pve-node'),
3006 vmid
=> get_standard_option
('pve-vmid'),
3007 snapname
=> get_standard_option
('pve-snapshot-name'),
3011 description
=> "For removal from config file, even if removing disk snapshots fails.",
3017 description
=> "the task ID.",
3022 my $rpcenv = PVE
::RPCEnvironment
::get
();
3024 my $authuser = $rpcenv->get_user();
3026 my $node = extract_param
($param, 'node');
3028 my $vmid = extract_param
($param, 'vmid');
3030 my $snapname = extract_param
($param, 'snapname');
3033 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3034 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3037 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3040 __PACKAGE__-
>register_method({
3042 path
=> '{vmid}/template',
3046 description
=> "Create a Template.",
3048 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3049 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3052 additionalProperties
=> 0,
3054 node
=> get_standard_option
('pve-node'),
3055 vmid
=> get_standard_option
('pve-vmid'),
3059 description
=> "If you want to convert only 1 disk to base image.",
3060 enum
=> [PVE
::QemuServer
::disknames
()],
3065 returns
=> { type
=> 'null'},
3069 my $rpcenv = PVE
::RPCEnvironment
::get
();
3071 my $authuser = $rpcenv->get_user();
3073 my $node = extract_param
($param, 'node');
3075 my $vmid = extract_param
($param, 'vmid');
3077 my $disk = extract_param
($param, 'disk');
3079 my $updatefn = sub {
3081 my $conf = PVE
::QemuServer
::load_config
($vmid);
3083 PVE
::QemuServer
::check_lock
($conf);
3085 die "unable to create template, because VM contains snapshots\n"
3086 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3088 die "you can't convert a template to a template\n"
3089 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3091 die "you can't convert a VM to template if VM is running\n"
3092 if PVE
::QemuServer
::check_running
($vmid);
3095 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3098 $conf->{template
} = 1;
3099 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3101 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3104 PVE
::QemuServer
::lock_config
($vmid, $updatefn);