1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 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.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
133 my $foundvolid = undef;
136 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
137 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
139 PVE
::Storage
::foreach_volid
($dl, sub {
141 if($volumeid eq $volid) {
148 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
150 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
151 $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' ||
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 my ($vmid, $pool) = @_;
255 my $addVMtoPoolFn = sub {
256 my $usercfg = cfs_read_file
("user.cfg");
257 if (my $data = $usercfg->{pools
}->{$pool}) {
258 $data->{vms
}->{$vmid} = 1;
259 $usercfg->{vms
}->{$vmid} = $pool;
260 cfs_write_file
("user.cfg", $usercfg);
264 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM $vmid to pool '$pool'");
267 sub remove_vm_from_pool
{
270 my $delVMfromPoolFn = sub {
271 my $usercfg = cfs_read_file
("user.cfg");
272 if (my $pool = $usercfg->{vms
}->{$vmid}) {
273 if (my $data = $usercfg->{pools
}->{$pool}) {
274 delete $data->{vms
}->{$vmid};
275 delete $usercfg->{vms
}->{$vmid};
276 cfs_write_file
("user.cfg", $usercfg);
281 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup for VM $vmid failed");
284 __PACKAGE__-
>register_method({
288 description
=> "Create or restore a virtual machine.",
290 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
291 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
292 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
293 user
=> 'all', # check inside
298 additionalProperties
=> 0,
299 properties
=> PVE
::QemuServer
::json_config_properties
(
301 node
=> get_standard_option
('pve-node'),
302 vmid
=> get_standard_option
('pve-vmid'),
304 description
=> "The backup file.",
309 storage
=> get_standard_option
('pve-storage-id', {
310 description
=> "Default storage.",
316 description
=> "Allow to overwrite existing VM.",
317 requires
=> 'archive',
322 description
=> "Assign a unique random ethernet address.",
323 requires
=> 'archive',
327 type
=> 'string', format
=> 'pve-poolid',
328 description
=> "Add the VM to the specified pool.",
338 my $rpcenv = PVE
::RPCEnvironment
::get
();
340 my $authuser = $rpcenv->get_user();
342 my $node = extract_param
($param, 'node');
344 my $vmid = extract_param
($param, 'vmid');
346 my $archive = extract_param
($param, 'archive');
348 my $storage = extract_param
($param, 'storage');
350 my $force = extract_param
($param, 'force');
352 my $unique = extract_param
($param, 'unique');
354 my $pool = extract_param
($param, 'pool');
356 my $filename = PVE
::QemuServer
::config_file
($vmid);
358 my $storecfg = PVE
::Storage
::config
();
360 PVE
::Cluster
::check_cfs_quorum
();
362 if (defined($pool)) {
363 $rpcenv->check_pool_exist($pool);
366 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
367 if defined($storage);
369 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
371 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
373 } elsif ($archive && $force && (-f
$filename) &&
374 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
375 # OK: user has VM.Backup permissions, and want to restore an existing VM
381 &$resolve_cdrom_alias($param);
383 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
385 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
387 foreach my $opt (keys %$param) {
388 if (PVE
::QemuServer
::valid_drivename
($opt)) {
389 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
390 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
392 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
393 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
397 PVE
::QemuServer
::add_random_macs
($param);
399 my $keystr = join(' ', keys %$param);
400 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
402 if ($archive eq '-') {
403 die "pipe requires cli environment\n"
404 if $rpcenv->{type
} ne 'cli';
406 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
408 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
409 if PVE
::Storage
::parse_volume_id
($archive, 1);
411 die "can't find archive file '$archive'\n" if !($path && -f
$path);
416 my $restorefn = sub {
418 # fixme: this test does not work if VM exists on other node!
420 die "unable to restore vm $vmid: config file already exists\n"
423 die "unable to restore vm $vmid: vm is running\n"
424 if PVE
::QemuServer
::check_running
($vmid);
428 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
431 unique
=> $unique });
433 add_vm_to_pool
($vmid, $pool) if $pool;
436 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
442 die "unable to create vm $vmid: config file already exists\n"
453 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
455 # try to be smart about bootdisk
456 my @disks = PVE
::QemuServer
::disknames
();
458 foreach my $ds (reverse @disks) {
459 next if !$conf->{$ds};
460 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
461 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
465 if (!$conf->{bootdisk
} && $firstdisk) {
466 $conf->{bootdisk
} = $firstdisk;
469 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
475 foreach my $volid (@$vollist) {
476 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
479 die "create failed - $err";
482 add_vm_to_pool
($vmid, $pool) if $pool;
485 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
488 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
491 __PACKAGE__-
>register_method({
496 description
=> "Directory index",
501 additionalProperties
=> 0,
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid'),
512 subdir
=> { type
=> 'string' },
515 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
521 { subdir
=> 'config' },
522 { subdir
=> 'status' },
523 { subdir
=> 'unlink' },
524 { subdir
=> 'vncproxy' },
525 { subdir
=> 'migrate' },
526 { subdir
=> 'resize' },
528 { subdir
=> 'rrddata' },
529 { subdir
=> 'monitor' },
530 { subdir
=> 'snapshot' },
536 __PACKAGE__-
>register_method({
538 path
=> '{vmid}/rrd',
540 protected
=> 1, # fixme: can we avoid that?
542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
544 description
=> "Read VM RRD statistics (returns PNG)",
546 additionalProperties
=> 0,
548 node
=> get_standard_option
('pve-node'),
549 vmid
=> get_standard_option
('pve-vmid'),
551 description
=> "Specify the time frame you are interested in.",
553 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
556 description
=> "The list of datasources you want to display.",
557 type
=> 'string', format
=> 'pve-configid-list',
560 description
=> "The RRD consolidation function",
562 enum
=> [ 'AVERAGE', 'MAX' ],
570 filename
=> { type
=> 'string' },
576 return PVE
::Cluster
::create_rrd_graph
(
577 "pve2-vm/$param->{vmid}", $param->{timeframe
},
578 $param->{ds
}, $param->{cf
});
582 __PACKAGE__-
>register_method({
584 path
=> '{vmid}/rrddata',
586 protected
=> 1, # fixme: can we avoid that?
588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
590 description
=> "Read VM RRD statistics",
592 additionalProperties
=> 0,
594 node
=> get_standard_option
('pve-node'),
595 vmid
=> get_standard_option
('pve-vmid'),
597 description
=> "Specify the time frame you are interested in.",
599 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
602 description
=> "The RRD consolidation function",
604 enum
=> [ 'AVERAGE', 'MAX' ],
619 return PVE
::Cluster
::create_rrd_data
(
620 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
624 __PACKAGE__-
>register_method({
626 path
=> '{vmid}/config',
629 description
=> "Get virtual machine configuration.",
631 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid'),
645 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
652 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
654 delete $conf->{snapshots
};
659 my $vm_is_volid_owner = sub {
660 my ($storecfg, $vmid, $volid) =@_;
662 if ($volid !~ m
|^/|) {
664 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
665 if ($owner && ($owner == $vmid)) {
673 my $test_deallocate_drive = sub {
674 my ($storecfg, $vmid, $key, $drive, $force) = @_;
676 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
677 my $volid = $drive->{file
};
678 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
679 if ($force || $key =~ m/^unused/) {
680 my $sid = PVE
::Storage
::parse_volume_id
($volid);
689 my $delete_drive = sub {
690 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
692 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
693 my $volid = $drive->{file
};
694 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
695 if ($force || $key =~ m/^unused/) {
696 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
699 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
704 delete $conf->{$key};
707 my $vmconfig_delete_option = sub {
708 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
710 return if !defined($conf->{$opt});
712 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
715 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
717 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
718 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
719 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
723 my $unplugwarning = "";
724 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
725 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
726 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
727 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
728 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
729 $unplugwarning = "<br>verify that your guest support acpi hotplug";
732 if($opt eq 'tablet'){
733 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
735 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
739 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
740 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
742 delete $conf->{$opt};
745 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
748 my $safe_num_ne = sub {
751 return 0 if !defined($a) && !defined($b);
752 return 1 if !defined($a);
753 return 1 if !defined($b);
758 my $vmconfig_update_disk = sub {
759 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
761 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
763 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
764 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
766 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
771 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
773 my $media = $drive->{media
} || 'disk';
774 my $oldmedia = $old_drive->{media
} || 'disk';
775 die "unable to change media type\n" if $media ne $oldmedia;
777 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
778 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
780 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
784 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
785 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
786 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
787 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
788 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
789 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
790 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
791 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
792 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
793 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
798 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
799 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
801 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
802 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
804 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
806 if (PVE
::QemuServer
::check_running
($vmid)) {
807 if ($drive->{file
} eq 'none') {
808 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
810 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
811 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
812 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
816 } else { # hotplug new disks
818 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
822 my $vmconfig_update_net = sub {
823 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
825 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
826 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
827 my $newnet = PVE
::QemuServer
::parse_net
($value);
829 if($oldnet->{model
} ne $newnet->{model
}){
830 #if model change, we try to hot-unplug
831 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
834 if($newnet->{bridge
} && $oldnet->{bridge
}){
835 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
837 if($newnet->{rate
} ne $oldnet->{rate
}){
838 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
841 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
842 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
843 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
847 #if bridge/nat mode change, we try to hot-unplug
848 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
853 $conf->{$opt} = $value;
854 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
855 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
857 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
859 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
862 my $vm_config_perm_list = [
872 __PACKAGE__-
>register_method({
874 path
=> '{vmid}/config',
878 description
=> "Set virtual machine options.",
880 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
883 additionalProperties
=> 0,
884 properties
=> PVE
::QemuServer
::json_config_properties
(
886 node
=> get_standard_option
('pve-node'),
887 vmid
=> get_standard_option
('pve-vmid'),
888 skiplock
=> get_standard_option
('skiplock'),
890 type
=> 'string', format
=> 'pve-configid-list',
891 description
=> "A list of settings you want to delete.",
896 description
=> $opt_force_description,
898 requires
=> 'delete',
902 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
908 returns
=> { type
=> 'null'},
912 my $rpcenv = PVE
::RPCEnvironment
::get
();
914 my $authuser = $rpcenv->get_user();
916 my $node = extract_param
($param, 'node');
918 my $vmid = extract_param
($param, 'vmid');
920 my $digest = extract_param
($param, 'digest');
922 my @paramarr = (); # used for log message
923 foreach my $key (keys %$param) {
924 push @paramarr, "-$key", $param->{$key};
927 my $skiplock = extract_param
($param, 'skiplock');
928 raise_param_exc
({ skiplock
=> "Only root may use this option." })
929 if $skiplock && $authuser ne 'root@pam';
931 my $delete_str = extract_param
($param, 'delete');
933 my $force = extract_param
($param, 'force');
935 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
937 my $storecfg = PVE
::Storage
::config
();
939 my $defaults = PVE
::QemuServer
::load_defaults
();
941 &$resolve_cdrom_alias($param);
943 # now try to verify all parameters
946 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
947 $opt = 'ide2' if $opt eq 'cdrom';
948 raise_param_exc
({ delete => "you can't use '-$opt' and " .
949 "-delete $opt' at the same time" })
950 if defined($param->{$opt});
952 if (!PVE
::QemuServer
::option_exists
($opt)) {
953 raise_param_exc
({ delete => "unknown option '$opt'" });
959 foreach my $opt (keys %$param) {
960 if (PVE
::QemuServer
::valid_drivename
($opt)) {
962 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
963 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
964 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
965 } elsif ($opt =~ m/^net(\d+)$/) {
967 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
968 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
972 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
974 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
976 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
980 my $conf = PVE
::QemuServer
::load_config
($vmid);
982 die "checksum missmatch (file change by other user?)\n"
983 if $digest && $digest ne $conf->{digest
};
985 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
987 if ($param->{memory
} || defined($param->{balloon
})) {
988 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
989 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
991 die "balloon value too large (must be smaller than assigned memory)\n"
992 if $balloon > $maxmem;
995 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
997 foreach my $opt (@delete) { # delete
998 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
999 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
1002 my $running = PVE
::QemuServer
::check_running
($vmid);
1004 foreach my $opt (keys %$param) { # add/change
1006 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
1008 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
1010 if (PVE
::QemuServer
::valid_drivename
($opt)) {
1012 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
1013 $opt, $param->{$opt}, $force);
1015 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1017 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1018 $opt, $param->{$opt});
1022 if($opt eq 'tablet' && $param->{$opt} == 1){
1023 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1024 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
1025 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1028 $conf->{$opt} = $param->{$opt};
1029 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1033 # allow manual ballooning if shares is set to zero
1034 if ($running && defined($param->{balloon
}) &&
1035 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1036 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1037 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1042 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1048 __PACKAGE__-
>register_method({
1049 name
=> 'destroy_vm',
1054 description
=> "Destroy the vm (also delete all used/owned volumes).",
1056 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1059 additionalProperties
=> 0,
1061 node
=> get_standard_option
('pve-node'),
1062 vmid
=> get_standard_option
('pve-vmid'),
1063 skiplock
=> get_standard_option
('skiplock'),
1072 my $rpcenv = PVE
::RPCEnvironment
::get
();
1074 my $authuser = $rpcenv->get_user();
1076 my $vmid = $param->{vmid
};
1078 my $skiplock = $param->{skiplock
};
1079 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1080 if $skiplock && $authuser ne 'root@pam';
1083 my $conf = PVE
::QemuServer
::load_config
($vmid);
1085 my $storecfg = PVE
::Storage
::config
();
1087 my $delVMfromPoolFn = sub {
1088 my $usercfg = cfs_read_file
("user.cfg");
1089 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1090 if (my $data = $usercfg->{pools
}->{$pool}) {
1091 delete $data->{vms
}->{$vmid};
1092 delete $usercfg->{vms
}->{$vmid};
1093 cfs_write_file
("user.cfg", $usercfg);
1101 syslog
('info', "destroy VM $vmid: $upid\n");
1103 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1105 remove_vm_from_pool
($vmid);
1108 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1111 __PACKAGE__-
>register_method({
1113 path
=> '{vmid}/unlink',
1117 description
=> "Unlink/delete disk images.",
1119 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1122 additionalProperties
=> 0,
1124 node
=> get_standard_option
('pve-node'),
1125 vmid
=> get_standard_option
('pve-vmid'),
1127 type
=> 'string', format
=> 'pve-configid-list',
1128 description
=> "A list of disk IDs you want to delete.",
1132 description
=> $opt_force_description,
1137 returns
=> { type
=> 'null'},
1141 $param->{delete} = extract_param
($param, 'idlist');
1143 __PACKAGE__-
>update_vm($param);
1150 __PACKAGE__-
>register_method({
1152 path
=> '{vmid}/vncproxy',
1156 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1158 description
=> "Creates a TCP VNC proxy connections.",
1160 additionalProperties
=> 0,
1162 node
=> get_standard_option
('pve-node'),
1163 vmid
=> get_standard_option
('pve-vmid'),
1167 additionalProperties
=> 0,
1169 user
=> { type
=> 'string' },
1170 ticket
=> { type
=> 'string' },
1171 cert
=> { type
=> 'string' },
1172 port
=> { type
=> 'integer' },
1173 upid
=> { type
=> 'string' },
1179 my $rpcenv = PVE
::RPCEnvironment
::get
();
1181 my $authuser = $rpcenv->get_user();
1183 my $vmid = $param->{vmid
};
1184 my $node = $param->{node
};
1186 my $authpath = "/vms/$vmid";
1188 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1190 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1193 my $port = PVE
::Tools
::next_vnc_port
();
1197 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1198 $remip = PVE
::Cluster
::remote_node_ip
($node);
1201 # NOTE: kvm VNC traffic is already TLS encrypted
1202 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1209 syslog
('info', "starting vnc proxy $upid\n");
1211 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1213 my $qmstr = join(' ', @$qmcmd);
1215 # also redirect stderr (else we get RFB protocol errors)
1216 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1218 PVE
::Tools
::run_command
($cmd);
1223 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1225 PVE
::Tools
::wait_for_vnc_port
($port);
1236 __PACKAGE__-
>register_method({
1238 path
=> '{vmid}/status',
1241 description
=> "Directory index",
1246 additionalProperties
=> 0,
1248 node
=> get_standard_option
('pve-node'),
1249 vmid
=> get_standard_option
('pve-vmid'),
1257 subdir
=> { type
=> 'string' },
1260 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1266 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1269 { subdir
=> 'current' },
1270 { subdir
=> 'start' },
1271 { subdir
=> 'stop' },
1277 my $vm_is_ha_managed = sub {
1280 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1281 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1287 __PACKAGE__-
>register_method({
1288 name
=> 'vm_status',
1289 path
=> '{vmid}/status/current',
1292 protected
=> 1, # qemu pid files are only readable by root
1293 description
=> "Get virtual machine status.",
1295 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1298 additionalProperties
=> 0,
1300 node
=> get_standard_option
('pve-node'),
1301 vmid
=> get_standard_option
('pve-vmid'),
1304 returns
=> { type
=> 'object' },
1309 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1311 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1312 my $status = $vmstatus->{$param->{vmid
}};
1314 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1319 __PACKAGE__-
>register_method({
1321 path
=> '{vmid}/status/start',
1325 description
=> "Start virtual machine.",
1327 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1330 additionalProperties
=> 0,
1332 node
=> get_standard_option
('pve-node'),
1333 vmid
=> get_standard_option
('pve-vmid'),
1334 skiplock
=> get_standard_option
('skiplock'),
1335 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1336 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1346 my $rpcenv = PVE
::RPCEnvironment
::get
();
1348 my $authuser = $rpcenv->get_user();
1350 my $node = extract_param
($param, 'node');
1352 my $vmid = extract_param
($param, 'vmid');
1354 my $stateuri = extract_param
($param, 'stateuri');
1355 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1356 if $stateuri && $authuser ne 'root@pam';
1358 my $skiplock = extract_param
($param, 'skiplock');
1359 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1360 if $skiplock && $authuser ne 'root@pam';
1362 my $migratedfrom = extract_param
($param, 'migratedfrom');
1363 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1364 if $migratedfrom && $authuser ne 'root@pam';
1366 my $storecfg = PVE
::Storage
::config
();
1368 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1369 $rpcenv->{type
} ne 'ha') {
1374 my $service = "pvevm:$vmid";
1376 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1378 print "Executing HA start for VM $vmid\n";
1380 PVE
::Tools
::run_command
($cmd);
1385 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1392 syslog
('info', "start VM $vmid: $upid\n");
1394 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1399 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1403 __PACKAGE__-
>register_method({
1405 path
=> '{vmid}/status/stop',
1409 description
=> "Stop virtual machine.",
1411 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1414 additionalProperties
=> 0,
1416 node
=> get_standard_option
('pve-node'),
1417 vmid
=> get_standard_option
('pve-vmid'),
1418 skiplock
=> get_standard_option
('skiplock'),
1419 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1421 description
=> "Wait maximal timeout seconds.",
1427 description
=> "Do not decativate storage volumes.",
1440 my $rpcenv = PVE
::RPCEnvironment
::get
();
1442 my $authuser = $rpcenv->get_user();
1444 my $node = extract_param
($param, 'node');
1446 my $vmid = extract_param
($param, 'vmid');
1448 my $skiplock = extract_param
($param, 'skiplock');
1449 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1450 if $skiplock && $authuser ne 'root@pam';
1452 my $keepActive = extract_param
($param, 'keepActive');
1453 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1454 if $keepActive && $authuser ne 'root@pam';
1456 my $migratedfrom = extract_param
($param, 'migratedfrom');
1457 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1458 if $migratedfrom && $authuser ne 'root@pam';
1461 my $storecfg = PVE
::Storage
::config
();
1463 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1468 my $service = "pvevm:$vmid";
1470 my $cmd = ['clusvcadm', '-d', $service];
1472 print "Executing HA stop for VM $vmid\n";
1474 PVE
::Tools
::run_command
($cmd);
1479 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1485 syslog
('info', "stop VM $vmid: $upid\n");
1487 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1488 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1493 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1497 __PACKAGE__-
>register_method({
1499 path
=> '{vmid}/status/reset',
1503 description
=> "Reset 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'),
1521 my $rpcenv = PVE
::RPCEnvironment
::get
();
1523 my $authuser = $rpcenv->get_user();
1525 my $node = extract_param
($param, 'node');
1527 my $vmid = extract_param
($param, 'vmid');
1529 my $skiplock = extract_param
($param, 'skiplock');
1530 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1531 if $skiplock && $authuser ne 'root@pam';
1533 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1538 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1543 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1546 __PACKAGE__-
>register_method({
1547 name
=> 'vm_shutdown',
1548 path
=> '{vmid}/status/shutdown',
1552 description
=> "Shutdown virtual machine.",
1554 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid'),
1561 skiplock
=> get_standard_option
('skiplock'),
1563 description
=> "Wait maximal timeout seconds.",
1569 description
=> "Make sure the VM stops.",
1575 description
=> "Do not decativate storage volumes.",
1588 my $rpcenv = PVE
::RPCEnvironment
::get
();
1590 my $authuser = $rpcenv->get_user();
1592 my $node = extract_param
($param, 'node');
1594 my $vmid = extract_param
($param, 'vmid');
1596 my $skiplock = extract_param
($param, 'skiplock');
1597 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1598 if $skiplock && $authuser ne 'root@pam';
1600 my $keepActive = extract_param
($param, 'keepActive');
1601 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1602 if $keepActive && $authuser ne 'root@pam';
1604 my $storecfg = PVE
::Storage
::config
();
1609 syslog
('info', "shutdown VM $vmid: $upid\n");
1611 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1612 1, $param->{forceStop
}, $keepActive);
1617 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1620 __PACKAGE__-
>register_method({
1621 name
=> 'vm_suspend',
1622 path
=> '{vmid}/status/suspend',
1626 description
=> "Suspend virtual machine.",
1628 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1631 additionalProperties
=> 0,
1633 node
=> get_standard_option
('pve-node'),
1634 vmid
=> get_standard_option
('pve-vmid'),
1635 skiplock
=> get_standard_option
('skiplock'),
1644 my $rpcenv = PVE
::RPCEnvironment
::get
();
1646 my $authuser = $rpcenv->get_user();
1648 my $node = extract_param
($param, 'node');
1650 my $vmid = extract_param
($param, 'vmid');
1652 my $skiplock = extract_param
($param, 'skiplock');
1653 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1654 if $skiplock && $authuser ne 'root@pam';
1656 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1661 syslog
('info', "suspend VM $vmid: $upid\n");
1663 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1668 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1671 __PACKAGE__-
>register_method({
1672 name
=> 'vm_resume',
1673 path
=> '{vmid}/status/resume',
1677 description
=> "Resume virtual machine.",
1679 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1682 additionalProperties
=> 0,
1684 node
=> get_standard_option
('pve-node'),
1685 vmid
=> get_standard_option
('pve-vmid'),
1686 skiplock
=> get_standard_option
('skiplock'),
1695 my $rpcenv = PVE
::RPCEnvironment
::get
();
1697 my $authuser = $rpcenv->get_user();
1699 my $node = extract_param
($param, 'node');
1701 my $vmid = extract_param
($param, 'vmid');
1703 my $skiplock = extract_param
($param, 'skiplock');
1704 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1705 if $skiplock && $authuser ne 'root@pam';
1707 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1712 syslog
('info', "resume VM $vmid: $upid\n");
1714 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1719 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1722 __PACKAGE__-
>register_method({
1723 name
=> 'vm_sendkey',
1724 path
=> '{vmid}/sendkey',
1728 description
=> "Send key event to virtual machine.",
1730 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1733 additionalProperties
=> 0,
1735 node
=> get_standard_option
('pve-node'),
1736 vmid
=> get_standard_option
('pve-vmid'),
1737 skiplock
=> get_standard_option
('skiplock'),
1739 description
=> "The key (qemu monitor encoding).",
1744 returns
=> { type
=> 'null'},
1748 my $rpcenv = PVE
::RPCEnvironment
::get
();
1750 my $authuser = $rpcenv->get_user();
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $skiplock = extract_param
($param, 'skiplock');
1757 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1758 if $skiplock && $authuser ne 'root@pam';
1760 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1765 __PACKAGE__-
>register_method({
1766 name
=> 'vm_feature',
1767 path
=> '{vmid}/feature',
1771 description
=> "Check if feature for virtual machine is available.",
1773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1776 additionalProperties
=> 0,
1778 node
=> get_standard_option
('pve-node'),
1779 vmid
=> get_standard_option
('pve-vmid'),
1781 description
=> "Feature to check.",
1783 enum
=> [ 'snapshot', 'clone' ],
1785 snapname
=> get_standard_option
('pve-snapshot-name', {
1796 my $node = extract_param
($param, 'node');
1798 my $vmid = extract_param
($param, 'vmid');
1800 my $snapname = extract_param
($param, 'snapname');
1802 my $feature = extract_param
($param, 'feature');
1804 my $running = PVE
::QemuServer
::check_running
($vmid);
1806 my $conf = PVE
::QemuServer
::load_config
($vmid);
1809 my $snap = $conf->{snapshots
}->{$snapname};
1810 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1813 my $storecfg = PVE
::Storage
::config
();
1815 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1816 my $res = $hasfeature ?
1 : 0 ;
1820 __PACKAGE__-
>register_method({
1822 path
=> '{vmid}/clone',
1826 description
=> "Create a copy of virtual machine/template.",
1828 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1829 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1830 "'Datastore.AllocateSpace' on any used storage.",
1833 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1835 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1836 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1841 additionalProperties
=> 0,
1843 node
=> get_standard_option
('pve-node'),
1844 vmid
=> get_standard_option
('pve-vmid'),
1845 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1848 type
=> 'string', format
=> 'dns-name',
1849 description
=> "Set a name for the new VM.",
1854 description
=> "Description for the new VM.",
1858 type
=> 'string', format
=> 'pve-poolid',
1859 description
=> "Add the new VM to the specified pool.",
1861 snapname
=> get_standard_option
('pve-snapshot-name', {
1865 storage
=> get_standard_option
('pve-storage-id', {
1866 description
=> "Target storage for full clone.",
1871 description
=> "Target format for file storage.",
1875 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1880 description
=> "Create a full copy of all disk. This is always done when " .
1881 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1884 target
=> get_standard_option
('pve-node', {
1885 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1896 my $rpcenv = PVE
::RPCEnvironment
::get
();
1898 my $authuser = $rpcenv->get_user();
1900 my $node = extract_param
($param, 'node');
1902 my $vmid = extract_param
($param, 'vmid');
1904 my $newid = extract_param
($param, 'newid');
1906 my $pool = extract_param
($param, 'pool');
1908 if (defined($pool)) {
1909 $rpcenv->check_pool_exist($pool);
1912 my $snapname = extract_param
($param, 'snapname');
1914 my $storage = extract_param
($param, 'storage');
1916 my $format = extract_param
($param, 'format');
1918 my $target = extract_param
($param, 'target');
1920 my $localnode = PVE
::INotify
::nodename
();
1922 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1924 PVE
::Cluster
::check_node_exists
($target) if $target;
1926 my $storecfg = PVE
::Storage
::config
();
1928 PVE
::Cluster
::check_cfs_quorum
();
1930 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1932 # exclusive lock if VM is running - else shared lock is enough;
1933 my $shared_lock = $running ?
0 : 1;
1937 # do all tests after lock
1938 # we also try to do all tests before we fork the worker
1940 my $conf = PVE
::QemuServer
::load_config
($vmid);
1942 PVE
::QemuServer
::check_lock
($conf);
1944 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1946 die "unexpected state change\n" if $verify_running != $running;
1948 die "snapshot '$snapname' does not exist\n"
1949 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1951 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1953 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1955 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1957 my $conffile = PVE
::QemuServer
::config_file
($newid);
1959 die "unable to create VM $newid: config file already exists\n"
1962 my $newconf = { lock => 'clone' };
1966 foreach my $opt (keys %$oldconf) {
1967 my $value = $oldconf->{$opt};
1969 # do not copy snapshot related info
1970 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1971 $opt eq 'vmstate' || $opt eq 'snapstate';
1973 # always change MAC! address
1974 if ($opt =~ m/^net(\d+)$/) {
1975 my $net = PVE
::QemuServer
::parse_net
($value);
1976 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1977 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1978 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1979 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1980 $newconf->{$opt} = $value; # simply copy configuration
1982 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1983 die "Full clone feature is not available"
1984 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1987 $drives->{$opt} = $drive;
1988 push @$vollist, $drive->{file
};
1991 # copy everything else
1992 $newconf->{$opt} = $value;
1996 delete $newconf->{template
};
1998 if ($param->{name
}) {
1999 $newconf->{name
} = $param->{name
};
2001 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2004 if ($param->{description
}) {
2005 $newconf->{description
} = $param->{description
};
2008 # create empty/temp config - this fails if VM already exists on other node
2009 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2014 my $newvollist = [];
2017 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2019 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2021 foreach my $opt (keys %$drives) {
2022 my $drive = $drives->{$opt};
2025 if (!$drive->{full
}) {
2026 print "create linked clone of drive $opt ($drive->{file})\n";
2027 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2028 push @$newvollist, $newvolid;
2031 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2032 $storeid = $storage if $storage;
2038 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2039 $fmt = $drive->{format
} || $defformat;
2042 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2044 print "create full clone of drive $opt ($drive->{file})\n";
2045 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2046 push @$newvollist, $newvolid;
2048 if(!$running || $snapname){
2049 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2051 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2056 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2057 my $disk = { file
=> $newvolid, size
=> $size };
2058 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2060 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2063 delete $newconf->{lock};
2064 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2067 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2068 die "Failed to move config to node '$target' - rename failed: $!\n"
2069 if !rename($conffile, $newconffile);
2072 add_vm_to_pool
($newid, $pool) if $pool;
2077 sleep 1; # some storage like rbd need to wait before release volume - really?
2079 foreach my $volid (@$newvollist) {
2080 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2083 die "clone failed: $err";
2089 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2092 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2093 # Aquire exclusive lock lock for $newid
2094 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2099 __PACKAGE__-
>register_method({
2100 name
=> 'migrate_vm',
2101 path
=> '{vmid}/migrate',
2105 description
=> "Migrate virtual machine. Creates a new migration task.",
2107 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2110 additionalProperties
=> 0,
2112 node
=> get_standard_option
('pve-node'),
2113 vmid
=> get_standard_option
('pve-vmid'),
2114 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2117 description
=> "Use online/live migration.",
2122 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2129 description
=> "the task ID.",
2134 my $rpcenv = PVE
::RPCEnvironment
::get
();
2136 my $authuser = $rpcenv->get_user();
2138 my $target = extract_param
($param, 'target');
2140 my $localnode = PVE
::INotify
::nodename
();
2141 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2143 PVE
::Cluster
::check_cfs_quorum
();
2145 PVE
::Cluster
::check_node_exists
($target);
2147 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2149 my $vmid = extract_param
($param, 'vmid');
2151 raise_param_exc
({ force
=> "Only root may use this option." })
2152 if $param->{force
} && $authuser ne 'root@pam';
2155 my $conf = PVE
::QemuServer
::load_config
($vmid);
2157 # try to detect errors early
2159 PVE
::QemuServer
::check_lock
($conf);
2161 if (PVE
::QemuServer
::check_running
($vmid)) {
2162 die "cant migrate running VM without --online\n"
2163 if !$param->{online
};
2166 my $storecfg = PVE
::Storage
::config
();
2167 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2169 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2174 my $service = "pvevm:$vmid";
2176 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2178 print "Executing HA migrate for VM $vmid to node $target\n";
2180 PVE
::Tools
::run_command
($cmd);
2185 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2192 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2195 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2200 __PACKAGE__-
>register_method({
2202 path
=> '{vmid}/monitor',
2206 description
=> "Execute Qemu monitor commands.",
2208 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2211 additionalProperties
=> 0,
2213 node
=> get_standard_option
('pve-node'),
2214 vmid
=> get_standard_option
('pve-vmid'),
2217 description
=> "The monitor command.",
2221 returns
=> { type
=> 'string'},
2225 my $vmid = $param->{vmid
};
2227 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2231 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2233 $res = "ERROR: $@" if $@;
2238 __PACKAGE__-
>register_method({
2239 name
=> 'resize_vm',
2240 path
=> '{vmid}/resize',
2244 description
=> "Extend volume size.",
2246 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2249 additionalProperties
=> 0,
2251 node
=> get_standard_option
('pve-node'),
2252 vmid
=> get_standard_option
('pve-vmid'),
2253 skiplock
=> get_standard_option
('skiplock'),
2256 description
=> "The disk you want to resize.",
2257 enum
=> [PVE
::QemuServer
::disknames
()],
2261 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2262 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.",
2266 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2272 returns
=> { type
=> 'null'},
2276 my $rpcenv = PVE
::RPCEnvironment
::get
();
2278 my $authuser = $rpcenv->get_user();
2280 my $node = extract_param
($param, 'node');
2282 my $vmid = extract_param
($param, 'vmid');
2284 my $digest = extract_param
($param, 'digest');
2286 my $disk = extract_param
($param, 'disk');
2288 my $sizestr = extract_param
($param, 'size');
2290 my $skiplock = extract_param
($param, 'skiplock');
2291 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2292 if $skiplock && $authuser ne 'root@pam';
2294 my $storecfg = PVE
::Storage
::config
();
2296 my $updatefn = sub {
2298 my $conf = PVE
::QemuServer
::load_config
($vmid);
2300 die "checksum missmatch (file change by other user?)\n"
2301 if $digest && $digest ne $conf->{digest
};
2302 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2304 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2306 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2308 my $volid = $drive->{file
};
2310 die "disk '$disk' has no associated volume\n" if !$volid;
2312 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2314 die "you can't online resize a virtio windows bootdisk\n"
2315 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2317 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2319 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2321 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2323 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2324 my ($ext, $newsize, $unit) = ($1, $2, $4);
2327 $newsize = $newsize * 1024;
2328 } elsif ($unit eq 'M') {
2329 $newsize = $newsize * 1024 * 1024;
2330 } elsif ($unit eq 'G') {
2331 $newsize = $newsize * 1024 * 1024 * 1024;
2332 } elsif ($unit eq 'T') {
2333 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2336 $newsize += $size if $ext;
2337 $newsize = int($newsize);
2339 die "unable to skrink disk size\n" if $newsize < $size;
2341 return if $size == $newsize;
2343 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2345 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2347 $drive->{size
} = $newsize;
2348 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2350 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2353 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2357 __PACKAGE__-
>register_method({
2358 name
=> 'snapshot_list',
2359 path
=> '{vmid}/snapshot',
2361 description
=> "List all snapshots.",
2363 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2366 protected
=> 1, # qemu pid files are only readable by root
2368 additionalProperties
=> 0,
2370 vmid
=> get_standard_option
('pve-vmid'),
2371 node
=> get_standard_option
('pve-node'),
2380 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2385 my $vmid = $param->{vmid
};
2387 my $conf = PVE
::QemuServer
::load_config
($vmid);
2388 my $snaphash = $conf->{snapshots
} || {};
2392 foreach my $name (keys %$snaphash) {
2393 my $d = $snaphash->{$name};
2396 snaptime
=> $d->{snaptime
} || 0,
2397 vmstate
=> $d->{vmstate
} ?
1 : 0,
2398 description
=> $d->{description
} || '',
2400 $item->{parent
} = $d->{parent
} if $d->{parent
};
2401 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2405 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2406 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2407 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2409 push @$res, $current;
2414 __PACKAGE__-
>register_method({
2416 path
=> '{vmid}/snapshot',
2420 description
=> "Snapshot a VM.",
2422 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2425 additionalProperties
=> 0,
2427 node
=> get_standard_option
('pve-node'),
2428 vmid
=> get_standard_option
('pve-vmid'),
2429 snapname
=> get_standard_option
('pve-snapshot-name'),
2433 description
=> "Save the vmstate",
2438 description
=> "Freeze the filesystem",
2443 description
=> "A textual description or comment.",
2449 description
=> "the task ID.",
2454 my $rpcenv = PVE
::RPCEnvironment
::get
();
2456 my $authuser = $rpcenv->get_user();
2458 my $node = extract_param
($param, 'node');
2460 my $vmid = extract_param
($param, 'vmid');
2462 my $snapname = extract_param
($param, 'snapname');
2464 die "unable to use snapshot name 'current' (reserved name)\n"
2465 if $snapname eq 'current';
2468 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2469 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2470 $param->{freezefs
}, $param->{description
});
2473 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2476 __PACKAGE__-
>register_method({
2477 name
=> 'snapshot_cmd_idx',
2478 path
=> '{vmid}/snapshot/{snapname}',
2485 additionalProperties
=> 0,
2487 vmid
=> get_standard_option
('pve-vmid'),
2488 node
=> get_standard_option
('pve-node'),
2489 snapname
=> get_standard_option
('pve-snapshot-name'),
2498 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2505 push @$res, { cmd
=> 'rollback' };
2506 push @$res, { cmd
=> 'config' };
2511 __PACKAGE__-
>register_method({
2512 name
=> 'update_snapshot_config',
2513 path
=> '{vmid}/snapshot/{snapname}/config',
2517 description
=> "Update snapshot metadata.",
2519 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2522 additionalProperties
=> 0,
2524 node
=> get_standard_option
('pve-node'),
2525 vmid
=> get_standard_option
('pve-vmid'),
2526 snapname
=> get_standard_option
('pve-snapshot-name'),
2530 description
=> "A textual description or comment.",
2534 returns
=> { type
=> 'null' },
2538 my $rpcenv = PVE
::RPCEnvironment
::get
();
2540 my $authuser = $rpcenv->get_user();
2542 my $vmid = extract_param
($param, 'vmid');
2544 my $snapname = extract_param
($param, 'snapname');
2546 return undef if !defined($param->{description
});
2548 my $updatefn = sub {
2550 my $conf = PVE
::QemuServer
::load_config
($vmid);
2552 PVE
::QemuServer
::check_lock
($conf);
2554 my $snap = $conf->{snapshots
}->{$snapname};
2556 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2558 $snap->{description
} = $param->{description
} if defined($param->{description
});
2560 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2563 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2568 __PACKAGE__-
>register_method({
2569 name
=> 'get_snapshot_config',
2570 path
=> '{vmid}/snapshot/{snapname}/config',
2573 description
=> "Get snapshot configuration",
2575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2578 additionalProperties
=> 0,
2580 node
=> get_standard_option
('pve-node'),
2581 vmid
=> get_standard_option
('pve-vmid'),
2582 snapname
=> get_standard_option
('pve-snapshot-name'),
2585 returns
=> { type
=> "object" },
2589 my $rpcenv = PVE
::RPCEnvironment
::get
();
2591 my $authuser = $rpcenv->get_user();
2593 my $vmid = extract_param
($param, 'vmid');
2595 my $snapname = extract_param
($param, 'snapname');
2597 my $conf = PVE
::QemuServer
::load_config
($vmid);
2599 my $snap = $conf->{snapshots
}->{$snapname};
2601 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2606 __PACKAGE__-
>register_method({
2608 path
=> '{vmid}/snapshot/{snapname}/rollback',
2612 description
=> "Rollback VM state to specified snapshot.",
2614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2617 additionalProperties
=> 0,
2619 node
=> get_standard_option
('pve-node'),
2620 vmid
=> get_standard_option
('pve-vmid'),
2621 snapname
=> get_standard_option
('pve-snapshot-name'),
2626 description
=> "the task ID.",
2631 my $rpcenv = PVE
::RPCEnvironment
::get
();
2633 my $authuser = $rpcenv->get_user();
2635 my $node = extract_param
($param, 'node');
2637 my $vmid = extract_param
($param, 'vmid');
2639 my $snapname = extract_param
($param, 'snapname');
2642 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2643 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2646 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2649 __PACKAGE__-
>register_method({
2650 name
=> 'delsnapshot',
2651 path
=> '{vmid}/snapshot/{snapname}',
2655 description
=> "Delete a VM snapshot.",
2657 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2660 additionalProperties
=> 0,
2662 node
=> get_standard_option
('pve-node'),
2663 vmid
=> get_standard_option
('pve-vmid'),
2664 snapname
=> get_standard_option
('pve-snapshot-name'),
2668 description
=> "For removal from config file, even if removing disk snapshots fails.",
2674 description
=> "the task ID.",
2679 my $rpcenv = PVE
::RPCEnvironment
::get
();
2681 my $authuser = $rpcenv->get_user();
2683 my $node = extract_param
($param, 'node');
2685 my $vmid = extract_param
($param, 'vmid');
2687 my $snapname = extract_param
($param, 'snapname');
2690 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2691 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2694 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2697 __PACKAGE__-
>register_method({
2699 path
=> '{vmid}/template',
2703 description
=> "Create a Template.",
2705 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2707 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2708 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2712 additionalProperties
=> 0,
2714 node
=> get_standard_option
('pve-node'),
2715 vmid
=> get_standard_option
('pve-vmid'),
2719 description
=> "If you want to convert only 1 disk to base image.",
2720 enum
=> [PVE
::QemuServer
::disknames
()],
2725 returns
=> { type
=> 'null'},
2729 my $rpcenv = PVE
::RPCEnvironment
::get
();
2731 my $authuser = $rpcenv->get_user();
2733 my $node = extract_param
($param, 'node');
2735 my $vmid = extract_param
($param, 'vmid');
2737 my $disk = extract_param
($param, 'disk');
2739 my $updatefn = sub {
2741 my $conf = PVE
::QemuServer
::load_config
($vmid);
2743 PVE
::QemuServer
::check_lock
($conf);
2745 die "unable to create template, because VM contains snapshots\n"
2746 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2748 die "you can't convert a template to a template\n"
2749 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2751 die "you can't convert a VM to template if VM is running\n"
2752 if PVE
::QemuServer
::check_running
($vmid);
2755 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2758 $conf->{template
} = 1;
2759 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2761 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2764 PVE
::QemuServer
::lock_config
($vmid, $updatefn);