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' },
502 __PACKAGE__-
>register_method({
504 path
=> '{vmid}/rrd',
506 protected
=> 1, # fixme: can we avoid that?
508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
510 description
=> "Read VM RRD statistics (returns PNG)",
512 additionalProperties
=> 0,
514 node
=> get_standard_option
('pve-node'),
515 vmid
=> get_standard_option
('pve-vmid'),
517 description
=> "Specify the time frame you are interested in.",
519 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
522 description
=> "The list of datasources you want to display.",
523 type
=> 'string', format
=> 'pve-configid-list',
526 description
=> "The RRD consolidation function",
528 enum
=> [ 'AVERAGE', 'MAX' ],
536 filename
=> { type
=> 'string' },
542 return PVE
::Cluster
::create_rrd_graph
(
543 "pve2-vm/$param->{vmid}", $param->{timeframe
},
544 $param->{ds
}, $param->{cf
});
548 __PACKAGE__-
>register_method({
550 path
=> '{vmid}/rrddata',
552 protected
=> 1, # fixme: can we avoid that?
554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
556 description
=> "Read VM RRD statistics",
558 additionalProperties
=> 0,
560 node
=> get_standard_option
('pve-node'),
561 vmid
=> get_standard_option
('pve-vmid'),
563 description
=> "Specify the time frame you are interested in.",
565 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
568 description
=> "The RRD consolidation function",
570 enum
=> [ 'AVERAGE', 'MAX' ],
585 return PVE
::Cluster
::create_rrd_data
(
586 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
590 __PACKAGE__-
>register_method({
592 path
=> '{vmid}/config',
595 description
=> "Get virtual machine configuration.",
597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
603 vmid
=> get_standard_option
('pve-vmid'),
611 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
618 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
620 delete $conf->{snapshots
};
625 my $vm_is_volid_owner = sub {
626 my ($storecfg, $vmid, $volid) =@_;
628 if ($volid !~ m
|^/|) {
630 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
631 if ($owner && ($owner == $vmid)) {
639 my $test_deallocate_drive = sub {
640 my ($storecfg, $vmid, $key, $drive, $force) = @_;
642 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
643 my $volid = $drive->{file
};
644 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
645 if ($force || $key =~ m/^unused/) {
646 my $sid = PVE
::Storage
::parse_volume_id
($volid);
655 my $delete_drive = sub {
656 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
658 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
659 my $volid = $drive->{file
};
661 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
662 if ($force || $key =~ m/^unused/) {
664 # check if the disk is really unused
665 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
666 my $path = PVE
::Storage
::path
($storecfg, $volid);
668 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
669 if $used_paths->{$path};
671 PVE
::Storage
::vdisk_free
($storecfg, $volid);
675 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
680 delete $conf->{$key};
683 my $vmconfig_delete_option = sub {
684 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
686 return if !defined($conf->{$opt});
688 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
691 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
693 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
694 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
695 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
699 my $unplugwarning = "";
700 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
701 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
702 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
703 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
704 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
705 $unplugwarning = "<br>verify that your guest support acpi hotplug";
708 if ($opt eq 'tablet') {
709 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
711 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
715 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
716 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
718 delete $conf->{$opt};
721 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
724 my $safe_num_ne = sub {
727 return 0 if !defined($a) && !defined($b);
728 return 1 if !defined($a);
729 return 1 if !defined($b);
734 my $vmconfig_update_disk = sub {
735 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
737 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
739 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
740 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
742 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
747 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
749 my $media = $drive->{media
} || 'disk';
750 my $oldmedia = $old_drive->{media
} || 'disk';
751 die "unable to change media type\n" if $media ne $oldmedia;
753 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
754 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
756 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
760 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
761 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
762 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
763 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
764 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
765 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
766 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
767 ($drive->{mbps
} || 0)*1024*1024,
768 ($drive->{mbps_rd
} || 0)*1024*1024,
769 ($drive->{mbps_wr
} || 0)*1024*1024,
771 $drive->{iops_rd
} || 0,
772 $drive->{iops_wr
} || 0)
773 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
778 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
779 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
782 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
784 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
786 if (PVE
::QemuServer
::check_running
($vmid)) {
787 if ($drive->{file
} eq 'none') {
788 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
790 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
796 } else { # hotplug new disks
798 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
802 my $vmconfig_update_net = sub {
803 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
805 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
806 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
807 my $newnet = PVE
::QemuServer
::parse_net
($value);
809 if($oldnet->{model
} ne $newnet->{model
}){
810 #if model change, we try to hot-unplug
811 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
814 if($newnet->{bridge
} && $oldnet->{bridge
}){
815 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
817 if($newnet->{rate
} ne $oldnet->{rate
}){
818 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
821 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
822 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
823 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
827 #if bridge/nat mode change, we try to hot-unplug
828 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
833 $conf->{$opt} = $value;
834 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
835 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
837 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
839 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
842 # POST/PUT {vmid}/config implementation
844 # The original API used PUT (idempotent) an we assumed that all operations
845 # are fast. But it turned out that almost any configuration change can
846 # involve hot-plug actions, or disk alloc/free. Such actions can take long
847 # time to complete and have side effects (not idempotent).
849 # The new implementation uses POST and forks a worker process. We added
850 # a new option 'background_delay'. If specified we wait up to
851 # 'background_delay' second for the worker task to complete. It returns null
852 # if the task is finished within that time, else we return the UPID.
854 my $update_vm_api = sub {
855 my ($param, $sync) = @_;
857 my $rpcenv = PVE
::RPCEnvironment
::get
();
859 my $authuser = $rpcenv->get_user();
861 my $node = extract_param
($param, 'node');
863 my $vmid = extract_param
($param, 'vmid');
865 my $digest = extract_param
($param, 'digest');
867 my $background_delay = extract_param
($param, 'background_delay');
869 my @paramarr = (); # used for log message
870 foreach my $key (keys %$param) {
871 push @paramarr, "-$key", $param->{$key};
874 my $skiplock = extract_param
($param, 'skiplock');
875 raise_param_exc
({ skiplock
=> "Only root may use this option." })
876 if $skiplock && $authuser ne 'root@pam';
878 my $delete_str = extract_param
($param, 'delete');
880 my $force = extract_param
($param, 'force');
882 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
884 my $storecfg = PVE
::Storage
::config
();
886 my $defaults = PVE
::QemuServer
::load_defaults
();
888 &$resolve_cdrom_alias($param);
890 # now try to verify all parameters
893 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
894 $opt = 'ide2' if $opt eq 'cdrom';
895 raise_param_exc
({ delete => "you can't use '-$opt' and " .
896 "-delete $opt' at the same time" })
897 if defined($param->{$opt});
899 if (!PVE
::QemuServer
::option_exists
($opt)) {
900 raise_param_exc
({ delete => "unknown option '$opt'" });
906 foreach my $opt (keys %$param) {
907 if (PVE
::QemuServer
::valid_drivename
($opt)) {
909 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
910 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
911 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
912 } elsif ($opt =~ m/^net(\d+)$/) {
914 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
915 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
919 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
921 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
923 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
927 my $conf = PVE
::QemuServer
::load_config
($vmid);
929 die "checksum missmatch (file change by other user?)\n"
930 if $digest && $digest ne $conf->{digest
};
932 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
934 if ($param->{memory
} || defined($param->{balloon
})) {
935 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
936 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
938 die "balloon value too large (must be smaller than assigned memory)\n"
939 if $balloon && $balloon > $maxmem;
942 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
946 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
948 foreach my $opt (@delete) { # delete
949 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
950 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
953 my $running = PVE
::QemuServer
::check_running
($vmid);
955 foreach my $opt (keys %$param) { # add/change
957 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
959 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
961 if (PVE
::QemuServer
::valid_drivename
($opt)) {
963 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
964 $opt, $param->{$opt}, $force);
966 } elsif ($opt =~ m/^net(\d+)$/) { #nics
968 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt});
973 if($opt eq 'tablet' && $param->{$opt} == 1){
974 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
975 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
976 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
979 $conf->{$opt} = $param->{$opt};
980 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
984 # allow manual ballooning if shares is set to zero
985 if ($running && defined($param->{balloon
}) &&
986 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
987 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
988 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
996 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
998 if ($background_delay) {
1000 # Note: It would be better to do that in the Event based HTTPServer
1001 # to avoid blocking call to sleep.
1003 my $end_time = time() + $background_delay;
1005 my $task = PVE
::Tools
::upid_decode
($upid);
1008 while (time() < $end_time) {
1009 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1011 sleep(1); # this gets interrupted when child process ends
1015 my $status = PVE
::Tools
::upid_read_status
($upid);
1016 return undef if $status eq 'OK';
1025 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1028 my $vm_config_perm_list = [
1033 'VM.Config.Network',
1035 'VM.Config.Options',
1038 __PACKAGE__-
>register_method({
1039 name
=> 'update_vm_async',
1040 path
=> '{vmid}/config',
1044 description
=> "Set virtual machine options (asynchrounous API).",
1046 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1049 additionalProperties
=> 0,
1050 properties
=> PVE
::QemuServer
::json_config_properties
(
1052 node
=> get_standard_option
('pve-node'),
1053 vmid
=> get_standard_option
('pve-vmid'),
1054 skiplock
=> get_standard_option
('skiplock'),
1056 type
=> 'string', format
=> 'pve-configid-list',
1057 description
=> "A list of settings you want to delete.",
1062 description
=> $opt_force_description,
1064 requires
=> 'delete',
1068 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1072 background_delay
=> {
1074 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1085 code
=> $update_vm_api,
1088 __PACKAGE__-
>register_method({
1089 name
=> 'update_vm',
1090 path
=> '{vmid}/config',
1094 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1096 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1099 additionalProperties
=> 0,
1100 properties
=> PVE
::QemuServer
::json_config_properties
(
1102 node
=> get_standard_option
('pve-node'),
1103 vmid
=> get_standard_option
('pve-vmid'),
1104 skiplock
=> get_standard_option
('skiplock'),
1106 type
=> 'string', format
=> 'pve-configid-list',
1107 description
=> "A list of settings you want to delete.",
1112 description
=> $opt_force_description,
1114 requires
=> 'delete',
1118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1124 returns
=> { type
=> 'null' },
1127 &$update_vm_api($param, 1);
1133 __PACKAGE__-
>register_method({
1134 name
=> 'destroy_vm',
1139 description
=> "Destroy the vm (also delete all used/owned volumes).",
1141 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1144 additionalProperties
=> 0,
1146 node
=> get_standard_option
('pve-node'),
1147 vmid
=> get_standard_option
('pve-vmid'),
1148 skiplock
=> get_standard_option
('skiplock'),
1157 my $rpcenv = PVE
::RPCEnvironment
::get
();
1159 my $authuser = $rpcenv->get_user();
1161 my $vmid = $param->{vmid
};
1163 my $skiplock = $param->{skiplock
};
1164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1165 if $skiplock && $authuser ne 'root@pam';
1168 my $conf = PVE
::QemuServer
::load_config
($vmid);
1170 my $storecfg = PVE
::Storage
::config
();
1172 my $delVMfromPoolFn = sub {
1173 my $usercfg = cfs_read_file
("user.cfg");
1174 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1175 if (my $data = $usercfg->{pools
}->{$pool}) {
1176 delete $data->{vms
}->{$vmid};
1177 delete $usercfg->{vms
}->{$vmid};
1178 cfs_write_file
("user.cfg", $usercfg);
1186 syslog
('info', "destroy VM $vmid: $upid\n");
1188 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1190 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1193 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1196 __PACKAGE__-
>register_method({
1198 path
=> '{vmid}/unlink',
1202 description
=> "Unlink/delete disk images.",
1204 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1207 additionalProperties
=> 0,
1209 node
=> get_standard_option
('pve-node'),
1210 vmid
=> get_standard_option
('pve-vmid'),
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "A list of disk IDs you want to delete.",
1217 description
=> $opt_force_description,
1222 returns
=> { type
=> 'null'},
1226 $param->{delete} = extract_param
($param, 'idlist');
1228 __PACKAGE__-
>update_vm($param);
1235 __PACKAGE__-
>register_method({
1237 path
=> '{vmid}/vncproxy',
1241 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1243 description
=> "Creates a TCP VNC proxy connections.",
1245 additionalProperties
=> 0,
1247 node
=> get_standard_option
('pve-node'),
1248 vmid
=> get_standard_option
('pve-vmid'),
1252 additionalProperties
=> 0,
1254 user
=> { type
=> 'string' },
1255 ticket
=> { type
=> 'string' },
1256 cert
=> { type
=> 'string' },
1257 port
=> { type
=> 'integer' },
1258 upid
=> { type
=> 'string' },
1264 my $rpcenv = PVE
::RPCEnvironment
::get
();
1266 my $authuser = $rpcenv->get_user();
1268 my $vmid = $param->{vmid
};
1269 my $node = $param->{node
};
1271 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1273 my $authpath = "/vms/$vmid";
1275 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1277 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1280 my $port = PVE
::Tools
::next_vnc_port
();
1285 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1286 $remip = PVE
::Cluster
::remote_node_ip
($node);
1287 # NOTE: kvm VNC traffic is already TLS encrypted
1288 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1296 syslog
('info', "starting vnc proxy $upid\n");
1300 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1302 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1303 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1304 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1305 '-timeout', $timeout, '-authpath', $authpath,
1306 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1309 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1311 my $qmstr = join(' ', @$qmcmd);
1313 # also redirect stderr (else we get RFB protocol errors)
1314 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1317 PVE
::Tools
::run_command
($cmd);
1322 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1324 PVE
::Tools
::wait_for_vnc_port
($port);
1335 __PACKAGE__-
>register_method({
1336 name
=> 'spiceproxy',
1337 path
=> '{vmid}/spiceproxy',
1340 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1342 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1344 description
=> "Returns a SPICE configuration to connect to the VM.",
1346 additionalProperties
=> 0,
1348 node
=> get_standard_option
('pve-node'),
1349 vmid
=> get_standard_option
('pve-vmid'),
1351 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).",
1352 type
=> 'string', format
=> 'dns-name',
1358 description
=> "Returned values can be directly passed to the 'remote-viewer' application.",
1359 additionalProperties
=> 1,
1361 type
=> { type
=> 'string' },
1362 password
=> { type
=> 'string' },
1363 proxy
=> { type
=> 'string' },
1364 host
=> { type
=> 'string' },
1365 'tls-port' => { type
=> 'integer' },
1371 my $rpcenv = PVE
::RPCEnvironment
::get
();
1373 my $authuser = $rpcenv->get_user();
1375 my $vmid = $param->{vmid
};
1376 my $node = $param->{node
};
1377 my $proxy = $param->{proxy
};
1379 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1383 my $port = PVE
::QemuServer
::spice_port
($vmid);
1384 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1385 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1388 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1393 my $filename = "/etc/pve/local/pve-ssl.pem";
1394 my $subject = PVE
::QemuServer
::read_x509_subject_spice
($filename);
1396 my $cacert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192);
1397 $cacert =~ s/\n/\\n/g;
1401 title
=> "VM $vmid",
1402 host
=> $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
1403 proxy
=> "http://$proxy:3128",
1404 'tls-port' => $port,
1405 'host-subject' => $subject,
1407 password
=> $ticket,
1408 'delete-this-file' => 1,
1412 __PACKAGE__-
>register_method({
1414 path
=> '{vmid}/status',
1417 description
=> "Directory index",
1422 additionalProperties
=> 0,
1424 node
=> get_standard_option
('pve-node'),
1425 vmid
=> get_standard_option
('pve-vmid'),
1433 subdir
=> { type
=> 'string' },
1436 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1442 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1445 { subdir
=> 'current' },
1446 { subdir
=> 'start' },
1447 { subdir
=> 'stop' },
1453 my $vm_is_ha_managed = sub {
1456 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1457 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1463 __PACKAGE__-
>register_method({
1464 name
=> 'vm_status',
1465 path
=> '{vmid}/status/current',
1468 protected
=> 1, # qemu pid files are only readable by root
1469 description
=> "Get virtual machine status.",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1480 returns
=> { type
=> 'object' },
1485 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1487 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1488 my $status = $vmstatus->{$param->{vmid
}};
1490 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1492 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1497 __PACKAGE__-
>register_method({
1499 path
=> '{vmid}/status/start',
1503 description
=> "Start virtual machine.",
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1508 additionalProperties
=> 0,
1510 node
=> get_standard_option
('pve-node'),
1511 vmid
=> get_standard_option
('pve-vmid'),
1512 skiplock
=> get_standard_option
('skiplock'),
1513 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1514 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1515 machine
=> get_standard_option
('pve-qm-machine'),
1524 my $rpcenv = PVE
::RPCEnvironment
::get
();
1526 my $authuser = $rpcenv->get_user();
1528 my $node = extract_param
($param, 'node');
1530 my $vmid = extract_param
($param, 'vmid');
1532 my $machine = extract_param
($param, 'machine');
1534 my $stateuri = extract_param
($param, 'stateuri');
1535 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1536 if $stateuri && $authuser ne 'root@pam';
1538 my $skiplock = extract_param
($param, 'skiplock');
1539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1540 if $skiplock && $authuser ne 'root@pam';
1542 my $migratedfrom = extract_param
($param, 'migratedfrom');
1543 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1544 if $migratedfrom && $authuser ne 'root@pam';
1546 # read spice ticket from STDIN
1548 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1549 if (defined(my $line = <>)) {
1551 $spice_ticket = $line;
1555 my $storecfg = PVE
::Storage
::config
();
1557 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1558 $rpcenv->{type
} ne 'ha') {
1563 my $service = "pvevm:$vmid";
1565 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1567 print "Executing HA start for VM $vmid\n";
1569 PVE
::Tools
::run_command
($cmd);
1574 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1581 syslog
('info', "start VM $vmid: $upid\n");
1583 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1584 $machine, $spice_ticket);
1589 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1593 __PACKAGE__-
>register_method({
1595 path
=> '{vmid}/status/stop',
1599 description
=> "Stop virtual machine.",
1601 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1604 additionalProperties
=> 0,
1606 node
=> get_standard_option
('pve-node'),
1607 vmid
=> get_standard_option
('pve-vmid'),
1608 skiplock
=> get_standard_option
('skiplock'),
1609 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1611 description
=> "Wait maximal timeout seconds.",
1617 description
=> "Do not decativate storage volumes.",
1630 my $rpcenv = PVE
::RPCEnvironment
::get
();
1632 my $authuser = $rpcenv->get_user();
1634 my $node = extract_param
($param, 'node');
1636 my $vmid = extract_param
($param, 'vmid');
1638 my $skiplock = extract_param
($param, 'skiplock');
1639 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1640 if $skiplock && $authuser ne 'root@pam';
1642 my $keepActive = extract_param
($param, 'keepActive');
1643 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1644 if $keepActive && $authuser ne 'root@pam';
1646 my $migratedfrom = extract_param
($param, 'migratedfrom');
1647 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1648 if $migratedfrom && $authuser ne 'root@pam';
1651 my $storecfg = PVE
::Storage
::config
();
1653 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1658 my $service = "pvevm:$vmid";
1660 my $cmd = ['clusvcadm', '-d', $service];
1662 print "Executing HA stop for VM $vmid\n";
1664 PVE
::Tools
::run_command
($cmd);
1669 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1675 syslog
('info', "stop VM $vmid: $upid\n");
1677 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1678 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1683 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1687 __PACKAGE__-
>register_method({
1689 path
=> '{vmid}/status/reset',
1693 description
=> "Reset virtual machine.",
1695 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1698 additionalProperties
=> 0,
1700 node
=> get_standard_option
('pve-node'),
1701 vmid
=> get_standard_option
('pve-vmid'),
1702 skiplock
=> get_standard_option
('skiplock'),
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $node = extract_param
($param, 'node');
1717 my $vmid = extract_param
($param, 'vmid');
1719 my $skiplock = extract_param
($param, 'skiplock');
1720 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1721 if $skiplock && $authuser ne 'root@pam';
1723 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1728 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1733 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1736 __PACKAGE__-
>register_method({
1737 name
=> 'vm_shutdown',
1738 path
=> '{vmid}/status/shutdown',
1742 description
=> "Shutdown virtual machine.",
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1751 skiplock
=> get_standard_option
('skiplock'),
1753 description
=> "Wait maximal timeout seconds.",
1759 description
=> "Make sure the VM stops.",
1765 description
=> "Do not decativate storage volumes.",
1778 my $rpcenv = PVE
::RPCEnvironment
::get
();
1780 my $authuser = $rpcenv->get_user();
1782 my $node = extract_param
($param, 'node');
1784 my $vmid = extract_param
($param, 'vmid');
1786 my $skiplock = extract_param
($param, 'skiplock');
1787 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1788 if $skiplock && $authuser ne 'root@pam';
1790 my $keepActive = extract_param
($param, 'keepActive');
1791 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1792 if $keepActive && $authuser ne 'root@pam';
1794 my $storecfg = PVE
::Storage
::config
();
1799 syslog
('info', "shutdown VM $vmid: $upid\n");
1801 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1802 1, $param->{forceStop
}, $keepActive);
1807 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1810 __PACKAGE__-
>register_method({
1811 name
=> 'vm_suspend',
1812 path
=> '{vmid}/status/suspend',
1816 description
=> "Suspend virtual machine.",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid'),
1825 skiplock
=> get_standard_option
('skiplock'),
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $node = extract_param
($param, 'node');
1840 my $vmid = extract_param
($param, 'vmid');
1842 my $skiplock = extract_param
($param, 'skiplock');
1843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1844 if $skiplock && $authuser ne 'root@pam';
1846 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1851 syslog
('info', "suspend VM $vmid: $upid\n");
1853 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1858 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1861 __PACKAGE__-
>register_method({
1862 name
=> 'vm_resume',
1863 path
=> '{vmid}/status/resume',
1867 description
=> "Resume virtual machine.",
1869 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1872 additionalProperties
=> 0,
1874 node
=> get_standard_option
('pve-node'),
1875 vmid
=> get_standard_option
('pve-vmid'),
1876 skiplock
=> get_standard_option
('skiplock'),
1885 my $rpcenv = PVE
::RPCEnvironment
::get
();
1887 my $authuser = $rpcenv->get_user();
1889 my $node = extract_param
($param, 'node');
1891 my $vmid = extract_param
($param, 'vmid');
1893 my $skiplock = extract_param
($param, 'skiplock');
1894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1895 if $skiplock && $authuser ne 'root@pam';
1897 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1902 syslog
('info', "resume VM $vmid: $upid\n");
1904 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1909 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1912 __PACKAGE__-
>register_method({
1913 name
=> 'vm_sendkey',
1914 path
=> '{vmid}/sendkey',
1918 description
=> "Send key event to virtual machine.",
1920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1923 additionalProperties
=> 0,
1925 node
=> get_standard_option
('pve-node'),
1926 vmid
=> get_standard_option
('pve-vmid'),
1927 skiplock
=> get_standard_option
('skiplock'),
1929 description
=> "The key (qemu monitor encoding).",
1934 returns
=> { type
=> 'null'},
1938 my $rpcenv = PVE
::RPCEnvironment
::get
();
1940 my $authuser = $rpcenv->get_user();
1942 my $node = extract_param
($param, 'node');
1944 my $vmid = extract_param
($param, 'vmid');
1946 my $skiplock = extract_param
($param, 'skiplock');
1947 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1948 if $skiplock && $authuser ne 'root@pam';
1950 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_feature',
1957 path
=> '{vmid}/feature',
1961 description
=> "Check if feature for virtual machine is available.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid'),
1971 description
=> "Feature to check.",
1973 enum
=> [ 'snapshot', 'clone', 'copy' ],
1975 snapname
=> get_standard_option
('pve-snapshot-name', {
1983 hasFeature
=> { type
=> 'boolean' },
1986 items
=> { type
=> 'string' },
1993 my $node = extract_param
($param, 'node');
1995 my $vmid = extract_param
($param, 'vmid');
1997 my $snapname = extract_param
($param, 'snapname');
1999 my $feature = extract_param
($param, 'feature');
2001 my $running = PVE
::QemuServer
::check_running
($vmid);
2003 my $conf = PVE
::QemuServer
::load_config
($vmid);
2006 my $snap = $conf->{snapshots
}->{$snapname};
2007 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2010 my $storecfg = PVE
::Storage
::config
();
2012 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2013 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2016 hasFeature
=> $hasFeature,
2017 nodes
=> [ keys %$nodelist ],
2021 __PACKAGE__-
>register_method({
2023 path
=> '{vmid}/clone',
2027 description
=> "Create a copy of virtual machine/template.",
2029 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2030 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2031 "'Datastore.AllocateSpace' on any used storage.",
2034 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2036 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2037 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2042 additionalProperties
=> 0,
2044 node
=> get_standard_option
('pve-node'),
2045 vmid
=> get_standard_option
('pve-vmid'),
2046 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2049 type
=> 'string', format
=> 'dns-name',
2050 description
=> "Set a name for the new VM.",
2055 description
=> "Description for the new VM.",
2059 type
=> 'string', format
=> 'pve-poolid',
2060 description
=> "Add the new VM to the specified pool.",
2062 snapname
=> get_standard_option
('pve-snapshot-name', {
2066 storage
=> get_standard_option
('pve-storage-id', {
2067 description
=> "Target storage for full clone.",
2072 description
=> "Target format for file storage.",
2076 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2081 description
=> "Create a full copy of all disk. This is always done when " .
2082 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2085 target
=> get_standard_option
('pve-node', {
2086 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2097 my $rpcenv = PVE
::RPCEnvironment
::get
();
2099 my $authuser = $rpcenv->get_user();
2101 my $node = extract_param
($param, 'node');
2103 my $vmid = extract_param
($param, 'vmid');
2105 my $newid = extract_param
($param, 'newid');
2107 my $pool = extract_param
($param, 'pool');
2109 if (defined($pool)) {
2110 $rpcenv->check_pool_exist($pool);
2113 my $snapname = extract_param
($param, 'snapname');
2115 my $storage = extract_param
($param, 'storage');
2117 my $format = extract_param
($param, 'format');
2119 my $target = extract_param
($param, 'target');
2121 my $localnode = PVE
::INotify
::nodename
();
2123 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2125 PVE
::Cluster
::check_node_exists
($target) if $target;
2127 my $storecfg = PVE
::Storage
::config
();
2130 # check if storage is enabled on local node
2131 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2133 # check if storage is available on target node
2134 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2135 # clone only works if target storage is shared
2136 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2137 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2141 PVE
::Cluster
::check_cfs_quorum
();
2143 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2145 # exclusive lock if VM is running - else shared lock is enough;
2146 my $shared_lock = $running ?
0 : 1;
2150 # do all tests after lock
2151 # we also try to do all tests before we fork the worker
2153 my $conf = PVE
::QemuServer
::load_config
($vmid);
2155 PVE
::QemuServer
::check_lock
($conf);
2157 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2159 die "unexpected state change\n" if $verify_running != $running;
2161 die "snapshot '$snapname' does not exist\n"
2162 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2164 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2166 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2168 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2170 my $conffile = PVE
::QemuServer
::config_file
($newid);
2172 die "unable to create VM $newid: config file already exists\n"
2175 my $newconf = { lock => 'clone' };
2179 foreach my $opt (keys %$oldconf) {
2180 my $value = $oldconf->{$opt};
2182 # do not copy snapshot related info
2183 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2184 $opt eq 'vmstate' || $opt eq 'snapstate';
2186 # always change MAC! address
2187 if ($opt =~ m/^net(\d+)$/) {
2188 my $net = PVE
::QemuServer
::parse_net
($value);
2189 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2190 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2191 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2192 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2193 die "unable to parse drive options for '$opt'\n" if !$drive;
2194 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2195 $newconf->{$opt} = $value; # simply copy configuration
2197 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2198 die "Full clone feature is not available"
2199 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2202 $drives->{$opt} = $drive;
2203 push @$vollist, $drive->{file
};
2206 # copy everything else
2207 $newconf->{$opt} = $value;
2211 delete $newconf->{template
};
2213 if ($param->{name
}) {
2214 $newconf->{name
} = $param->{name
};
2216 if ($oldconf->{name
}) {
2217 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2219 $newconf->{name
} = "Copy-of-VM-$vmid";
2223 if ($param->{description
}) {
2224 $newconf->{description
} = $param->{description
};
2227 # create empty/temp config - this fails if VM already exists on other node
2228 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2233 my $newvollist = [];
2236 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2238 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2240 foreach my $opt (keys %$drives) {
2241 my $drive = $drives->{$opt};
2243 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2244 $newid, $storage, $format, $drive->{full
}, $newvollist);
2246 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2248 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2251 delete $newconf->{lock};
2252 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2255 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2256 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2258 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2259 die "Failed to move config to node '$target' - rename failed: $!\n"
2260 if !rename($conffile, $newconffile);
2263 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2268 sleep 1; # some storage like rbd need to wait before release volume - really?
2270 foreach my $volid (@$newvollist) {
2271 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2274 die "clone failed: $err";
2280 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2283 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2284 # Aquire exclusive lock lock for $newid
2285 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2290 __PACKAGE__-
>register_method({
2291 name
=> 'move_vm_disk',
2292 path
=> '{vmid}/move_disk',
2296 description
=> "Move volume to different storage.",
2298 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2299 "and 'Datastore.AllocateSpace' permissions on the storage.",
2302 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2303 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2307 additionalProperties
=> 0,
2309 node
=> get_standard_option
('pve-node'),
2310 vmid
=> get_standard_option
('pve-vmid'),
2313 description
=> "The disk you want to move.",
2314 enum
=> [ PVE
::QemuServer
::disknames
() ],
2316 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2319 description
=> "Target Format.",
2320 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2325 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2331 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2339 description
=> "the task ID.",
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $digest = extract_param
($param, 'digest');
2354 my $disk = extract_param
($param, 'disk');
2356 my $storeid = extract_param
($param, 'storage');
2358 my $format = extract_param
($param, 'format');
2360 my $storecfg = PVE
::Storage
::config
();
2362 my $updatefn = sub {
2364 my $conf = PVE
::QemuServer
::load_config
($vmid);
2366 die "checksum missmatch (file change by other user?)\n"
2367 if $digest && $digest ne $conf->{digest
};
2369 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2371 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2373 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2375 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2378 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2379 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2383 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2384 (!$format || !$oldfmt || $oldfmt eq $format);
2386 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2388 my $running = PVE
::QemuServer
::check_running
($vmid);
2390 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2394 my $newvollist = [];
2397 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2399 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2400 $vmid, $storeid, $format, 1, $newvollist);
2402 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2404 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2406 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2410 foreach my $volid (@$newvollist) {
2411 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2414 die "storage migration failed: $err";
2417 if ($param->{delete}) {
2418 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2423 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2426 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2429 __PACKAGE__-
>register_method({
2430 name
=> 'migrate_vm',
2431 path
=> '{vmid}/migrate',
2435 description
=> "Migrate virtual machine. Creates a new migration task.",
2437 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2440 additionalProperties
=> 0,
2442 node
=> get_standard_option
('pve-node'),
2443 vmid
=> get_standard_option
('pve-vmid'),
2444 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2447 description
=> "Use online/live migration.",
2452 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2459 description
=> "the task ID.",
2464 my $rpcenv = PVE
::RPCEnvironment
::get
();
2466 my $authuser = $rpcenv->get_user();
2468 my $target = extract_param
($param, 'target');
2470 my $localnode = PVE
::INotify
::nodename
();
2471 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2473 PVE
::Cluster
::check_cfs_quorum
();
2475 PVE
::Cluster
::check_node_exists
($target);
2477 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2479 my $vmid = extract_param
($param, 'vmid');
2481 raise_param_exc
({ force
=> "Only root may use this option." })
2482 if $param->{force
} && $authuser ne 'root@pam';
2485 my $conf = PVE
::QemuServer
::load_config
($vmid);
2487 # try to detect errors early
2489 PVE
::QemuServer
::check_lock
($conf);
2491 if (PVE
::QemuServer
::check_running
($vmid)) {
2492 die "cant migrate running VM without --online\n"
2493 if !$param->{online
};
2496 my $storecfg = PVE
::Storage
::config
();
2497 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2499 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2504 my $service = "pvevm:$vmid";
2506 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2508 print "Executing HA migrate for VM $vmid to node $target\n";
2510 PVE
::Tools
::run_command
($cmd);
2515 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2522 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2525 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2530 __PACKAGE__-
>register_method({
2532 path
=> '{vmid}/monitor',
2536 description
=> "Execute Qemu monitor commands.",
2538 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2541 additionalProperties
=> 0,
2543 node
=> get_standard_option
('pve-node'),
2544 vmid
=> get_standard_option
('pve-vmid'),
2547 description
=> "The monitor command.",
2551 returns
=> { type
=> 'string'},
2555 my $vmid = $param->{vmid
};
2557 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2561 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2563 $res = "ERROR: $@" if $@;
2568 __PACKAGE__-
>register_method({
2569 name
=> 'resize_vm',
2570 path
=> '{vmid}/resize',
2574 description
=> "Extend volume size.",
2576 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2583 skiplock
=> get_standard_option
('skiplock'),
2586 description
=> "The disk you want to resize.",
2587 enum
=> [PVE
::QemuServer
::disknames
()],
2591 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2592 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.",
2596 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2602 returns
=> { type
=> 'null'},
2606 my $rpcenv = PVE
::RPCEnvironment
::get
();
2608 my $authuser = $rpcenv->get_user();
2610 my $node = extract_param
($param, 'node');
2612 my $vmid = extract_param
($param, 'vmid');
2614 my $digest = extract_param
($param, 'digest');
2616 my $disk = extract_param
($param, 'disk');
2618 my $sizestr = extract_param
($param, 'size');
2620 my $skiplock = extract_param
($param, 'skiplock');
2621 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2622 if $skiplock && $authuser ne 'root@pam';
2624 my $storecfg = PVE
::Storage
::config
();
2626 my $updatefn = sub {
2628 my $conf = PVE
::QemuServer
::load_config
($vmid);
2630 die "checksum missmatch (file change by other user?)\n"
2631 if $digest && $digest ne $conf->{digest
};
2632 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2634 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2636 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2638 my $volid = $drive->{file
};
2640 die "disk '$disk' has no associated volume\n" if !$volid;
2642 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2644 die "you can't online resize a virtio windows bootdisk\n"
2645 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2647 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2649 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2651 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2653 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2654 my ($ext, $newsize, $unit) = ($1, $2, $4);
2657 $newsize = $newsize * 1024;
2658 } elsif ($unit eq 'M') {
2659 $newsize = $newsize * 1024 * 1024;
2660 } elsif ($unit eq 'G') {
2661 $newsize = $newsize * 1024 * 1024 * 1024;
2662 } elsif ($unit eq 'T') {
2663 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2666 $newsize += $size if $ext;
2667 $newsize = int($newsize);
2669 die "unable to skrink disk size\n" if $newsize < $size;
2671 return if $size == $newsize;
2673 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2675 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2677 $drive->{size
} = $newsize;
2678 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2680 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2683 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2687 __PACKAGE__-
>register_method({
2688 name
=> 'snapshot_list',
2689 path
=> '{vmid}/snapshot',
2691 description
=> "List all snapshots.",
2693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2696 protected
=> 1, # qemu pid files are only readable by root
2698 additionalProperties
=> 0,
2700 vmid
=> get_standard_option
('pve-vmid'),
2701 node
=> get_standard_option
('pve-node'),
2710 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2715 my $vmid = $param->{vmid
};
2717 my $conf = PVE
::QemuServer
::load_config
($vmid);
2718 my $snaphash = $conf->{snapshots
} || {};
2722 foreach my $name (keys %$snaphash) {
2723 my $d = $snaphash->{$name};
2726 snaptime
=> $d->{snaptime
} || 0,
2727 vmstate
=> $d->{vmstate
} ?
1 : 0,
2728 description
=> $d->{description
} || '',
2730 $item->{parent
} = $d->{parent
} if $d->{parent
};
2731 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2735 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2736 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2737 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2739 push @$res, $current;
2744 __PACKAGE__-
>register_method({
2746 path
=> '{vmid}/snapshot',
2750 description
=> "Snapshot a VM.",
2752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2755 additionalProperties
=> 0,
2757 node
=> get_standard_option
('pve-node'),
2758 vmid
=> get_standard_option
('pve-vmid'),
2759 snapname
=> get_standard_option
('pve-snapshot-name'),
2763 description
=> "Save the vmstate",
2768 description
=> "Freeze the filesystem",
2773 description
=> "A textual description or comment.",
2779 description
=> "the task ID.",
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2786 my $authuser = $rpcenv->get_user();
2788 my $node = extract_param
($param, 'node');
2790 my $vmid = extract_param
($param, 'vmid');
2792 my $snapname = extract_param
($param, 'snapname');
2794 die "unable to use snapshot name 'current' (reserved name)\n"
2795 if $snapname eq 'current';
2798 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2799 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2800 $param->{freezefs
}, $param->{description
});
2803 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2806 __PACKAGE__-
>register_method({
2807 name
=> 'snapshot_cmd_idx',
2808 path
=> '{vmid}/snapshot/{snapname}',
2815 additionalProperties
=> 0,
2817 vmid
=> get_standard_option
('pve-vmid'),
2818 node
=> get_standard_option
('pve-node'),
2819 snapname
=> get_standard_option
('pve-snapshot-name'),
2828 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2835 push @$res, { cmd
=> 'rollback' };
2836 push @$res, { cmd
=> 'config' };
2841 __PACKAGE__-
>register_method({
2842 name
=> 'update_snapshot_config',
2843 path
=> '{vmid}/snapshot/{snapname}/config',
2847 description
=> "Update snapshot metadata.",
2849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2852 additionalProperties
=> 0,
2854 node
=> get_standard_option
('pve-node'),
2855 vmid
=> get_standard_option
('pve-vmid'),
2856 snapname
=> get_standard_option
('pve-snapshot-name'),
2860 description
=> "A textual description or comment.",
2864 returns
=> { type
=> 'null' },
2868 my $rpcenv = PVE
::RPCEnvironment
::get
();
2870 my $authuser = $rpcenv->get_user();
2872 my $vmid = extract_param
($param, 'vmid');
2874 my $snapname = extract_param
($param, 'snapname');
2876 return undef if !defined($param->{description
});
2878 my $updatefn = sub {
2880 my $conf = PVE
::QemuServer
::load_config
($vmid);
2882 PVE
::QemuServer
::check_lock
($conf);
2884 my $snap = $conf->{snapshots
}->{$snapname};
2886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2888 $snap->{description
} = $param->{description
} if defined($param->{description
});
2890 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2893 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2898 __PACKAGE__-
>register_method({
2899 name
=> 'get_snapshot_config',
2900 path
=> '{vmid}/snapshot/{snapname}/config',
2903 description
=> "Get snapshot configuration",
2905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2908 additionalProperties
=> 0,
2910 node
=> get_standard_option
('pve-node'),
2911 vmid
=> get_standard_option
('pve-vmid'),
2912 snapname
=> get_standard_option
('pve-snapshot-name'),
2915 returns
=> { type
=> "object" },
2919 my $rpcenv = PVE
::RPCEnvironment
::get
();
2921 my $authuser = $rpcenv->get_user();
2923 my $vmid = extract_param
($param, 'vmid');
2925 my $snapname = extract_param
($param, 'snapname');
2927 my $conf = PVE
::QemuServer
::load_config
($vmid);
2929 my $snap = $conf->{snapshots
}->{$snapname};
2931 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2936 __PACKAGE__-
>register_method({
2938 path
=> '{vmid}/snapshot/{snapname}/rollback',
2942 description
=> "Rollback VM state to specified snapshot.",
2944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2947 additionalProperties
=> 0,
2949 node
=> get_standard_option
('pve-node'),
2950 vmid
=> get_standard_option
('pve-vmid'),
2951 snapname
=> get_standard_option
('pve-snapshot-name'),
2956 description
=> "the task ID.",
2961 my $rpcenv = PVE
::RPCEnvironment
::get
();
2963 my $authuser = $rpcenv->get_user();
2965 my $node = extract_param
($param, 'node');
2967 my $vmid = extract_param
($param, 'vmid');
2969 my $snapname = extract_param
($param, 'snapname');
2972 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2973 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2976 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2979 __PACKAGE__-
>register_method({
2980 name
=> 'delsnapshot',
2981 path
=> '{vmid}/snapshot/{snapname}',
2985 description
=> "Delete a VM snapshot.",
2987 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2990 additionalProperties
=> 0,
2992 node
=> get_standard_option
('pve-node'),
2993 vmid
=> get_standard_option
('pve-vmid'),
2994 snapname
=> get_standard_option
('pve-snapshot-name'),
2998 description
=> "For removal from config file, even if removing disk snapshots fails.",
3004 description
=> "the task ID.",
3009 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3013 my $node = extract_param
($param, 'node');
3015 my $vmid = extract_param
($param, 'vmid');
3017 my $snapname = extract_param
($param, 'snapname');
3020 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3021 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3024 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3027 __PACKAGE__-
>register_method({
3029 path
=> '{vmid}/template',
3033 description
=> "Create a Template.",
3035 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3036 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3039 additionalProperties
=> 0,
3041 node
=> get_standard_option
('pve-node'),
3042 vmid
=> get_standard_option
('pve-vmid'),
3046 description
=> "If you want to convert only 1 disk to base image.",
3047 enum
=> [PVE
::QemuServer
::disknames
()],
3052 returns
=> { type
=> 'null'},
3056 my $rpcenv = PVE
::RPCEnvironment
::get
();
3058 my $authuser = $rpcenv->get_user();
3060 my $node = extract_param
($param, 'node');
3062 my $vmid = extract_param
($param, 'vmid');
3064 my $disk = extract_param
($param, 'disk');
3066 my $updatefn = sub {
3068 my $conf = PVE
::QemuServer
::load_config
($vmid);
3070 PVE
::QemuServer
::check_lock
($conf);
3072 die "unable to create template, because VM contains snapshots\n"
3073 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3075 die "you can't convert a template to a template\n"
3076 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3078 die "you can't convert a VM to template if VM is running\n"
3079 if PVE
::QemuServer
::check_running
($vmid);
3082 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3085 $conf->{template
} = 1;
3086 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3088 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3091 PVE
::QemuServer
::lock_config
($vmid, $updatefn);