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
();
1940 # check if storage is enabled on local node
1941 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1943 # check if storage is available on target node
1944 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1945 # clone only works if target storage is shared
1946 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1947 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1951 PVE
::Cluster
::check_cfs_quorum
();
1953 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1955 # exclusive lock if VM is running - else shared lock is enough;
1956 my $shared_lock = $running ?
0 : 1;
1960 # do all tests after lock
1961 # we also try to do all tests before we fork the worker
1963 my $conf = PVE
::QemuServer
::load_config
($vmid);
1965 PVE
::QemuServer
::check_lock
($conf);
1967 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1969 die "unexpected state change\n" if $verify_running != $running;
1971 die "snapshot '$snapname' does not exist\n"
1972 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1974 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1976 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1978 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1980 my $conffile = PVE
::QemuServer
::config_file
($newid);
1982 die "unable to create VM $newid: config file already exists\n"
1985 my $newconf = { lock => 'clone' };
1989 foreach my $opt (keys %$oldconf) {
1990 my $value = $oldconf->{$opt};
1992 # do not copy snapshot related info
1993 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1994 $opt eq 'vmstate' || $opt eq 'snapstate';
1996 # always change MAC! address
1997 if ($opt =~ m/^net(\d+)$/) {
1998 my $net = PVE
::QemuServer
::parse_net
($value);
1999 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2000 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2001 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2002 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2003 $newconf->{$opt} = $value; # simply copy configuration
2005 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2006 die "Full clone feature is not available"
2007 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2010 $drives->{$opt} = $drive;
2011 push @$vollist, $drive->{file
};
2014 # copy everything else
2015 $newconf->{$opt} = $value;
2019 delete $newconf->{template
};
2021 if ($param->{name
}) {
2022 $newconf->{name
} = $param->{name
};
2024 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2027 if ($param->{description
}) {
2028 $newconf->{description
} = $param->{description
};
2031 # create empty/temp config - this fails if VM already exists on other node
2032 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2037 my $newvollist = [];
2040 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2042 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2044 foreach my $opt (keys %$drives) {
2045 my $drive = $drives->{$opt};
2048 if (!$drive->{full
}) {
2049 print "create linked clone of drive $opt ($drive->{file})\n";
2050 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2051 push @$newvollist, $newvolid;
2054 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2055 $storeid = $storage if $storage;
2061 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2062 $fmt = $drive->{format
} || $defformat;
2065 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2067 print "create full clone of drive $opt ($drive->{file})\n";
2068 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2069 push @$newvollist, $newvolid;
2071 if(!$running || $snapname){
2072 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2074 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2079 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2080 my $disk = { file
=> $newvolid, size
=> $size };
2081 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2083 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2086 delete $newconf->{lock};
2087 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2090 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2091 die "Failed to move config to node '$target' - rename failed: $!\n"
2092 if !rename($conffile, $newconffile);
2095 add_vm_to_pool
($newid, $pool) if $pool;
2100 sleep 1; # some storage like rbd need to wait before release volume - really?
2102 foreach my $volid (@$newvollist) {
2103 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2106 die "clone failed: $err";
2112 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2115 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2116 # Aquire exclusive lock lock for $newid
2117 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2122 __PACKAGE__-
>register_method({
2123 name
=> 'migrate_vm',
2124 path
=> '{vmid}/migrate',
2128 description
=> "Migrate virtual machine. Creates a new migration task.",
2130 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2133 additionalProperties
=> 0,
2135 node
=> get_standard_option
('pve-node'),
2136 vmid
=> get_standard_option
('pve-vmid'),
2137 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2140 description
=> "Use online/live migration.",
2145 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2152 description
=> "the task ID.",
2157 my $rpcenv = PVE
::RPCEnvironment
::get
();
2159 my $authuser = $rpcenv->get_user();
2161 my $target = extract_param
($param, 'target');
2163 my $localnode = PVE
::INotify
::nodename
();
2164 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2166 PVE
::Cluster
::check_cfs_quorum
();
2168 PVE
::Cluster
::check_node_exists
($target);
2170 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2172 my $vmid = extract_param
($param, 'vmid');
2174 raise_param_exc
({ force
=> "Only root may use this option." })
2175 if $param->{force
} && $authuser ne 'root@pam';
2178 my $conf = PVE
::QemuServer
::load_config
($vmid);
2180 # try to detect errors early
2182 PVE
::QemuServer
::check_lock
($conf);
2184 if (PVE
::QemuServer
::check_running
($vmid)) {
2185 die "cant migrate running VM without --online\n"
2186 if !$param->{online
};
2189 my $storecfg = PVE
::Storage
::config
();
2190 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2192 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2197 my $service = "pvevm:$vmid";
2199 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2201 print "Executing HA migrate for VM $vmid to node $target\n";
2203 PVE
::Tools
::run_command
($cmd);
2208 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2215 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2218 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2223 __PACKAGE__-
>register_method({
2225 path
=> '{vmid}/monitor',
2229 description
=> "Execute Qemu monitor commands.",
2231 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2234 additionalProperties
=> 0,
2236 node
=> get_standard_option
('pve-node'),
2237 vmid
=> get_standard_option
('pve-vmid'),
2240 description
=> "The monitor command.",
2244 returns
=> { type
=> 'string'},
2248 my $vmid = $param->{vmid
};
2250 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2254 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2256 $res = "ERROR: $@" if $@;
2261 __PACKAGE__-
>register_method({
2262 name
=> 'resize_vm',
2263 path
=> '{vmid}/resize',
2267 description
=> "Extend volume size.",
2269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2272 additionalProperties
=> 0,
2274 node
=> get_standard_option
('pve-node'),
2275 vmid
=> get_standard_option
('pve-vmid'),
2276 skiplock
=> get_standard_option
('skiplock'),
2279 description
=> "The disk you want to resize.",
2280 enum
=> [PVE
::QemuServer
::disknames
()],
2284 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2285 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.",
2289 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2295 returns
=> { type
=> 'null'},
2299 my $rpcenv = PVE
::RPCEnvironment
::get
();
2301 my $authuser = $rpcenv->get_user();
2303 my $node = extract_param
($param, 'node');
2305 my $vmid = extract_param
($param, 'vmid');
2307 my $digest = extract_param
($param, 'digest');
2309 my $disk = extract_param
($param, 'disk');
2311 my $sizestr = extract_param
($param, 'size');
2313 my $skiplock = extract_param
($param, 'skiplock');
2314 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2315 if $skiplock && $authuser ne 'root@pam';
2317 my $storecfg = PVE
::Storage
::config
();
2319 my $updatefn = sub {
2321 my $conf = PVE
::QemuServer
::load_config
($vmid);
2323 die "checksum missmatch (file change by other user?)\n"
2324 if $digest && $digest ne $conf->{digest
};
2325 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2327 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2329 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2331 my $volid = $drive->{file
};
2333 die "disk '$disk' has no associated volume\n" if !$volid;
2335 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2337 die "you can't online resize a virtio windows bootdisk\n"
2338 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2340 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2342 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2344 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2346 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2347 my ($ext, $newsize, $unit) = ($1, $2, $4);
2350 $newsize = $newsize * 1024;
2351 } elsif ($unit eq 'M') {
2352 $newsize = $newsize * 1024 * 1024;
2353 } elsif ($unit eq 'G') {
2354 $newsize = $newsize * 1024 * 1024 * 1024;
2355 } elsif ($unit eq 'T') {
2356 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2359 $newsize += $size if $ext;
2360 $newsize = int($newsize);
2362 die "unable to skrink disk size\n" if $newsize < $size;
2364 return if $size == $newsize;
2366 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2368 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2370 $drive->{size
} = $newsize;
2371 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2373 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2376 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2380 __PACKAGE__-
>register_method({
2381 name
=> 'snapshot_list',
2382 path
=> '{vmid}/snapshot',
2384 description
=> "List all snapshots.",
2386 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2389 protected
=> 1, # qemu pid files are only readable by root
2391 additionalProperties
=> 0,
2393 vmid
=> get_standard_option
('pve-vmid'),
2394 node
=> get_standard_option
('pve-node'),
2403 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2408 my $vmid = $param->{vmid
};
2410 my $conf = PVE
::QemuServer
::load_config
($vmid);
2411 my $snaphash = $conf->{snapshots
} || {};
2415 foreach my $name (keys %$snaphash) {
2416 my $d = $snaphash->{$name};
2419 snaptime
=> $d->{snaptime
} || 0,
2420 vmstate
=> $d->{vmstate
} ?
1 : 0,
2421 description
=> $d->{description
} || '',
2423 $item->{parent
} = $d->{parent
} if $d->{parent
};
2424 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2428 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2429 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2430 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2432 push @$res, $current;
2437 __PACKAGE__-
>register_method({
2439 path
=> '{vmid}/snapshot',
2443 description
=> "Snapshot a VM.",
2445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2448 additionalProperties
=> 0,
2450 node
=> get_standard_option
('pve-node'),
2451 vmid
=> get_standard_option
('pve-vmid'),
2452 snapname
=> get_standard_option
('pve-snapshot-name'),
2456 description
=> "Save the vmstate",
2461 description
=> "Freeze the filesystem",
2466 description
=> "A textual description or comment.",
2472 description
=> "the task ID.",
2477 my $rpcenv = PVE
::RPCEnvironment
::get
();
2479 my $authuser = $rpcenv->get_user();
2481 my $node = extract_param
($param, 'node');
2483 my $vmid = extract_param
($param, 'vmid');
2485 my $snapname = extract_param
($param, 'snapname');
2487 die "unable to use snapshot name 'current' (reserved name)\n"
2488 if $snapname eq 'current';
2491 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2492 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2493 $param->{freezefs
}, $param->{description
});
2496 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2499 __PACKAGE__-
>register_method({
2500 name
=> 'snapshot_cmd_idx',
2501 path
=> '{vmid}/snapshot/{snapname}',
2508 additionalProperties
=> 0,
2510 vmid
=> get_standard_option
('pve-vmid'),
2511 node
=> get_standard_option
('pve-node'),
2512 snapname
=> get_standard_option
('pve-snapshot-name'),
2521 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2528 push @$res, { cmd
=> 'rollback' };
2529 push @$res, { cmd
=> 'config' };
2534 __PACKAGE__-
>register_method({
2535 name
=> 'update_snapshot_config',
2536 path
=> '{vmid}/snapshot/{snapname}/config',
2540 description
=> "Update snapshot metadata.",
2542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2545 additionalProperties
=> 0,
2547 node
=> get_standard_option
('pve-node'),
2548 vmid
=> get_standard_option
('pve-vmid'),
2549 snapname
=> get_standard_option
('pve-snapshot-name'),
2553 description
=> "A textual description or comment.",
2557 returns
=> { type
=> 'null' },
2561 my $rpcenv = PVE
::RPCEnvironment
::get
();
2563 my $authuser = $rpcenv->get_user();
2565 my $vmid = extract_param
($param, 'vmid');
2567 my $snapname = extract_param
($param, 'snapname');
2569 return undef if !defined($param->{description
});
2571 my $updatefn = sub {
2573 my $conf = PVE
::QemuServer
::load_config
($vmid);
2575 PVE
::QemuServer
::check_lock
($conf);
2577 my $snap = $conf->{snapshots
}->{$snapname};
2579 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2581 $snap->{description
} = $param->{description
} if defined($param->{description
});
2583 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2586 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2591 __PACKAGE__-
>register_method({
2592 name
=> 'get_snapshot_config',
2593 path
=> '{vmid}/snapshot/{snapname}/config',
2596 description
=> "Get snapshot configuration",
2598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2601 additionalProperties
=> 0,
2603 node
=> get_standard_option
('pve-node'),
2604 vmid
=> get_standard_option
('pve-vmid'),
2605 snapname
=> get_standard_option
('pve-snapshot-name'),
2608 returns
=> { type
=> "object" },
2612 my $rpcenv = PVE
::RPCEnvironment
::get
();
2614 my $authuser = $rpcenv->get_user();
2616 my $vmid = extract_param
($param, 'vmid');
2618 my $snapname = extract_param
($param, 'snapname');
2620 my $conf = PVE
::QemuServer
::load_config
($vmid);
2622 my $snap = $conf->{snapshots
}->{$snapname};
2624 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2629 __PACKAGE__-
>register_method({
2631 path
=> '{vmid}/snapshot/{snapname}/rollback',
2635 description
=> "Rollback VM state to specified snapshot.",
2637 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2640 additionalProperties
=> 0,
2642 node
=> get_standard_option
('pve-node'),
2643 vmid
=> get_standard_option
('pve-vmid'),
2644 snapname
=> get_standard_option
('pve-snapshot-name'),
2649 description
=> "the task ID.",
2654 my $rpcenv = PVE
::RPCEnvironment
::get
();
2656 my $authuser = $rpcenv->get_user();
2658 my $node = extract_param
($param, 'node');
2660 my $vmid = extract_param
($param, 'vmid');
2662 my $snapname = extract_param
($param, 'snapname');
2665 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2666 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2669 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2672 __PACKAGE__-
>register_method({
2673 name
=> 'delsnapshot',
2674 path
=> '{vmid}/snapshot/{snapname}',
2678 description
=> "Delete a VM snapshot.",
2680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2683 additionalProperties
=> 0,
2685 node
=> get_standard_option
('pve-node'),
2686 vmid
=> get_standard_option
('pve-vmid'),
2687 snapname
=> get_standard_option
('pve-snapshot-name'),
2691 description
=> "For removal from config file, even if removing disk snapshots fails.",
2697 description
=> "the task ID.",
2702 my $rpcenv = PVE
::RPCEnvironment
::get
();
2704 my $authuser = $rpcenv->get_user();
2706 my $node = extract_param
($param, 'node');
2708 my $vmid = extract_param
($param, 'vmid');
2710 my $snapname = extract_param
($param, 'snapname');
2713 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2714 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2717 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2720 __PACKAGE__-
>register_method({
2722 path
=> '{vmid}/template',
2726 description
=> "Create a Template.",
2728 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2729 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2732 additionalProperties
=> 0,
2734 node
=> get_standard_option
('pve-node'),
2735 vmid
=> get_standard_option
('pve-vmid'),
2739 description
=> "If you want to convert only 1 disk to base image.",
2740 enum
=> [PVE
::QemuServer
::disknames
()],
2745 returns
=> { type
=> 'null'},
2749 my $rpcenv = PVE
::RPCEnvironment
::get
();
2751 my $authuser = $rpcenv->get_user();
2753 my $node = extract_param
($param, 'node');
2755 my $vmid = extract_param
($param, 'vmid');
2757 my $disk = extract_param
($param, 'disk');
2759 my $updatefn = sub {
2761 my $conf = PVE
::QemuServer
::load_config
($vmid);
2763 PVE
::QemuServer
::check_lock
($conf);
2765 die "unable to create template, because VM contains snapshots\n"
2766 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2768 die "you can't convert a template to a template\n"
2769 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2771 die "you can't convert a VM to template if VM is running\n"
2772 if PVE
::QemuServer
::check_running
($vmid);
2775 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2778 $conf->{template
} = 1;
2779 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2781 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2784 PVE
::QemuServer
::lock_config
($vmid, $updatefn);