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', 'copy' ],
1785 snapname
=> get_standard_option
('pve-snapshot-name', {
1793 hasFeature
=> { type
=> 'boolean' },
1796 items
=> { type
=> 'string' },
1803 my $node = extract_param
($param, 'node');
1805 my $vmid = extract_param
($param, 'vmid');
1807 my $snapname = extract_param
($param, 'snapname');
1809 my $feature = extract_param
($param, 'feature');
1811 my $running = PVE
::QemuServer
::check_running
($vmid);
1813 my $conf = PVE
::QemuServer
::load_config
($vmid);
1816 my $snap = $conf->{snapshots
}->{$snapname};
1817 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1820 my $storecfg = PVE
::Storage
::config
();
1822 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1823 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1826 hasFeature
=> $hasFeature,
1827 nodes
=> [ keys %$nodelist ],
1831 __PACKAGE__-
>register_method({
1833 path
=> '{vmid}/clone',
1837 description
=> "Create a copy of virtual machine/template.",
1839 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1840 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1841 "'Datastore.AllocateSpace' on any used storage.",
1844 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1846 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1847 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1852 additionalProperties
=> 0,
1854 node
=> get_standard_option
('pve-node'),
1855 vmid
=> get_standard_option
('pve-vmid'),
1856 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1859 type
=> 'string', format
=> 'dns-name',
1860 description
=> "Set a name for the new VM.",
1865 description
=> "Description for the new VM.",
1869 type
=> 'string', format
=> 'pve-poolid',
1870 description
=> "Add the new VM to the specified pool.",
1872 snapname
=> get_standard_option
('pve-snapshot-name', {
1876 storage
=> get_standard_option
('pve-storage-id', {
1877 description
=> "Target storage for full clone.",
1882 description
=> "Target format for file storage.",
1886 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1891 description
=> "Create a full copy of all disk. This is always done when " .
1892 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1895 target
=> get_standard_option
('pve-node', {
1896 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1907 my $rpcenv = PVE
::RPCEnvironment
::get
();
1909 my $authuser = $rpcenv->get_user();
1911 my $node = extract_param
($param, 'node');
1913 my $vmid = extract_param
($param, 'vmid');
1915 my $newid = extract_param
($param, 'newid');
1917 my $pool = extract_param
($param, 'pool');
1919 if (defined($pool)) {
1920 $rpcenv->check_pool_exist($pool);
1923 my $snapname = extract_param
($param, 'snapname');
1925 my $storage = extract_param
($param, 'storage');
1927 my $format = extract_param
($param, 'format');
1929 my $target = extract_param
($param, 'target');
1931 my $localnode = PVE
::INotify
::nodename
();
1933 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1935 PVE
::Cluster
::check_node_exists
($target) if $target;
1937 my $storecfg = PVE
::Storage
::config
();
1939 PVE
::Cluster
::check_cfs_quorum
();
1941 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1943 # exclusive lock if VM is running - else shared lock is enough;
1944 my $shared_lock = $running ?
0 : 1;
1948 # do all tests after lock
1949 # we also try to do all tests before we fork the worker
1951 my $conf = PVE
::QemuServer
::load_config
($vmid);
1953 PVE
::QemuServer
::check_lock
($conf);
1955 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1957 die "unexpected state change\n" if $verify_running != $running;
1959 die "snapshot '$snapname' does not exist\n"
1960 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1962 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1964 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1966 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1968 my $conffile = PVE
::QemuServer
::config_file
($newid);
1970 die "unable to create VM $newid: config file already exists\n"
1973 my $newconf = { lock => 'clone' };
1977 foreach my $opt (keys %$oldconf) {
1978 my $value = $oldconf->{$opt};
1980 # do not copy snapshot related info
1981 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1982 $opt eq 'vmstate' || $opt eq 'snapstate';
1984 # always change MAC! address
1985 if ($opt =~ m/^net(\d+)$/) {
1986 my $net = PVE
::QemuServer
::parse_net
($value);
1987 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1988 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1989 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1990 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1991 $newconf->{$opt} = $value; # simply copy configuration
1993 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1994 die "Full clone feature is not available"
1995 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1998 $drives->{$opt} = $drive;
1999 push @$vollist, $drive->{file
};
2002 # copy everything else
2003 $newconf->{$opt} = $value;
2007 delete $newconf->{template
};
2009 if ($param->{name
}) {
2010 $newconf->{name
} = $param->{name
};
2012 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2015 if ($param->{description
}) {
2016 $newconf->{description
} = $param->{description
};
2019 # create empty/temp config - this fails if VM already exists on other node
2020 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2025 my $newvollist = [];
2028 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2030 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2032 foreach my $opt (keys %$drives) {
2033 my $drive = $drives->{$opt};
2036 if (!$drive->{full
}) {
2037 print "create linked clone of drive $opt ($drive->{file})\n";
2038 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2039 push @$newvollist, $newvolid;
2042 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2043 $storeid = $storage if $storage;
2049 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2050 $fmt = $drive->{format
} || $defformat;
2053 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2055 print "create full clone of drive $opt ($drive->{file})\n";
2056 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2057 push @$newvollist, $newvolid;
2059 if(!$running || $snapname){
2060 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2062 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2067 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2068 my $disk = { file
=> $newvolid, size
=> $size };
2069 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2071 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2074 delete $newconf->{lock};
2075 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2078 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2079 die "Failed to move config to node '$target' - rename failed: $!\n"
2080 if !rename($conffile, $newconffile);
2083 add_vm_to_pool
($newid, $pool) if $pool;
2088 sleep 1; # some storage like rbd need to wait before release volume - really?
2090 foreach my $volid (@$newvollist) {
2091 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2094 die "clone failed: $err";
2100 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2103 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2104 # Aquire exclusive lock lock for $newid
2105 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2110 __PACKAGE__-
>register_method({
2111 name
=> 'migrate_vm',
2112 path
=> '{vmid}/migrate',
2116 description
=> "Migrate virtual machine. Creates a new migration task.",
2118 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2121 additionalProperties
=> 0,
2123 node
=> get_standard_option
('pve-node'),
2124 vmid
=> get_standard_option
('pve-vmid'),
2125 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2128 description
=> "Use online/live migration.",
2133 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2140 description
=> "the task ID.",
2145 my $rpcenv = PVE
::RPCEnvironment
::get
();
2147 my $authuser = $rpcenv->get_user();
2149 my $target = extract_param
($param, 'target');
2151 my $localnode = PVE
::INotify
::nodename
();
2152 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2154 PVE
::Cluster
::check_cfs_quorum
();
2156 PVE
::Cluster
::check_node_exists
($target);
2158 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2160 my $vmid = extract_param
($param, 'vmid');
2162 raise_param_exc
({ force
=> "Only root may use this option." })
2163 if $param->{force
} && $authuser ne 'root@pam';
2166 my $conf = PVE
::QemuServer
::load_config
($vmid);
2168 # try to detect errors early
2170 PVE
::QemuServer
::check_lock
($conf);
2172 if (PVE
::QemuServer
::check_running
($vmid)) {
2173 die "cant migrate running VM without --online\n"
2174 if !$param->{online
};
2177 my $storecfg = PVE
::Storage
::config
();
2178 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2180 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2185 my $service = "pvevm:$vmid";
2187 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2189 print "Executing HA migrate for VM $vmid to node $target\n";
2191 PVE
::Tools
::run_command
($cmd);
2196 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2203 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2206 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2211 __PACKAGE__-
>register_method({
2213 path
=> '{vmid}/monitor',
2217 description
=> "Execute Qemu monitor commands.",
2219 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2222 additionalProperties
=> 0,
2224 node
=> get_standard_option
('pve-node'),
2225 vmid
=> get_standard_option
('pve-vmid'),
2228 description
=> "The monitor command.",
2232 returns
=> { type
=> 'string'},
2236 my $vmid = $param->{vmid
};
2238 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2242 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2244 $res = "ERROR: $@" if $@;
2249 __PACKAGE__-
>register_method({
2250 name
=> 'resize_vm',
2251 path
=> '{vmid}/resize',
2255 description
=> "Extend volume size.",
2257 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2260 additionalProperties
=> 0,
2262 node
=> get_standard_option
('pve-node'),
2263 vmid
=> get_standard_option
('pve-vmid'),
2264 skiplock
=> get_standard_option
('skiplock'),
2267 description
=> "The disk you want to resize.",
2268 enum
=> [PVE
::QemuServer
::disknames
()],
2272 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2273 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.",
2277 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2283 returns
=> { type
=> 'null'},
2287 my $rpcenv = PVE
::RPCEnvironment
::get
();
2289 my $authuser = $rpcenv->get_user();
2291 my $node = extract_param
($param, 'node');
2293 my $vmid = extract_param
($param, 'vmid');
2295 my $digest = extract_param
($param, 'digest');
2297 my $disk = extract_param
($param, 'disk');
2299 my $sizestr = extract_param
($param, 'size');
2301 my $skiplock = extract_param
($param, 'skiplock');
2302 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2303 if $skiplock && $authuser ne 'root@pam';
2305 my $storecfg = PVE
::Storage
::config
();
2307 my $updatefn = sub {
2309 my $conf = PVE
::QemuServer
::load_config
($vmid);
2311 die "checksum missmatch (file change by other user?)\n"
2312 if $digest && $digest ne $conf->{digest
};
2313 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2315 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2317 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2319 my $volid = $drive->{file
};
2321 die "disk '$disk' has no associated volume\n" if !$volid;
2323 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2325 die "you can't online resize a virtio windows bootdisk\n"
2326 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2328 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2330 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2332 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2334 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2335 my ($ext, $newsize, $unit) = ($1, $2, $4);
2338 $newsize = $newsize * 1024;
2339 } elsif ($unit eq 'M') {
2340 $newsize = $newsize * 1024 * 1024;
2341 } elsif ($unit eq 'G') {
2342 $newsize = $newsize * 1024 * 1024 * 1024;
2343 } elsif ($unit eq 'T') {
2344 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2347 $newsize += $size if $ext;
2348 $newsize = int($newsize);
2350 die "unable to skrink disk size\n" if $newsize < $size;
2352 return if $size == $newsize;
2354 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2356 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2358 $drive->{size
} = $newsize;
2359 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2361 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2364 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2368 __PACKAGE__-
>register_method({
2369 name
=> 'snapshot_list',
2370 path
=> '{vmid}/snapshot',
2372 description
=> "List all snapshots.",
2374 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2377 protected
=> 1, # qemu pid files are only readable by root
2379 additionalProperties
=> 0,
2381 vmid
=> get_standard_option
('pve-vmid'),
2382 node
=> get_standard_option
('pve-node'),
2391 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2396 my $vmid = $param->{vmid
};
2398 my $conf = PVE
::QemuServer
::load_config
($vmid);
2399 my $snaphash = $conf->{snapshots
} || {};
2403 foreach my $name (keys %$snaphash) {
2404 my $d = $snaphash->{$name};
2407 snaptime
=> $d->{snaptime
} || 0,
2408 vmstate
=> $d->{vmstate
} ?
1 : 0,
2409 description
=> $d->{description
} || '',
2411 $item->{parent
} = $d->{parent
} if $d->{parent
};
2412 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2416 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2417 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2418 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2420 push @$res, $current;
2425 __PACKAGE__-
>register_method({
2427 path
=> '{vmid}/snapshot',
2431 description
=> "Snapshot a VM.",
2433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2436 additionalProperties
=> 0,
2438 node
=> get_standard_option
('pve-node'),
2439 vmid
=> get_standard_option
('pve-vmid'),
2440 snapname
=> get_standard_option
('pve-snapshot-name'),
2444 description
=> "Save the vmstate",
2449 description
=> "Freeze the filesystem",
2454 description
=> "A textual description or comment.",
2460 description
=> "the task ID.",
2465 my $rpcenv = PVE
::RPCEnvironment
::get
();
2467 my $authuser = $rpcenv->get_user();
2469 my $node = extract_param
($param, 'node');
2471 my $vmid = extract_param
($param, 'vmid');
2473 my $snapname = extract_param
($param, 'snapname');
2475 die "unable to use snapshot name 'current' (reserved name)\n"
2476 if $snapname eq 'current';
2479 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2480 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2481 $param->{freezefs
}, $param->{description
});
2484 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2487 __PACKAGE__-
>register_method({
2488 name
=> 'snapshot_cmd_idx',
2489 path
=> '{vmid}/snapshot/{snapname}',
2496 additionalProperties
=> 0,
2498 vmid
=> get_standard_option
('pve-vmid'),
2499 node
=> get_standard_option
('pve-node'),
2500 snapname
=> get_standard_option
('pve-snapshot-name'),
2509 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2516 push @$res, { cmd
=> 'rollback' };
2517 push @$res, { cmd
=> 'config' };
2522 __PACKAGE__-
>register_method({
2523 name
=> 'update_snapshot_config',
2524 path
=> '{vmid}/snapshot/{snapname}/config',
2528 description
=> "Update snapshot metadata.",
2530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2533 additionalProperties
=> 0,
2535 node
=> get_standard_option
('pve-node'),
2536 vmid
=> get_standard_option
('pve-vmid'),
2537 snapname
=> get_standard_option
('pve-snapshot-name'),
2541 description
=> "A textual description or comment.",
2545 returns
=> { type
=> 'null' },
2549 my $rpcenv = PVE
::RPCEnvironment
::get
();
2551 my $authuser = $rpcenv->get_user();
2553 my $vmid = extract_param
($param, 'vmid');
2555 my $snapname = extract_param
($param, 'snapname');
2557 return undef if !defined($param->{description
});
2559 my $updatefn = sub {
2561 my $conf = PVE
::QemuServer
::load_config
($vmid);
2563 PVE
::QemuServer
::check_lock
($conf);
2565 my $snap = $conf->{snapshots
}->{$snapname};
2567 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2569 $snap->{description
} = $param->{description
} if defined($param->{description
});
2571 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2574 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2579 __PACKAGE__-
>register_method({
2580 name
=> 'get_snapshot_config',
2581 path
=> '{vmid}/snapshot/{snapname}/config',
2584 description
=> "Get snapshot configuration",
2586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2589 additionalProperties
=> 0,
2591 node
=> get_standard_option
('pve-node'),
2592 vmid
=> get_standard_option
('pve-vmid'),
2593 snapname
=> get_standard_option
('pve-snapshot-name'),
2596 returns
=> { type
=> "object" },
2600 my $rpcenv = PVE
::RPCEnvironment
::get
();
2602 my $authuser = $rpcenv->get_user();
2604 my $vmid = extract_param
($param, 'vmid');
2606 my $snapname = extract_param
($param, 'snapname');
2608 my $conf = PVE
::QemuServer
::load_config
($vmid);
2610 my $snap = $conf->{snapshots
}->{$snapname};
2612 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2617 __PACKAGE__-
>register_method({
2619 path
=> '{vmid}/snapshot/{snapname}/rollback',
2623 description
=> "Rollback VM state to specified snapshot.",
2625 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2628 additionalProperties
=> 0,
2630 node
=> get_standard_option
('pve-node'),
2631 vmid
=> get_standard_option
('pve-vmid'),
2632 snapname
=> get_standard_option
('pve-snapshot-name'),
2637 description
=> "the task ID.",
2642 my $rpcenv = PVE
::RPCEnvironment
::get
();
2644 my $authuser = $rpcenv->get_user();
2646 my $node = extract_param
($param, 'node');
2648 my $vmid = extract_param
($param, 'vmid');
2650 my $snapname = extract_param
($param, 'snapname');
2653 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2654 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2657 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2660 __PACKAGE__-
>register_method({
2661 name
=> 'delsnapshot',
2662 path
=> '{vmid}/snapshot/{snapname}',
2666 description
=> "Delete a VM snapshot.",
2668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid'),
2675 snapname
=> get_standard_option
('pve-snapshot-name'),
2679 description
=> "For removal from config file, even if removing disk snapshots fails.",
2685 description
=> "the task ID.",
2690 my $rpcenv = PVE
::RPCEnvironment
::get
();
2692 my $authuser = $rpcenv->get_user();
2694 my $node = extract_param
($param, 'node');
2696 my $vmid = extract_param
($param, 'vmid');
2698 my $snapname = extract_param
($param, 'snapname');
2701 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2702 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2705 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2708 __PACKAGE__-
>register_method({
2710 path
=> '{vmid}/template',
2714 description
=> "Create a Template.",
2716 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2717 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2720 additionalProperties
=> 0,
2722 node
=> get_standard_option
('pve-node'),
2723 vmid
=> get_standard_option
('pve-vmid'),
2727 description
=> "If you want to convert only 1 disk to base image.",
2728 enum
=> [PVE
::QemuServer
::disknames
()],
2733 returns
=> { type
=> 'null'},
2737 my $rpcenv = PVE
::RPCEnvironment
::get
();
2739 my $authuser = $rpcenv->get_user();
2741 my $node = extract_param
($param, 'node');
2743 my $vmid = extract_param
($param, 'vmid');
2745 my $disk = extract_param
($param, 'disk');
2747 my $updatefn = sub {
2749 my $conf = PVE
::QemuServer
::load_config
($vmid);
2751 PVE
::QemuServer
::check_lock
($conf);
2753 die "unable to create template, because VM contains snapshots\n"
2754 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2756 die "you can't convert a template to a template\n"
2757 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2759 die "you can't convert a VM to template if VM is running\n"
2760 if PVE
::QemuServer
::check_running
($vmid);
2763 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2766 $conf->{template
} = 1;
2767 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2769 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2772 PVE
::QemuServer
::lock_config
($vmid, $updatefn);