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);
2081 $disk->{full
} = undef;
2082 $disk->{format
} = undef;
2083 $disk->{file
} = $newvolid;
2084 $disk->{size
} = $size;
2086 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2088 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2091 delete $newconf->{lock};
2092 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2095 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2096 die "Failed to move config to node '$target' - rename failed: $!\n"
2097 if !rename($conffile, $newconffile);
2100 add_vm_to_pool
($newid, $pool) if $pool;
2105 sleep 1; # some storage like rbd need to wait before release volume - really?
2107 foreach my $volid (@$newvollist) {
2108 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2111 die "clone failed: $err";
2117 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2120 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2121 # Aquire exclusive lock lock for $newid
2122 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2127 __PACKAGE__-
>register_method({
2128 name
=> 'migrate_vm',
2129 path
=> '{vmid}/migrate',
2133 description
=> "Migrate virtual machine. Creates a new migration task.",
2135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2138 additionalProperties
=> 0,
2140 node
=> get_standard_option
('pve-node'),
2141 vmid
=> get_standard_option
('pve-vmid'),
2142 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2145 description
=> "Use online/live migration.",
2150 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2157 description
=> "the task ID.",
2162 my $rpcenv = PVE
::RPCEnvironment
::get
();
2164 my $authuser = $rpcenv->get_user();
2166 my $target = extract_param
($param, 'target');
2168 my $localnode = PVE
::INotify
::nodename
();
2169 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2171 PVE
::Cluster
::check_cfs_quorum
();
2173 PVE
::Cluster
::check_node_exists
($target);
2175 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2177 my $vmid = extract_param
($param, 'vmid');
2179 raise_param_exc
({ force
=> "Only root may use this option." })
2180 if $param->{force
} && $authuser ne 'root@pam';
2183 my $conf = PVE
::QemuServer
::load_config
($vmid);
2185 # try to detect errors early
2187 PVE
::QemuServer
::check_lock
($conf);
2189 if (PVE
::QemuServer
::check_running
($vmid)) {
2190 die "cant migrate running VM without --online\n"
2191 if !$param->{online
};
2194 my $storecfg = PVE
::Storage
::config
();
2195 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2197 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2202 my $service = "pvevm:$vmid";
2204 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2206 print "Executing HA migrate for VM $vmid to node $target\n";
2208 PVE
::Tools
::run_command
($cmd);
2213 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2220 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2223 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2228 __PACKAGE__-
>register_method({
2230 path
=> '{vmid}/monitor',
2234 description
=> "Execute Qemu monitor commands.",
2236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2239 additionalProperties
=> 0,
2241 node
=> get_standard_option
('pve-node'),
2242 vmid
=> get_standard_option
('pve-vmid'),
2245 description
=> "The monitor command.",
2249 returns
=> { type
=> 'string'},
2253 my $vmid = $param->{vmid
};
2255 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2259 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2261 $res = "ERROR: $@" if $@;
2266 __PACKAGE__-
>register_method({
2267 name
=> 'resize_vm',
2268 path
=> '{vmid}/resize',
2272 description
=> "Extend volume size.",
2274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2277 additionalProperties
=> 0,
2279 node
=> get_standard_option
('pve-node'),
2280 vmid
=> get_standard_option
('pve-vmid'),
2281 skiplock
=> get_standard_option
('skiplock'),
2284 description
=> "The disk you want to resize.",
2285 enum
=> [PVE
::QemuServer
::disknames
()],
2289 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2290 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.",
2294 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2300 returns
=> { type
=> 'null'},
2304 my $rpcenv = PVE
::RPCEnvironment
::get
();
2306 my $authuser = $rpcenv->get_user();
2308 my $node = extract_param
($param, 'node');
2310 my $vmid = extract_param
($param, 'vmid');
2312 my $digest = extract_param
($param, 'digest');
2314 my $disk = extract_param
($param, 'disk');
2316 my $sizestr = extract_param
($param, 'size');
2318 my $skiplock = extract_param
($param, 'skiplock');
2319 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2320 if $skiplock && $authuser ne 'root@pam';
2322 my $storecfg = PVE
::Storage
::config
();
2324 my $updatefn = sub {
2326 my $conf = PVE
::QemuServer
::load_config
($vmid);
2328 die "checksum missmatch (file change by other user?)\n"
2329 if $digest && $digest ne $conf->{digest
};
2330 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2332 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2334 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2336 my $volid = $drive->{file
};
2338 die "disk '$disk' has no associated volume\n" if !$volid;
2340 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2342 die "you can't online resize a virtio windows bootdisk\n"
2343 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2345 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2347 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2349 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2351 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2352 my ($ext, $newsize, $unit) = ($1, $2, $4);
2355 $newsize = $newsize * 1024;
2356 } elsif ($unit eq 'M') {
2357 $newsize = $newsize * 1024 * 1024;
2358 } elsif ($unit eq 'G') {
2359 $newsize = $newsize * 1024 * 1024 * 1024;
2360 } elsif ($unit eq 'T') {
2361 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2364 $newsize += $size if $ext;
2365 $newsize = int($newsize);
2367 die "unable to skrink disk size\n" if $newsize < $size;
2369 return if $size == $newsize;
2371 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2373 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2375 $drive->{size
} = $newsize;
2376 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2378 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2381 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2385 __PACKAGE__-
>register_method({
2386 name
=> 'snapshot_list',
2387 path
=> '{vmid}/snapshot',
2389 description
=> "List all snapshots.",
2391 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2394 protected
=> 1, # qemu pid files are only readable by root
2396 additionalProperties
=> 0,
2398 vmid
=> get_standard_option
('pve-vmid'),
2399 node
=> get_standard_option
('pve-node'),
2408 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2413 my $vmid = $param->{vmid
};
2415 my $conf = PVE
::QemuServer
::load_config
($vmid);
2416 my $snaphash = $conf->{snapshots
} || {};
2420 foreach my $name (keys %$snaphash) {
2421 my $d = $snaphash->{$name};
2424 snaptime
=> $d->{snaptime
} || 0,
2425 vmstate
=> $d->{vmstate
} ?
1 : 0,
2426 description
=> $d->{description
} || '',
2428 $item->{parent
} = $d->{parent
} if $d->{parent
};
2429 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2433 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2434 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2435 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2437 push @$res, $current;
2442 __PACKAGE__-
>register_method({
2444 path
=> '{vmid}/snapshot',
2448 description
=> "Snapshot a VM.",
2450 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2453 additionalProperties
=> 0,
2455 node
=> get_standard_option
('pve-node'),
2456 vmid
=> get_standard_option
('pve-vmid'),
2457 snapname
=> get_standard_option
('pve-snapshot-name'),
2461 description
=> "Save the vmstate",
2466 description
=> "Freeze the filesystem",
2471 description
=> "A textual description or comment.",
2477 description
=> "the task ID.",
2482 my $rpcenv = PVE
::RPCEnvironment
::get
();
2484 my $authuser = $rpcenv->get_user();
2486 my $node = extract_param
($param, 'node');
2488 my $vmid = extract_param
($param, 'vmid');
2490 my $snapname = extract_param
($param, 'snapname');
2492 die "unable to use snapshot name 'current' (reserved name)\n"
2493 if $snapname eq 'current';
2496 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2497 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2498 $param->{freezefs
}, $param->{description
});
2501 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2504 __PACKAGE__-
>register_method({
2505 name
=> 'snapshot_cmd_idx',
2506 path
=> '{vmid}/snapshot/{snapname}',
2513 additionalProperties
=> 0,
2515 vmid
=> get_standard_option
('pve-vmid'),
2516 node
=> get_standard_option
('pve-node'),
2517 snapname
=> get_standard_option
('pve-snapshot-name'),
2526 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2533 push @$res, { cmd
=> 'rollback' };
2534 push @$res, { cmd
=> 'config' };
2539 __PACKAGE__-
>register_method({
2540 name
=> 'update_snapshot_config',
2541 path
=> '{vmid}/snapshot/{snapname}/config',
2545 description
=> "Update snapshot metadata.",
2547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2550 additionalProperties
=> 0,
2552 node
=> get_standard_option
('pve-node'),
2553 vmid
=> get_standard_option
('pve-vmid'),
2554 snapname
=> get_standard_option
('pve-snapshot-name'),
2558 description
=> "A textual description or comment.",
2562 returns
=> { type
=> 'null' },
2566 my $rpcenv = PVE
::RPCEnvironment
::get
();
2568 my $authuser = $rpcenv->get_user();
2570 my $vmid = extract_param
($param, 'vmid');
2572 my $snapname = extract_param
($param, 'snapname');
2574 return undef if !defined($param->{description
});
2576 my $updatefn = sub {
2578 my $conf = PVE
::QemuServer
::load_config
($vmid);
2580 PVE
::QemuServer
::check_lock
($conf);
2582 my $snap = $conf->{snapshots
}->{$snapname};
2584 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2586 $snap->{description
} = $param->{description
} if defined($param->{description
});
2588 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2591 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2596 __PACKAGE__-
>register_method({
2597 name
=> 'get_snapshot_config',
2598 path
=> '{vmid}/snapshot/{snapname}/config',
2601 description
=> "Get snapshot configuration",
2603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid'),
2610 snapname
=> get_standard_option
('pve-snapshot-name'),
2613 returns
=> { type
=> "object" },
2617 my $rpcenv = PVE
::RPCEnvironment
::get
();
2619 my $authuser = $rpcenv->get_user();
2621 my $vmid = extract_param
($param, 'vmid');
2623 my $snapname = extract_param
($param, 'snapname');
2625 my $conf = PVE
::QemuServer
::load_config
($vmid);
2627 my $snap = $conf->{snapshots
}->{$snapname};
2629 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2634 __PACKAGE__-
>register_method({
2636 path
=> '{vmid}/snapshot/{snapname}/rollback',
2640 description
=> "Rollback VM state to specified snapshot.",
2642 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2645 additionalProperties
=> 0,
2647 node
=> get_standard_option
('pve-node'),
2648 vmid
=> get_standard_option
('pve-vmid'),
2649 snapname
=> get_standard_option
('pve-snapshot-name'),
2654 description
=> "the task ID.",
2659 my $rpcenv = PVE
::RPCEnvironment
::get
();
2661 my $authuser = $rpcenv->get_user();
2663 my $node = extract_param
($param, 'node');
2665 my $vmid = extract_param
($param, 'vmid');
2667 my $snapname = extract_param
($param, 'snapname');
2670 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2671 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2674 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2677 __PACKAGE__-
>register_method({
2678 name
=> 'delsnapshot',
2679 path
=> '{vmid}/snapshot/{snapname}',
2683 description
=> "Delete a VM snapshot.",
2685 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2688 additionalProperties
=> 0,
2690 node
=> get_standard_option
('pve-node'),
2691 vmid
=> get_standard_option
('pve-vmid'),
2692 snapname
=> get_standard_option
('pve-snapshot-name'),
2696 description
=> "For removal from config file, even if removing disk snapshots fails.",
2702 description
=> "the task ID.",
2707 my $rpcenv = PVE
::RPCEnvironment
::get
();
2709 my $authuser = $rpcenv->get_user();
2711 my $node = extract_param
($param, 'node');
2713 my $vmid = extract_param
($param, 'vmid');
2715 my $snapname = extract_param
($param, 'snapname');
2718 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2719 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2722 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2725 __PACKAGE__-
>register_method({
2727 path
=> '{vmid}/template',
2731 description
=> "Create a Template.",
2733 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2734 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2737 additionalProperties
=> 0,
2739 node
=> get_standard_option
('pve-node'),
2740 vmid
=> get_standard_option
('pve-vmid'),
2744 description
=> "If you want to convert only 1 disk to base image.",
2745 enum
=> [PVE
::QemuServer
::disknames
()],
2750 returns
=> { type
=> 'null'},
2754 my $rpcenv = PVE
::RPCEnvironment
::get
();
2756 my $authuser = $rpcenv->get_user();
2758 my $node = extract_param
($param, 'node');
2760 my $vmid = extract_param
($param, 'vmid');
2762 my $disk = extract_param
($param, 'disk');
2764 my $updatefn = sub {
2766 my $conf = PVE
::QemuServer
::load_config
($vmid);
2768 PVE
::QemuServer
::check_lock
($conf);
2770 die "unable to create template, because VM contains snapshots\n"
2771 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2773 die "you can't convert a template to a template\n"
2774 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2776 die "you can't convert a VM to template if VM is running\n"
2777 if PVE
::QemuServer
::check_running
($vmid);
2780 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2783 $conf->{template
} = 1;
2784 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2786 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2789 PVE
::QemuServer
::lock_config
($vmid, $updatefn);