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'),
1363 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1366 returns
=> get_standard_option
('remote-viewer-config'),
1370 my $rpcenv = PVE
::RPCEnvironment
::get
();
1372 my $authuser = $rpcenv->get_user();
1374 my $vmid = $param->{vmid
};
1375 my $node = $param->{node
};
1376 my $proxy = $param->{proxy
};
1378 my $title = "VM $vmid";
1380 my $port = PVE
::QemuServer
::spice_port
($vmid);
1382 my ($ticket, undef, $remote_viewer_config) =
1383 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1385 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1386 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1388 return $remote_viewer_config;
1391 __PACKAGE__-
>register_method({
1393 path
=> '{vmid}/status',
1396 description
=> "Directory index",
1401 additionalProperties
=> 0,
1403 node
=> get_standard_option
('pve-node'),
1404 vmid
=> get_standard_option
('pve-vmid'),
1412 subdir
=> { type
=> 'string' },
1415 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1421 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1424 { subdir
=> 'current' },
1425 { subdir
=> 'start' },
1426 { subdir
=> 'stop' },
1432 my $vm_is_ha_managed = sub {
1435 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1436 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1442 __PACKAGE__-
>register_method({
1443 name
=> 'vm_status',
1444 path
=> '{vmid}/status/current',
1447 protected
=> 1, # qemu pid files are only readable by root
1448 description
=> "Get virtual machine status.",
1450 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1453 additionalProperties
=> 0,
1455 node
=> get_standard_option
('pve-node'),
1456 vmid
=> get_standard_option
('pve-vmid'),
1459 returns
=> { type
=> 'object' },
1464 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1466 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1467 my $status = $vmstatus->{$param->{vmid
}};
1469 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1471 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1476 __PACKAGE__-
>register_method({
1478 path
=> '{vmid}/status/start',
1482 description
=> "Start virtual machine.",
1484 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1487 additionalProperties
=> 0,
1489 node
=> get_standard_option
('pve-node'),
1490 vmid
=> get_standard_option
('pve-vmid'),
1491 skiplock
=> get_standard_option
('skiplock'),
1492 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1493 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1494 machine
=> get_standard_option
('pve-qm-machine'),
1503 my $rpcenv = PVE
::RPCEnvironment
::get
();
1505 my $authuser = $rpcenv->get_user();
1507 my $node = extract_param
($param, 'node');
1509 my $vmid = extract_param
($param, 'vmid');
1511 my $machine = extract_param
($param, 'machine');
1513 my $stateuri = extract_param
($param, 'stateuri');
1514 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1515 if $stateuri && $authuser ne 'root@pam';
1517 my $skiplock = extract_param
($param, 'skiplock');
1518 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1519 if $skiplock && $authuser ne 'root@pam';
1521 my $migratedfrom = extract_param
($param, 'migratedfrom');
1522 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1523 if $migratedfrom && $authuser ne 'root@pam';
1525 # read spice ticket from STDIN
1527 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1528 if (defined(my $line = <>)) {
1530 $spice_ticket = $line;
1534 my $storecfg = PVE
::Storage
::config
();
1536 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1537 $rpcenv->{type
} ne 'ha') {
1542 my $service = "pvevm:$vmid";
1544 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1546 print "Executing HA start for VM $vmid\n";
1548 PVE
::Tools
::run_command
($cmd);
1553 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1560 syslog
('info', "start VM $vmid: $upid\n");
1562 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1563 $machine, $spice_ticket);
1568 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1572 __PACKAGE__-
>register_method({
1574 path
=> '{vmid}/status/stop',
1578 description
=> "Stop virtual machine.",
1580 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1583 additionalProperties
=> 0,
1585 node
=> get_standard_option
('pve-node'),
1586 vmid
=> get_standard_option
('pve-vmid'),
1587 skiplock
=> get_standard_option
('skiplock'),
1588 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1590 description
=> "Wait maximal timeout seconds.",
1596 description
=> "Do not decativate storage volumes.",
1609 my $rpcenv = PVE
::RPCEnvironment
::get
();
1611 my $authuser = $rpcenv->get_user();
1613 my $node = extract_param
($param, 'node');
1615 my $vmid = extract_param
($param, 'vmid');
1617 my $skiplock = extract_param
($param, 'skiplock');
1618 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1619 if $skiplock && $authuser ne 'root@pam';
1621 my $keepActive = extract_param
($param, 'keepActive');
1622 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1623 if $keepActive && $authuser ne 'root@pam';
1625 my $migratedfrom = extract_param
($param, 'migratedfrom');
1626 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1627 if $migratedfrom && $authuser ne 'root@pam';
1630 my $storecfg = PVE
::Storage
::config
();
1632 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1637 my $service = "pvevm:$vmid";
1639 my $cmd = ['clusvcadm', '-d', $service];
1641 print "Executing HA stop for VM $vmid\n";
1643 PVE
::Tools
::run_command
($cmd);
1648 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1654 syslog
('info', "stop VM $vmid: $upid\n");
1656 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1657 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1662 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1666 __PACKAGE__-
>register_method({
1668 path
=> '{vmid}/status/reset',
1672 description
=> "Reset virtual machine.",
1674 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1677 additionalProperties
=> 0,
1679 node
=> get_standard_option
('pve-node'),
1680 vmid
=> get_standard_option
('pve-vmid'),
1681 skiplock
=> get_standard_option
('skiplock'),
1690 my $rpcenv = PVE
::RPCEnvironment
::get
();
1692 my $authuser = $rpcenv->get_user();
1694 my $node = extract_param
($param, 'node');
1696 my $vmid = extract_param
($param, 'vmid');
1698 my $skiplock = extract_param
($param, 'skiplock');
1699 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1700 if $skiplock && $authuser ne 'root@pam';
1702 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1707 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1712 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1715 __PACKAGE__-
>register_method({
1716 name
=> 'vm_shutdown',
1717 path
=> '{vmid}/status/shutdown',
1721 description
=> "Shutdown virtual machine.",
1723 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1726 additionalProperties
=> 0,
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid'),
1730 skiplock
=> get_standard_option
('skiplock'),
1732 description
=> "Wait maximal timeout seconds.",
1738 description
=> "Make sure the VM stops.",
1744 description
=> "Do not decativate storage volumes.",
1757 my $rpcenv = PVE
::RPCEnvironment
::get
();
1759 my $authuser = $rpcenv->get_user();
1761 my $node = extract_param
($param, 'node');
1763 my $vmid = extract_param
($param, 'vmid');
1765 my $skiplock = extract_param
($param, 'skiplock');
1766 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1767 if $skiplock && $authuser ne 'root@pam';
1769 my $keepActive = extract_param
($param, 'keepActive');
1770 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1771 if $keepActive && $authuser ne 'root@pam';
1773 my $storecfg = PVE
::Storage
::config
();
1778 syslog
('info', "shutdown VM $vmid: $upid\n");
1780 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1781 1, $param->{forceStop
}, $keepActive);
1786 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1789 __PACKAGE__-
>register_method({
1790 name
=> 'vm_suspend',
1791 path
=> '{vmid}/status/suspend',
1795 description
=> "Suspend virtual machine.",
1797 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1800 additionalProperties
=> 0,
1802 node
=> get_standard_option
('pve-node'),
1803 vmid
=> get_standard_option
('pve-vmid'),
1804 skiplock
=> get_standard_option
('skiplock'),
1813 my $rpcenv = PVE
::RPCEnvironment
::get
();
1815 my $authuser = $rpcenv->get_user();
1817 my $node = extract_param
($param, 'node');
1819 my $vmid = extract_param
($param, 'vmid');
1821 my $skiplock = extract_param
($param, 'skiplock');
1822 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1823 if $skiplock && $authuser ne 'root@pam';
1825 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1830 syslog
('info', "suspend VM $vmid: $upid\n");
1832 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1837 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1840 __PACKAGE__-
>register_method({
1841 name
=> 'vm_resume',
1842 path
=> '{vmid}/status/resume',
1846 description
=> "Resume virtual machine.",
1848 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1851 additionalProperties
=> 0,
1853 node
=> get_standard_option
('pve-node'),
1854 vmid
=> get_standard_option
('pve-vmid'),
1855 skiplock
=> get_standard_option
('skiplock'),
1864 my $rpcenv = PVE
::RPCEnvironment
::get
();
1866 my $authuser = $rpcenv->get_user();
1868 my $node = extract_param
($param, 'node');
1870 my $vmid = extract_param
($param, 'vmid');
1872 my $skiplock = extract_param
($param, 'skiplock');
1873 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1874 if $skiplock && $authuser ne 'root@pam';
1876 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1881 syslog
('info', "resume VM $vmid: $upid\n");
1883 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1888 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1891 __PACKAGE__-
>register_method({
1892 name
=> 'vm_sendkey',
1893 path
=> '{vmid}/sendkey',
1897 description
=> "Send key event to virtual machine.",
1899 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid'),
1906 skiplock
=> get_standard_option
('skiplock'),
1908 description
=> "The key (qemu monitor encoding).",
1913 returns
=> { type
=> 'null'},
1917 my $rpcenv = PVE
::RPCEnvironment
::get
();
1919 my $authuser = $rpcenv->get_user();
1921 my $node = extract_param
($param, 'node');
1923 my $vmid = extract_param
($param, 'vmid');
1925 my $skiplock = extract_param
($param, 'skiplock');
1926 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1927 if $skiplock && $authuser ne 'root@pam';
1929 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1934 __PACKAGE__-
>register_method({
1935 name
=> 'vm_feature',
1936 path
=> '{vmid}/feature',
1940 description
=> "Check if feature for virtual machine is available.",
1942 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1945 additionalProperties
=> 0,
1947 node
=> get_standard_option
('pve-node'),
1948 vmid
=> get_standard_option
('pve-vmid'),
1950 description
=> "Feature to check.",
1952 enum
=> [ 'snapshot', 'clone', 'copy' ],
1954 snapname
=> get_standard_option
('pve-snapshot-name', {
1962 hasFeature
=> { type
=> 'boolean' },
1965 items
=> { type
=> 'string' },
1972 my $node = extract_param
($param, 'node');
1974 my $vmid = extract_param
($param, 'vmid');
1976 my $snapname = extract_param
($param, 'snapname');
1978 my $feature = extract_param
($param, 'feature');
1980 my $running = PVE
::QemuServer
::check_running
($vmid);
1982 my $conf = PVE
::QemuServer
::load_config
($vmid);
1985 my $snap = $conf->{snapshots
}->{$snapname};
1986 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1989 my $storecfg = PVE
::Storage
::config
();
1991 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1992 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1995 hasFeature
=> $hasFeature,
1996 nodes
=> [ keys %$nodelist ],
2000 __PACKAGE__-
>register_method({
2002 path
=> '{vmid}/clone',
2006 description
=> "Create a copy of virtual machine/template.",
2008 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2009 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2010 "'Datastore.AllocateSpace' on any used storage.",
2013 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2015 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2016 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2021 additionalProperties
=> 0,
2023 node
=> get_standard_option
('pve-node'),
2024 vmid
=> get_standard_option
('pve-vmid'),
2025 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2028 type
=> 'string', format
=> 'dns-name',
2029 description
=> "Set a name for the new VM.",
2034 description
=> "Description for the new VM.",
2038 type
=> 'string', format
=> 'pve-poolid',
2039 description
=> "Add the new VM to the specified pool.",
2041 snapname
=> get_standard_option
('pve-snapshot-name', {
2045 storage
=> get_standard_option
('pve-storage-id', {
2046 description
=> "Target storage for full clone.",
2051 description
=> "Target format for file storage.",
2055 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2060 description
=> "Create a full copy of all disk. This is always done when " .
2061 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2064 target
=> get_standard_option
('pve-node', {
2065 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2076 my $rpcenv = PVE
::RPCEnvironment
::get
();
2078 my $authuser = $rpcenv->get_user();
2080 my $node = extract_param
($param, 'node');
2082 my $vmid = extract_param
($param, 'vmid');
2084 my $newid = extract_param
($param, 'newid');
2086 my $pool = extract_param
($param, 'pool');
2088 if (defined($pool)) {
2089 $rpcenv->check_pool_exist($pool);
2092 my $snapname = extract_param
($param, 'snapname');
2094 my $storage = extract_param
($param, 'storage');
2096 my $format = extract_param
($param, 'format');
2098 my $target = extract_param
($param, 'target');
2100 my $localnode = PVE
::INotify
::nodename
();
2102 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2104 PVE
::Cluster
::check_node_exists
($target) if $target;
2106 my $storecfg = PVE
::Storage
::config
();
2109 # check if storage is enabled on local node
2110 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2112 # check if storage is available on target node
2113 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2114 # clone only works if target storage is shared
2115 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2116 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2120 PVE
::Cluster
::check_cfs_quorum
();
2122 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2124 # exclusive lock if VM is running - else shared lock is enough;
2125 my $shared_lock = $running ?
0 : 1;
2129 # do all tests after lock
2130 # we also try to do all tests before we fork the worker
2132 my $conf = PVE
::QemuServer
::load_config
($vmid);
2134 PVE
::QemuServer
::check_lock
($conf);
2136 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2138 die "unexpected state change\n" if $verify_running != $running;
2140 die "snapshot '$snapname' does not exist\n"
2141 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2143 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2145 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2147 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2149 my $conffile = PVE
::QemuServer
::config_file
($newid);
2151 die "unable to create VM $newid: config file already exists\n"
2154 my $newconf = { lock => 'clone' };
2158 foreach my $opt (keys %$oldconf) {
2159 my $value = $oldconf->{$opt};
2161 # do not copy snapshot related info
2162 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2163 $opt eq 'vmstate' || $opt eq 'snapstate';
2165 # always change MAC! address
2166 if ($opt =~ m/^net(\d+)$/) {
2167 my $net = PVE
::QemuServer
::parse_net
($value);
2168 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2169 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2170 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2171 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2172 die "unable to parse drive options for '$opt'\n" if !$drive;
2173 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2174 $newconf->{$opt} = $value; # simply copy configuration
2176 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2177 die "Full clone feature is not available"
2178 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2181 $drives->{$opt} = $drive;
2182 push @$vollist, $drive->{file
};
2185 # copy everything else
2186 $newconf->{$opt} = $value;
2190 delete $newconf->{template
};
2192 if ($param->{name
}) {
2193 $newconf->{name
} = $param->{name
};
2195 if ($oldconf->{name
}) {
2196 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2198 $newconf->{name
} = "Copy-of-VM-$vmid";
2202 if ($param->{description
}) {
2203 $newconf->{description
} = $param->{description
};
2206 # create empty/temp config - this fails if VM already exists on other node
2207 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2212 my $newvollist = [];
2215 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2217 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2219 foreach my $opt (keys %$drives) {
2220 my $drive = $drives->{$opt};
2222 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2223 $newid, $storage, $format, $drive->{full
}, $newvollist);
2225 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2227 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2230 delete $newconf->{lock};
2231 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2234 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2235 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2237 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2238 die "Failed to move config to node '$target' - rename failed: $!\n"
2239 if !rename($conffile, $newconffile);
2242 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2247 sleep 1; # some storage like rbd need to wait before release volume - really?
2249 foreach my $volid (@$newvollist) {
2250 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2253 die "clone failed: $err";
2259 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2262 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2263 # Aquire exclusive lock lock for $newid
2264 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2269 __PACKAGE__-
>register_method({
2270 name
=> 'move_vm_disk',
2271 path
=> '{vmid}/move_disk',
2275 description
=> "Move volume to different storage.",
2277 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2278 "and 'Datastore.AllocateSpace' permissions on the storage.",
2281 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2282 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2286 additionalProperties
=> 0,
2288 node
=> get_standard_option
('pve-node'),
2289 vmid
=> get_standard_option
('pve-vmid'),
2292 description
=> "The disk you want to move.",
2293 enum
=> [ PVE
::QemuServer
::disknames
() ],
2295 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2298 description
=> "Target Format.",
2299 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2304 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2310 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2318 description
=> "the task ID.",
2323 my $rpcenv = PVE
::RPCEnvironment
::get
();
2325 my $authuser = $rpcenv->get_user();
2327 my $node = extract_param
($param, 'node');
2329 my $vmid = extract_param
($param, 'vmid');
2331 my $digest = extract_param
($param, 'digest');
2333 my $disk = extract_param
($param, 'disk');
2335 my $storeid = extract_param
($param, 'storage');
2337 my $format = extract_param
($param, 'format');
2339 my $storecfg = PVE
::Storage
::config
();
2341 my $updatefn = sub {
2343 my $conf = PVE
::QemuServer
::load_config
($vmid);
2345 die "checksum missmatch (file change by other user?)\n"
2346 if $digest && $digest ne $conf->{digest
};
2348 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2350 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2352 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2354 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2357 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2358 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2362 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2363 (!$format || !$oldfmt || $oldfmt eq $format);
2365 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2367 my $running = PVE
::QemuServer
::check_running
($vmid);
2369 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2373 my $newvollist = [];
2376 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2378 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2379 $vmid, $storeid, $format, 1, $newvollist);
2381 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2383 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2385 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2389 foreach my $volid (@$newvollist) {
2390 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2393 die "storage migration failed: $err";
2396 if ($param->{delete}) {
2397 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2402 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2405 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2408 __PACKAGE__-
>register_method({
2409 name
=> 'migrate_vm',
2410 path
=> '{vmid}/migrate',
2414 description
=> "Migrate virtual machine. Creates a new migration task.",
2416 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2419 additionalProperties
=> 0,
2421 node
=> get_standard_option
('pve-node'),
2422 vmid
=> get_standard_option
('pve-vmid'),
2423 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2426 description
=> "Use online/live migration.",
2431 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2438 description
=> "the task ID.",
2443 my $rpcenv = PVE
::RPCEnvironment
::get
();
2445 my $authuser = $rpcenv->get_user();
2447 my $target = extract_param
($param, 'target');
2449 my $localnode = PVE
::INotify
::nodename
();
2450 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2452 PVE
::Cluster
::check_cfs_quorum
();
2454 PVE
::Cluster
::check_node_exists
($target);
2456 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2458 my $vmid = extract_param
($param, 'vmid');
2460 raise_param_exc
({ force
=> "Only root may use this option." })
2461 if $param->{force
} && $authuser ne 'root@pam';
2464 my $conf = PVE
::QemuServer
::load_config
($vmid);
2466 # try to detect errors early
2468 PVE
::QemuServer
::check_lock
($conf);
2470 if (PVE
::QemuServer
::check_running
($vmid)) {
2471 die "cant migrate running VM without --online\n"
2472 if !$param->{online
};
2475 my $storecfg = PVE
::Storage
::config
();
2476 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2478 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2483 my $service = "pvevm:$vmid";
2485 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2487 print "Executing HA migrate for VM $vmid to node $target\n";
2489 PVE
::Tools
::run_command
($cmd);
2494 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2501 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2504 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2509 __PACKAGE__-
>register_method({
2511 path
=> '{vmid}/monitor',
2515 description
=> "Execute Qemu monitor commands.",
2517 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2520 additionalProperties
=> 0,
2522 node
=> get_standard_option
('pve-node'),
2523 vmid
=> get_standard_option
('pve-vmid'),
2526 description
=> "The monitor command.",
2530 returns
=> { type
=> 'string'},
2534 my $vmid = $param->{vmid
};
2536 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2540 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2542 $res = "ERROR: $@" if $@;
2547 __PACKAGE__-
>register_method({
2548 name
=> 'resize_vm',
2549 path
=> '{vmid}/resize',
2553 description
=> "Extend volume size.",
2555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2558 additionalProperties
=> 0,
2560 node
=> get_standard_option
('pve-node'),
2561 vmid
=> get_standard_option
('pve-vmid'),
2562 skiplock
=> get_standard_option
('skiplock'),
2565 description
=> "The disk you want to resize.",
2566 enum
=> [PVE
::QemuServer
::disknames
()],
2570 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2571 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.",
2575 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2581 returns
=> { type
=> 'null'},
2585 my $rpcenv = PVE
::RPCEnvironment
::get
();
2587 my $authuser = $rpcenv->get_user();
2589 my $node = extract_param
($param, 'node');
2591 my $vmid = extract_param
($param, 'vmid');
2593 my $digest = extract_param
($param, 'digest');
2595 my $disk = extract_param
($param, 'disk');
2597 my $sizestr = extract_param
($param, 'size');
2599 my $skiplock = extract_param
($param, 'skiplock');
2600 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2601 if $skiplock && $authuser ne 'root@pam';
2603 my $storecfg = PVE
::Storage
::config
();
2605 my $updatefn = sub {
2607 my $conf = PVE
::QemuServer
::load_config
($vmid);
2609 die "checksum missmatch (file change by other user?)\n"
2610 if $digest && $digest ne $conf->{digest
};
2611 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2613 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2615 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2617 my $volid = $drive->{file
};
2619 die "disk '$disk' has no associated volume\n" if !$volid;
2621 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2623 die "you can't online resize a virtio windows bootdisk\n"
2624 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2626 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2628 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2630 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2632 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2633 my ($ext, $newsize, $unit) = ($1, $2, $4);
2636 $newsize = $newsize * 1024;
2637 } elsif ($unit eq 'M') {
2638 $newsize = $newsize * 1024 * 1024;
2639 } elsif ($unit eq 'G') {
2640 $newsize = $newsize * 1024 * 1024 * 1024;
2641 } elsif ($unit eq 'T') {
2642 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2645 $newsize += $size if $ext;
2646 $newsize = int($newsize);
2648 die "unable to skrink disk size\n" if $newsize < $size;
2650 return if $size == $newsize;
2652 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2654 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2656 $drive->{size
} = $newsize;
2657 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2659 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2662 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2666 __PACKAGE__-
>register_method({
2667 name
=> 'snapshot_list',
2668 path
=> '{vmid}/snapshot',
2670 description
=> "List all snapshots.",
2672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2675 protected
=> 1, # qemu pid files are only readable by root
2677 additionalProperties
=> 0,
2679 vmid
=> get_standard_option
('pve-vmid'),
2680 node
=> get_standard_option
('pve-node'),
2689 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2694 my $vmid = $param->{vmid
};
2696 my $conf = PVE
::QemuServer
::load_config
($vmid);
2697 my $snaphash = $conf->{snapshots
} || {};
2701 foreach my $name (keys %$snaphash) {
2702 my $d = $snaphash->{$name};
2705 snaptime
=> $d->{snaptime
} || 0,
2706 vmstate
=> $d->{vmstate
} ?
1 : 0,
2707 description
=> $d->{description
} || '',
2709 $item->{parent
} = $d->{parent
} if $d->{parent
};
2710 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2714 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2715 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2716 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2718 push @$res, $current;
2723 __PACKAGE__-
>register_method({
2725 path
=> '{vmid}/snapshot',
2729 description
=> "Snapshot a VM.",
2731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2734 additionalProperties
=> 0,
2736 node
=> get_standard_option
('pve-node'),
2737 vmid
=> get_standard_option
('pve-vmid'),
2738 snapname
=> get_standard_option
('pve-snapshot-name'),
2742 description
=> "Save the vmstate",
2747 description
=> "Freeze the filesystem",
2752 description
=> "A textual description or comment.",
2758 description
=> "the task ID.",
2763 my $rpcenv = PVE
::RPCEnvironment
::get
();
2765 my $authuser = $rpcenv->get_user();
2767 my $node = extract_param
($param, 'node');
2769 my $vmid = extract_param
($param, 'vmid');
2771 my $snapname = extract_param
($param, 'snapname');
2773 die "unable to use snapshot name 'current' (reserved name)\n"
2774 if $snapname eq 'current';
2777 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2778 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2779 $param->{freezefs
}, $param->{description
});
2782 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2785 __PACKAGE__-
>register_method({
2786 name
=> 'snapshot_cmd_idx',
2787 path
=> '{vmid}/snapshot/{snapname}',
2794 additionalProperties
=> 0,
2796 vmid
=> get_standard_option
('pve-vmid'),
2797 node
=> get_standard_option
('pve-node'),
2798 snapname
=> get_standard_option
('pve-snapshot-name'),
2807 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2814 push @$res, { cmd
=> 'rollback' };
2815 push @$res, { cmd
=> 'config' };
2820 __PACKAGE__-
>register_method({
2821 name
=> 'update_snapshot_config',
2822 path
=> '{vmid}/snapshot/{snapname}/config',
2826 description
=> "Update snapshot metadata.",
2828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2831 additionalProperties
=> 0,
2833 node
=> get_standard_option
('pve-node'),
2834 vmid
=> get_standard_option
('pve-vmid'),
2835 snapname
=> get_standard_option
('pve-snapshot-name'),
2839 description
=> "A textual description or comment.",
2843 returns
=> { type
=> 'null' },
2847 my $rpcenv = PVE
::RPCEnvironment
::get
();
2849 my $authuser = $rpcenv->get_user();
2851 my $vmid = extract_param
($param, 'vmid');
2853 my $snapname = extract_param
($param, 'snapname');
2855 return undef if !defined($param->{description
});
2857 my $updatefn = sub {
2859 my $conf = PVE
::QemuServer
::load_config
($vmid);
2861 PVE
::QemuServer
::check_lock
($conf);
2863 my $snap = $conf->{snapshots
}->{$snapname};
2865 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2867 $snap->{description
} = $param->{description
} if defined($param->{description
});
2869 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2872 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2877 __PACKAGE__-
>register_method({
2878 name
=> 'get_snapshot_config',
2879 path
=> '{vmid}/snapshot/{snapname}/config',
2882 description
=> "Get snapshot configuration",
2884 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2887 additionalProperties
=> 0,
2889 node
=> get_standard_option
('pve-node'),
2890 vmid
=> get_standard_option
('pve-vmid'),
2891 snapname
=> get_standard_option
('pve-snapshot-name'),
2894 returns
=> { type
=> "object" },
2898 my $rpcenv = PVE
::RPCEnvironment
::get
();
2900 my $authuser = $rpcenv->get_user();
2902 my $vmid = extract_param
($param, 'vmid');
2904 my $snapname = extract_param
($param, 'snapname');
2906 my $conf = PVE
::QemuServer
::load_config
($vmid);
2908 my $snap = $conf->{snapshots
}->{$snapname};
2910 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2915 __PACKAGE__-
>register_method({
2917 path
=> '{vmid}/snapshot/{snapname}/rollback',
2921 description
=> "Rollback VM state to specified snapshot.",
2923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2926 additionalProperties
=> 0,
2928 node
=> get_standard_option
('pve-node'),
2929 vmid
=> get_standard_option
('pve-vmid'),
2930 snapname
=> get_standard_option
('pve-snapshot-name'),
2935 description
=> "the task ID.",
2940 my $rpcenv = PVE
::RPCEnvironment
::get
();
2942 my $authuser = $rpcenv->get_user();
2944 my $node = extract_param
($param, 'node');
2946 my $vmid = extract_param
($param, 'vmid');
2948 my $snapname = extract_param
($param, 'snapname');
2951 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2952 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2955 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2958 __PACKAGE__-
>register_method({
2959 name
=> 'delsnapshot',
2960 path
=> '{vmid}/snapshot/{snapname}',
2964 description
=> "Delete a VM snapshot.",
2966 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2969 additionalProperties
=> 0,
2971 node
=> get_standard_option
('pve-node'),
2972 vmid
=> get_standard_option
('pve-vmid'),
2973 snapname
=> get_standard_option
('pve-snapshot-name'),
2977 description
=> "For removal from config file, even if removing disk snapshots fails.",
2983 description
=> "the task ID.",
2988 my $rpcenv = PVE
::RPCEnvironment
::get
();
2990 my $authuser = $rpcenv->get_user();
2992 my $node = extract_param
($param, 'node');
2994 my $vmid = extract_param
($param, 'vmid');
2996 my $snapname = extract_param
($param, 'snapname');
2999 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3000 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3003 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3006 __PACKAGE__-
>register_method({
3008 path
=> '{vmid}/template',
3012 description
=> "Create a Template.",
3014 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3015 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3018 additionalProperties
=> 0,
3020 node
=> get_standard_option
('pve-node'),
3021 vmid
=> get_standard_option
('pve-vmid'),
3025 description
=> "If you want to convert only 1 disk to base image.",
3026 enum
=> [PVE
::QemuServer
::disknames
()],
3031 returns
=> { type
=> 'null'},
3035 my $rpcenv = PVE
::RPCEnvironment
::get
();
3037 my $authuser = $rpcenv->get_user();
3039 my $node = extract_param
($param, 'node');
3041 my $vmid = extract_param
($param, 'vmid');
3043 my $disk = extract_param
($param, 'disk');
3045 my $updatefn = sub {
3047 my $conf = PVE
::QemuServer
::load_config
($vmid);
3049 PVE
::QemuServer
::check_lock
($conf);
3051 die "unable to create template, because VM contains snapshots\n"
3052 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3054 die "you can't convert a template to a template\n"
3055 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3057 die "you can't convert a VM to template if VM is running\n"
3058 if PVE
::QemuServer
::check_running
($vmid);
3061 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3064 $conf->{template
} = 1;
3065 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3067 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3070 PVE
::QemuServer
::lock_config
($vmid, $updatefn);