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' || $opt eq 'machine' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
378 if PVE
::Storage
::parse_volume_id
($archive, 1);
380 die "can't find archive file '$archive'\n" if !($path && -f
$path);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
448 die "create failed - $err";
451 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
454 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
457 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
460 __PACKAGE__-
>register_method({
465 description
=> "Directory index",
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
473 vmid
=> get_standard_option
('pve-vmid'),
481 subdir
=> { type
=> 'string' },
484 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'unlink' },
493 { subdir
=> 'vncproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'resize' },
496 { subdir
=> 'move' },
498 { subdir
=> 'rrddata' },
499 { subdir
=> 'monitor' },
500 { subdir
=> 'snapshot' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
771 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
772 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
773 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
778 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
779 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
782 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
784 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
786 if (PVE
::QemuServer
::check_running
($vmid)) {
787 if ($drive->{file
} eq 'none') {
788 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
790 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
796 } else { # hotplug new disks
798 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
802 my $vmconfig_update_net = sub {
803 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
805 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
806 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
807 my $newnet = PVE
::QemuServer
::parse_net
($value);
809 if($oldnet->{model
} ne $newnet->{model
}){
810 #if model change, we try to hot-unplug
811 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
814 if($newnet->{bridge
} && $oldnet->{bridge
}){
815 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
817 if($newnet->{rate
} ne $oldnet->{rate
}){
818 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
821 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
822 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
823 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
827 #if bridge/nat mode change, we try to hot-unplug
828 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
833 $conf->{$opt} = $value;
834 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
835 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
837 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
839 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
842 # POST/PUT {vmid}/config implementation
844 # The original API used PUT (idempotent) an we assumed that all operations
845 # are fast. But it turned out that almost any configuration change can
846 # involve hot-plug actions, or disk alloc/free. Such actions can take long
847 # time to complete and have side effects (not idempotent).
849 # The new implementation uses POST and forks a worker process. We added
850 # a new option 'background_delay'. If specified we wait up to
851 # 'background_delay' second for the worker task to complete. It returns null
852 # if the task is finished within that time, else we return the UPID.
854 my $update_vm_api = sub {
855 my ($param, $sync) = @_;
857 my $rpcenv = PVE
::RPCEnvironment
::get
();
859 my $authuser = $rpcenv->get_user();
861 my $node = extract_param
($param, 'node');
863 my $vmid = extract_param
($param, 'vmid');
865 my $digest = extract_param
($param, 'digest');
867 my $background_delay = extract_param
($param, 'background_delay');
869 my @paramarr = (); # used for log message
870 foreach my $key (keys %$param) {
871 push @paramarr, "-$key", $param->{$key};
874 my $skiplock = extract_param
($param, 'skiplock');
875 raise_param_exc
({ skiplock
=> "Only root may use this option." })
876 if $skiplock && $authuser ne 'root@pam';
878 my $delete_str = extract_param
($param, 'delete');
880 my $force = extract_param
($param, 'force');
882 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
884 my $storecfg = PVE
::Storage
::config
();
886 my $defaults = PVE
::QemuServer
::load_defaults
();
888 &$resolve_cdrom_alias($param);
890 # now try to verify all parameters
893 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
894 $opt = 'ide2' if $opt eq 'cdrom';
895 raise_param_exc
({ delete => "you can't use '-$opt' and " .
896 "-delete $opt' at the same time" })
897 if defined($param->{$opt});
899 if (!PVE
::QemuServer
::option_exists
($opt)) {
900 raise_param_exc
({ delete => "unknown option '$opt'" });
906 foreach my $opt (keys %$param) {
907 if (PVE
::QemuServer
::valid_drivename
($opt)) {
909 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
910 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
911 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
912 } elsif ($opt =~ m/^net(\d+)$/) {
914 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
915 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
919 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
921 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
923 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
927 my $conf = PVE
::QemuServer
::load_config
($vmid);
929 die "checksum missmatch (file change by other user?)\n"
930 if $digest && $digest ne $conf->{digest
};
932 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
934 if ($param->{memory
} || defined($param->{balloon
})) {
935 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
936 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
938 die "balloon value too large (must be smaller than assigned memory)\n"
939 if $balloon && $balloon > $maxmem;
942 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
946 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
948 foreach my $opt (@delete) { # delete
949 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
950 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
953 my $running = PVE
::QemuServer
::check_running
($vmid);
955 foreach my $opt (keys %$param) { # add/change
957 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
959 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
961 if (PVE
::QemuServer
::valid_drivename
($opt)) {
963 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
964 $opt, $param->{$opt}, $force);
966 } elsif ($opt =~ m/^net(\d+)$/) { #nics
968 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt});
973 if($opt eq 'tablet' && $param->{$opt} == 1){
974 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
975 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
976 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
979 $conf->{$opt} = $param->{$opt};
980 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
984 # allow manual ballooning if shares is set to zero
985 if ($running && defined($param->{balloon
}) &&
986 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
987 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
988 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
996 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
998 if ($background_delay) {
1000 # Note: It would be better to do that in the Event based HTTPServer
1001 # to avoid blocking call to sleep.
1003 my $end_time = time() + $background_delay;
1005 my $task = PVE
::Tools
::upid_decode
($upid);
1008 while (time() < $end_time) {
1009 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1011 sleep(1); # this gets interrupted when child process ends
1015 my $status = PVE
::Tools
::upid_read_status
($upid);
1016 return undef if $status eq 'OK';
1025 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1028 my $vm_config_perm_list = [
1033 'VM.Config.Network',
1035 'VM.Config.Options',
1038 __PACKAGE__-
>register_method({
1039 name
=> 'update_vm_async',
1040 path
=> '{vmid}/config',
1044 description
=> "Set virtual machine options (asynchrounous API).",
1046 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1049 additionalProperties
=> 0,
1050 properties
=> PVE
::QemuServer
::json_config_properties
(
1052 node
=> get_standard_option
('pve-node'),
1053 vmid
=> get_standard_option
('pve-vmid'),
1054 skiplock
=> get_standard_option
('skiplock'),
1056 type
=> 'string', format
=> 'pve-configid-list',
1057 description
=> "A list of settings you want to delete.",
1062 description
=> $opt_force_description,
1064 requires
=> 'delete',
1068 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1072 background_delay
=> {
1074 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1085 code
=> $update_vm_api,
1088 __PACKAGE__-
>register_method({
1089 name
=> 'update_vm',
1090 path
=> '{vmid}/config',
1094 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1096 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1099 additionalProperties
=> 0,
1100 properties
=> PVE
::QemuServer
::json_config_properties
(
1102 node
=> get_standard_option
('pve-node'),
1103 vmid
=> get_standard_option
('pve-vmid'),
1104 skiplock
=> get_standard_option
('skiplock'),
1106 type
=> 'string', format
=> 'pve-configid-list',
1107 description
=> "A list of settings you want to delete.",
1112 description
=> $opt_force_description,
1114 requires
=> 'delete',
1118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1124 returns
=> { type
=> 'null' },
1127 &$update_vm_api($param, 1);
1133 __PACKAGE__-
>register_method({
1134 name
=> 'destroy_vm',
1139 description
=> "Destroy the vm (also delete all used/owned volumes).",
1141 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1144 additionalProperties
=> 0,
1146 node
=> get_standard_option
('pve-node'),
1147 vmid
=> get_standard_option
('pve-vmid'),
1148 skiplock
=> get_standard_option
('skiplock'),
1157 my $rpcenv = PVE
::RPCEnvironment
::get
();
1159 my $authuser = $rpcenv->get_user();
1161 my $vmid = $param->{vmid
};
1163 my $skiplock = $param->{skiplock
};
1164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1165 if $skiplock && $authuser ne 'root@pam';
1168 my $conf = PVE
::QemuServer
::load_config
($vmid);
1170 my $storecfg = PVE
::Storage
::config
();
1172 my $delVMfromPoolFn = sub {
1173 my $usercfg = cfs_read_file
("user.cfg");
1174 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1175 if (my $data = $usercfg->{pools
}->{$pool}) {
1176 delete $data->{vms
}->{$vmid};
1177 delete $usercfg->{vms
}->{$vmid};
1178 cfs_write_file
("user.cfg", $usercfg);
1186 syslog
('info', "destroy VM $vmid: $upid\n");
1188 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1190 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1193 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1196 __PACKAGE__-
>register_method({
1198 path
=> '{vmid}/unlink',
1202 description
=> "Unlink/delete disk images.",
1204 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1207 additionalProperties
=> 0,
1209 node
=> get_standard_option
('pve-node'),
1210 vmid
=> get_standard_option
('pve-vmid'),
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "A list of disk IDs you want to delete.",
1217 description
=> $opt_force_description,
1222 returns
=> { type
=> 'null'},
1226 $param->{delete} = extract_param
($param, 'idlist');
1228 __PACKAGE__-
>update_vm($param);
1235 __PACKAGE__-
>register_method({
1237 path
=> '{vmid}/vncproxy',
1241 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1243 description
=> "Creates a TCP VNC proxy connections.",
1245 additionalProperties
=> 0,
1247 node
=> get_standard_option
('pve-node'),
1248 vmid
=> get_standard_option
('pve-vmid'),
1252 additionalProperties
=> 0,
1254 user
=> { type
=> 'string' },
1255 ticket
=> { type
=> 'string' },
1256 cert
=> { type
=> 'string' },
1257 port
=> { type
=> 'integer' },
1258 upid
=> { type
=> 'string' },
1264 my $rpcenv = PVE
::RPCEnvironment
::get
();
1266 my $authuser = $rpcenv->get_user();
1268 my $vmid = $param->{vmid
};
1269 my $node = $param->{node
};
1271 my $authpath = "/vms/$vmid";
1273 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1275 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1278 my $port = PVE
::Tools
::next_vnc_port
();
1282 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1283 $remip = PVE
::Cluster
::remote_node_ip
($node);
1286 # NOTE: kvm VNC traffic is already TLS encrypted
1287 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1294 syslog
('info', "starting vnc proxy $upid\n");
1296 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1298 my $qmstr = join(' ', @$qmcmd);
1300 # also redirect stderr (else we get RFB protocol errors)
1301 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1303 PVE
::Tools
::run_command
($cmd);
1308 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1310 PVE
::Tools
::wait_for_vnc_port
($port);
1321 __PACKAGE__-
>register_method({
1323 path
=> '{vmid}/status',
1326 description
=> "Directory index",
1331 additionalProperties
=> 0,
1333 node
=> get_standard_option
('pve-node'),
1334 vmid
=> get_standard_option
('pve-vmid'),
1342 subdir
=> { type
=> 'string' },
1345 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1351 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1354 { subdir
=> 'current' },
1355 { subdir
=> 'start' },
1356 { subdir
=> 'stop' },
1362 my $vm_is_ha_managed = sub {
1365 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1366 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1372 __PACKAGE__-
>register_method({
1373 name
=> 'vm_status',
1374 path
=> '{vmid}/status/current',
1377 protected
=> 1, # qemu pid files are only readable by root
1378 description
=> "Get virtual machine status.",
1380 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1383 additionalProperties
=> 0,
1385 node
=> get_standard_option
('pve-node'),
1386 vmid
=> get_standard_option
('pve-vmid'),
1389 returns
=> { type
=> 'object' },
1394 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1396 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1397 my $status = $vmstatus->{$param->{vmid
}};
1399 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1404 __PACKAGE__-
>register_method({
1406 path
=> '{vmid}/status/start',
1410 description
=> "Start virtual machine.",
1412 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1415 additionalProperties
=> 0,
1417 node
=> get_standard_option
('pve-node'),
1418 vmid
=> get_standard_option
('pve-vmid'),
1419 skiplock
=> get_standard_option
('skiplock'),
1420 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1421 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1422 machine
=> get_standard_option
('pve-qm-machine'),
1431 my $rpcenv = PVE
::RPCEnvironment
::get
();
1433 my $authuser = $rpcenv->get_user();
1435 my $node = extract_param
($param, 'node');
1437 my $vmid = extract_param
($param, 'vmid');
1439 my $machine = extract_param
($param, 'machine');
1441 my $stateuri = extract_param
($param, 'stateuri');
1442 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1443 if $stateuri && $authuser ne 'root@pam';
1445 my $skiplock = extract_param
($param, 'skiplock');
1446 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1447 if $skiplock && $authuser ne 'root@pam';
1449 my $migratedfrom = extract_param
($param, 'migratedfrom');
1450 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1451 if $migratedfrom && $authuser ne 'root@pam';
1453 my $storecfg = PVE
::Storage
::config
();
1455 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1456 $rpcenv->{type
} ne 'ha') {
1461 my $service = "pvevm:$vmid";
1463 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1465 print "Executing HA start for VM $vmid\n";
1467 PVE
::Tools
::run_command
($cmd);
1472 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1479 syslog
('info', "start VM $vmid: $upid\n");
1481 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1486 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1490 __PACKAGE__-
>register_method({
1492 path
=> '{vmid}/status/stop',
1496 description
=> "Stop virtual machine.",
1498 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1501 additionalProperties
=> 0,
1503 node
=> get_standard_option
('pve-node'),
1504 vmid
=> get_standard_option
('pve-vmid'),
1505 skiplock
=> get_standard_option
('skiplock'),
1506 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1508 description
=> "Wait maximal timeout seconds.",
1514 description
=> "Do not decativate storage volumes.",
1527 my $rpcenv = PVE
::RPCEnvironment
::get
();
1529 my $authuser = $rpcenv->get_user();
1531 my $node = extract_param
($param, 'node');
1533 my $vmid = extract_param
($param, 'vmid');
1535 my $skiplock = extract_param
($param, 'skiplock');
1536 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1537 if $skiplock && $authuser ne 'root@pam';
1539 my $keepActive = extract_param
($param, 'keepActive');
1540 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1541 if $keepActive && $authuser ne 'root@pam';
1543 my $migratedfrom = extract_param
($param, 'migratedfrom');
1544 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1545 if $migratedfrom && $authuser ne 'root@pam';
1548 my $storecfg = PVE
::Storage
::config
();
1550 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1555 my $service = "pvevm:$vmid";
1557 my $cmd = ['clusvcadm', '-d', $service];
1559 print "Executing HA stop for VM $vmid\n";
1561 PVE
::Tools
::run_command
($cmd);
1566 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1572 syslog
('info', "stop VM $vmid: $upid\n");
1574 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1575 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1580 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1584 __PACKAGE__-
>register_method({
1586 path
=> '{vmid}/status/reset',
1590 description
=> "Reset virtual machine.",
1592 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1595 additionalProperties
=> 0,
1597 node
=> get_standard_option
('pve-node'),
1598 vmid
=> get_standard_option
('pve-vmid'),
1599 skiplock
=> get_standard_option
('skiplock'),
1608 my $rpcenv = PVE
::RPCEnvironment
::get
();
1610 my $authuser = $rpcenv->get_user();
1612 my $node = extract_param
($param, 'node');
1614 my $vmid = extract_param
($param, 'vmid');
1616 my $skiplock = extract_param
($param, 'skiplock');
1617 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1618 if $skiplock && $authuser ne 'root@pam';
1620 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1625 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1630 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1633 __PACKAGE__-
>register_method({
1634 name
=> 'vm_shutdown',
1635 path
=> '{vmid}/status/shutdown',
1639 description
=> "Shutdown virtual machine.",
1641 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1644 additionalProperties
=> 0,
1646 node
=> get_standard_option
('pve-node'),
1647 vmid
=> get_standard_option
('pve-vmid'),
1648 skiplock
=> get_standard_option
('skiplock'),
1650 description
=> "Wait maximal timeout seconds.",
1656 description
=> "Make sure the VM stops.",
1662 description
=> "Do not decativate storage volumes.",
1675 my $rpcenv = PVE
::RPCEnvironment
::get
();
1677 my $authuser = $rpcenv->get_user();
1679 my $node = extract_param
($param, 'node');
1681 my $vmid = extract_param
($param, 'vmid');
1683 my $skiplock = extract_param
($param, 'skiplock');
1684 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1685 if $skiplock && $authuser ne 'root@pam';
1687 my $keepActive = extract_param
($param, 'keepActive');
1688 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1689 if $keepActive && $authuser ne 'root@pam';
1691 my $storecfg = PVE
::Storage
::config
();
1696 syslog
('info', "shutdown VM $vmid: $upid\n");
1698 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1699 1, $param->{forceStop
}, $keepActive);
1704 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1707 __PACKAGE__-
>register_method({
1708 name
=> 'vm_suspend',
1709 path
=> '{vmid}/status/suspend',
1713 description
=> "Suspend virtual machine.",
1715 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1718 additionalProperties
=> 0,
1720 node
=> get_standard_option
('pve-node'),
1721 vmid
=> get_standard_option
('pve-vmid'),
1722 skiplock
=> get_standard_option
('skiplock'),
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $node = extract_param
($param, 'node');
1737 my $vmid = extract_param
($param, 'vmid');
1739 my $skiplock = extract_param
($param, 'skiplock');
1740 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1741 if $skiplock && $authuser ne 'root@pam';
1743 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1748 syslog
('info', "suspend VM $vmid: $upid\n");
1750 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1755 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1758 __PACKAGE__-
>register_method({
1759 name
=> 'vm_resume',
1760 path
=> '{vmid}/status/resume',
1764 description
=> "Resume virtual machine.",
1766 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1769 additionalProperties
=> 0,
1771 node
=> get_standard_option
('pve-node'),
1772 vmid
=> get_standard_option
('pve-vmid'),
1773 skiplock
=> get_standard_option
('skiplock'),
1782 my $rpcenv = PVE
::RPCEnvironment
::get
();
1784 my $authuser = $rpcenv->get_user();
1786 my $node = extract_param
($param, 'node');
1788 my $vmid = extract_param
($param, 'vmid');
1790 my $skiplock = extract_param
($param, 'skiplock');
1791 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1792 if $skiplock && $authuser ne 'root@pam';
1794 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1799 syslog
('info', "resume VM $vmid: $upid\n");
1801 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1806 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1809 __PACKAGE__-
>register_method({
1810 name
=> 'vm_sendkey',
1811 path
=> '{vmid}/sendkey',
1815 description
=> "Send key event to virtual machine.",
1817 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1820 additionalProperties
=> 0,
1822 node
=> get_standard_option
('pve-node'),
1823 vmid
=> get_standard_option
('pve-vmid'),
1824 skiplock
=> get_standard_option
('skiplock'),
1826 description
=> "The key (qemu monitor encoding).",
1831 returns
=> { type
=> 'null'},
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $node = extract_param
($param, 'node');
1841 my $vmid = extract_param
($param, 'vmid');
1843 my $skiplock = extract_param
($param, 'skiplock');
1844 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1845 if $skiplock && $authuser ne 'root@pam';
1847 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1852 __PACKAGE__-
>register_method({
1853 name
=> 'vm_feature',
1854 path
=> '{vmid}/feature',
1858 description
=> "Check if feature for virtual machine is available.",
1860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1863 additionalProperties
=> 0,
1865 node
=> get_standard_option
('pve-node'),
1866 vmid
=> get_standard_option
('pve-vmid'),
1868 description
=> "Feature to check.",
1870 enum
=> [ 'snapshot', 'clone', 'copy' ],
1872 snapname
=> get_standard_option
('pve-snapshot-name', {
1880 hasFeature
=> { type
=> 'boolean' },
1883 items
=> { type
=> 'string' },
1890 my $node = extract_param
($param, 'node');
1892 my $vmid = extract_param
($param, 'vmid');
1894 my $snapname = extract_param
($param, 'snapname');
1896 my $feature = extract_param
($param, 'feature');
1898 my $running = PVE
::QemuServer
::check_running
($vmid);
1900 my $conf = PVE
::QemuServer
::load_config
($vmid);
1903 my $snap = $conf->{snapshots
}->{$snapname};
1904 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1907 my $storecfg = PVE
::Storage
::config
();
1909 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1910 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1913 hasFeature
=> $hasFeature,
1914 nodes
=> [ keys %$nodelist ],
1918 __PACKAGE__-
>register_method({
1920 path
=> '{vmid}/clone',
1924 description
=> "Create a copy of virtual machine/template.",
1926 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1927 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1928 "'Datastore.AllocateSpace' on any used storage.",
1931 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1933 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1934 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1943 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1946 type
=> 'string', format
=> 'dns-name',
1947 description
=> "Set a name for the new VM.",
1952 description
=> "Description for the new VM.",
1956 type
=> 'string', format
=> 'pve-poolid',
1957 description
=> "Add the new VM to the specified pool.",
1959 snapname
=> get_standard_option
('pve-snapshot-name', {
1963 storage
=> get_standard_option
('pve-storage-id', {
1964 description
=> "Target storage for full clone.",
1969 description
=> "Target format for file storage.",
1973 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1978 description
=> "Create a full copy of all disk. This is always done when " .
1979 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1982 target
=> get_standard_option
('pve-node', {
1983 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1994 my $rpcenv = PVE
::RPCEnvironment
::get
();
1996 my $authuser = $rpcenv->get_user();
1998 my $node = extract_param
($param, 'node');
2000 my $vmid = extract_param
($param, 'vmid');
2002 my $newid = extract_param
($param, 'newid');
2004 my $pool = extract_param
($param, 'pool');
2006 if (defined($pool)) {
2007 $rpcenv->check_pool_exist($pool);
2010 my $snapname = extract_param
($param, 'snapname');
2012 my $storage = extract_param
($param, 'storage');
2014 my $format = extract_param
($param, 'format');
2016 my $target = extract_param
($param, 'target');
2018 my $localnode = PVE
::INotify
::nodename
();
2020 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2022 PVE
::Cluster
::check_node_exists
($target) if $target;
2024 my $storecfg = PVE
::Storage
::config
();
2027 # check if storage is enabled on local node
2028 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2030 # check if storage is available on target node
2031 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2032 # clone only works if target storage is shared
2033 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2034 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2038 PVE
::Cluster
::check_cfs_quorum
();
2040 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2042 # exclusive lock if VM is running - else shared lock is enough;
2043 my $shared_lock = $running ?
0 : 1;
2047 # do all tests after lock
2048 # we also try to do all tests before we fork the worker
2050 my $conf = PVE
::QemuServer
::load_config
($vmid);
2052 PVE
::QemuServer
::check_lock
($conf);
2054 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2056 die "unexpected state change\n" if $verify_running != $running;
2058 die "snapshot '$snapname' does not exist\n"
2059 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2061 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2063 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2065 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2067 my $conffile = PVE
::QemuServer
::config_file
($newid);
2069 die "unable to create VM $newid: config file already exists\n"
2072 my $newconf = { lock => 'clone' };
2076 foreach my $opt (keys %$oldconf) {
2077 my $value = $oldconf->{$opt};
2079 # do not copy snapshot related info
2080 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2081 $opt eq 'vmstate' || $opt eq 'snapstate';
2083 # always change MAC! address
2084 if ($opt =~ m/^net(\d+)$/) {
2085 my $net = PVE
::QemuServer
::parse_net
($value);
2086 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2087 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2088 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2089 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2090 $newconf->{$opt} = $value; # simply copy configuration
2092 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2093 die "Full clone feature is not available"
2094 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2097 $drives->{$opt} = $drive;
2098 push @$vollist, $drive->{file
};
2101 # copy everything else
2102 $newconf->{$opt} = $value;
2106 delete $newconf->{template
};
2108 if ($param->{name
}) {
2109 $newconf->{name
} = $param->{name
};
2111 if ($oldconf->{name
}) {
2112 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2114 $newconf->{name
} = "Copy-of-VM-$vmid";
2118 if ($param->{description
}) {
2119 $newconf->{description
} = $param->{description
};
2122 # create empty/temp config - this fails if VM already exists on other node
2123 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2128 my $newvollist = [];
2131 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2133 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2135 foreach my $opt (keys %$drives) {
2136 my $drive = $drives->{$opt};
2138 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2139 $newid, $storage, $format, $drive->{full
}, $newvollist);
2141 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2143 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2146 delete $newconf->{lock};
2147 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2150 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2151 die "Failed to move config to node '$target' - rename failed: $!\n"
2152 if !rename($conffile, $newconffile);
2155 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2160 sleep 1; # some storage like rbd need to wait before release volume - really?
2162 foreach my $volid (@$newvollist) {
2163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2166 die "clone failed: $err";
2172 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2175 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2176 # Aquire exclusive lock lock for $newid
2177 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2182 __PACKAGE__-
>register_method({
2183 name
=> 'move_vm_disk',
2184 path
=> '{vmid}/move_disk',
2188 description
=> "Move volume to different storage.",
2190 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2191 "and 'Datastore.AllocateSpace' permissions on the storage.",
2194 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2195 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2199 additionalProperties
=> 0,
2201 node
=> get_standard_option
('pve-node'),
2202 vmid
=> get_standard_option
('pve-vmid'),
2205 description
=> "The disk you want to move.",
2206 enum
=> [ PVE
::QemuServer
::disknames
() ],
2208 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2211 description
=> "Target Format.",
2212 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2217 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2223 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2231 description
=> "the task ID.",
2236 my $rpcenv = PVE
::RPCEnvironment
::get
();
2238 my $authuser = $rpcenv->get_user();
2240 my $node = extract_param
($param, 'node');
2242 my $vmid = extract_param
($param, 'vmid');
2244 my $digest = extract_param
($param, 'digest');
2246 my $disk = extract_param
($param, 'disk');
2248 my $storeid = extract_param
($param, 'storage');
2250 my $format = extract_param
($param, 'format');
2252 my $storecfg = PVE
::Storage
::config
();
2254 my $updatefn = sub {
2256 my $conf = PVE
::QemuServer
::load_config
($vmid);
2258 die "checksum missmatch (file change by other user?)\n"
2259 if $digest && $digest ne $conf->{digest
};
2261 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2263 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2265 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2267 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2270 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2271 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2275 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2276 (!$format || !$oldfmt || $oldfmt eq $format);
2278 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2280 my $running = PVE
::QemuServer
::check_running
($vmid);
2282 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2286 my $newvollist = [];
2289 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2291 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2292 $vmid, $storeid, $format, 1, $newvollist);
2294 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2296 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2298 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2302 foreach my $volid (@$newvollist) {
2303 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2306 die "storage migration failed: $err";
2309 if ($param->{delete}) {
2310 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2315 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2318 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2321 __PACKAGE__-
>register_method({
2322 name
=> 'migrate_vm',
2323 path
=> '{vmid}/migrate',
2327 description
=> "Migrate virtual machine. Creates a new migration task.",
2329 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2332 additionalProperties
=> 0,
2334 node
=> get_standard_option
('pve-node'),
2335 vmid
=> get_standard_option
('pve-vmid'),
2336 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2339 description
=> "Use online/live migration.",
2344 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2351 description
=> "the task ID.",
2356 my $rpcenv = PVE
::RPCEnvironment
::get
();
2358 my $authuser = $rpcenv->get_user();
2360 my $target = extract_param
($param, 'target');
2362 my $localnode = PVE
::INotify
::nodename
();
2363 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2365 PVE
::Cluster
::check_cfs_quorum
();
2367 PVE
::Cluster
::check_node_exists
($target);
2369 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2371 my $vmid = extract_param
($param, 'vmid');
2373 raise_param_exc
({ force
=> "Only root may use this option." })
2374 if $param->{force
} && $authuser ne 'root@pam';
2377 my $conf = PVE
::QemuServer
::load_config
($vmid);
2379 # try to detect errors early
2381 PVE
::QemuServer
::check_lock
($conf);
2383 if (PVE
::QemuServer
::check_running
($vmid)) {
2384 die "cant migrate running VM without --online\n"
2385 if !$param->{online
};
2388 my $storecfg = PVE
::Storage
::config
();
2389 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2391 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2396 my $service = "pvevm:$vmid";
2398 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2400 print "Executing HA migrate for VM $vmid to node $target\n";
2402 PVE
::Tools
::run_command
($cmd);
2407 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2414 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2417 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2422 __PACKAGE__-
>register_method({
2424 path
=> '{vmid}/monitor',
2428 description
=> "Execute Qemu monitor commands.",
2430 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid'),
2439 description
=> "The monitor command.",
2443 returns
=> { type
=> 'string'},
2447 my $vmid = $param->{vmid
};
2449 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2453 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2455 $res = "ERROR: $@" if $@;
2460 __PACKAGE__-
>register_method({
2461 name
=> 'resize_vm',
2462 path
=> '{vmid}/resize',
2466 description
=> "Extend volume size.",
2468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2471 additionalProperties
=> 0,
2473 node
=> get_standard_option
('pve-node'),
2474 vmid
=> get_standard_option
('pve-vmid'),
2475 skiplock
=> get_standard_option
('skiplock'),
2478 description
=> "The disk you want to resize.",
2479 enum
=> [PVE
::QemuServer
::disknames
()],
2483 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2484 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.",
2488 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2494 returns
=> { type
=> 'null'},
2498 my $rpcenv = PVE
::RPCEnvironment
::get
();
2500 my $authuser = $rpcenv->get_user();
2502 my $node = extract_param
($param, 'node');
2504 my $vmid = extract_param
($param, 'vmid');
2506 my $digest = extract_param
($param, 'digest');
2508 my $disk = extract_param
($param, 'disk');
2510 my $sizestr = extract_param
($param, 'size');
2512 my $skiplock = extract_param
($param, 'skiplock');
2513 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2514 if $skiplock && $authuser ne 'root@pam';
2516 my $storecfg = PVE
::Storage
::config
();
2518 my $updatefn = sub {
2520 my $conf = PVE
::QemuServer
::load_config
($vmid);
2522 die "checksum missmatch (file change by other user?)\n"
2523 if $digest && $digest ne $conf->{digest
};
2524 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2526 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2528 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2530 my $volid = $drive->{file
};
2532 die "disk '$disk' has no associated volume\n" if !$volid;
2534 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2536 die "you can't online resize a virtio windows bootdisk\n"
2537 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2539 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2541 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2543 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2545 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2546 my ($ext, $newsize, $unit) = ($1, $2, $4);
2549 $newsize = $newsize * 1024;
2550 } elsif ($unit eq 'M') {
2551 $newsize = $newsize * 1024 * 1024;
2552 } elsif ($unit eq 'G') {
2553 $newsize = $newsize * 1024 * 1024 * 1024;
2554 } elsif ($unit eq 'T') {
2555 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2558 $newsize += $size if $ext;
2559 $newsize = int($newsize);
2561 die "unable to skrink disk size\n" if $newsize < $size;
2563 return if $size == $newsize;
2565 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2567 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2569 $drive->{size
} = $newsize;
2570 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2572 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2575 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2579 __PACKAGE__-
>register_method({
2580 name
=> 'snapshot_list',
2581 path
=> '{vmid}/snapshot',
2583 description
=> "List all snapshots.",
2585 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2588 protected
=> 1, # qemu pid files are only readable by root
2590 additionalProperties
=> 0,
2592 vmid
=> get_standard_option
('pve-vmid'),
2593 node
=> get_standard_option
('pve-node'),
2602 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2607 my $vmid = $param->{vmid
};
2609 my $conf = PVE
::QemuServer
::load_config
($vmid);
2610 my $snaphash = $conf->{snapshots
} || {};
2614 foreach my $name (keys %$snaphash) {
2615 my $d = $snaphash->{$name};
2618 snaptime
=> $d->{snaptime
} || 0,
2619 vmstate
=> $d->{vmstate
} ?
1 : 0,
2620 description
=> $d->{description
} || '',
2622 $item->{parent
} = $d->{parent
} if $d->{parent
};
2623 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2627 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2628 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2629 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2631 push @$res, $current;
2636 __PACKAGE__-
>register_method({
2638 path
=> '{vmid}/snapshot',
2642 description
=> "Snapshot a VM.",
2644 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2647 additionalProperties
=> 0,
2649 node
=> get_standard_option
('pve-node'),
2650 vmid
=> get_standard_option
('pve-vmid'),
2651 snapname
=> get_standard_option
('pve-snapshot-name'),
2655 description
=> "Save the vmstate",
2660 description
=> "Freeze the filesystem",
2665 description
=> "A textual description or comment.",
2671 description
=> "the task ID.",
2676 my $rpcenv = PVE
::RPCEnvironment
::get
();
2678 my $authuser = $rpcenv->get_user();
2680 my $node = extract_param
($param, 'node');
2682 my $vmid = extract_param
($param, 'vmid');
2684 my $snapname = extract_param
($param, 'snapname');
2686 die "unable to use snapshot name 'current' (reserved name)\n"
2687 if $snapname eq 'current';
2690 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2691 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2692 $param->{freezefs
}, $param->{description
});
2695 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2698 __PACKAGE__-
>register_method({
2699 name
=> 'snapshot_cmd_idx',
2700 path
=> '{vmid}/snapshot/{snapname}',
2707 additionalProperties
=> 0,
2709 vmid
=> get_standard_option
('pve-vmid'),
2710 node
=> get_standard_option
('pve-node'),
2711 snapname
=> get_standard_option
('pve-snapshot-name'),
2720 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2727 push @$res, { cmd
=> 'rollback' };
2728 push @$res, { cmd
=> 'config' };
2733 __PACKAGE__-
>register_method({
2734 name
=> 'update_snapshot_config',
2735 path
=> '{vmid}/snapshot/{snapname}/config',
2739 description
=> "Update snapshot metadata.",
2741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2744 additionalProperties
=> 0,
2746 node
=> get_standard_option
('pve-node'),
2747 vmid
=> get_standard_option
('pve-vmid'),
2748 snapname
=> get_standard_option
('pve-snapshot-name'),
2752 description
=> "A textual description or comment.",
2756 returns
=> { type
=> 'null' },
2760 my $rpcenv = PVE
::RPCEnvironment
::get
();
2762 my $authuser = $rpcenv->get_user();
2764 my $vmid = extract_param
($param, 'vmid');
2766 my $snapname = extract_param
($param, 'snapname');
2768 return undef if !defined($param->{description
});
2770 my $updatefn = sub {
2772 my $conf = PVE
::QemuServer
::load_config
($vmid);
2774 PVE
::QemuServer
::check_lock
($conf);
2776 my $snap = $conf->{snapshots
}->{$snapname};
2778 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2780 $snap->{description
} = $param->{description
} if defined($param->{description
});
2782 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2785 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2790 __PACKAGE__-
>register_method({
2791 name
=> 'get_snapshot_config',
2792 path
=> '{vmid}/snapshot/{snapname}/config',
2795 description
=> "Get snapshot configuration",
2797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2800 additionalProperties
=> 0,
2802 node
=> get_standard_option
('pve-node'),
2803 vmid
=> get_standard_option
('pve-vmid'),
2804 snapname
=> get_standard_option
('pve-snapshot-name'),
2807 returns
=> { type
=> "object" },
2811 my $rpcenv = PVE
::RPCEnvironment
::get
();
2813 my $authuser = $rpcenv->get_user();
2815 my $vmid = extract_param
($param, 'vmid');
2817 my $snapname = extract_param
($param, 'snapname');
2819 my $conf = PVE
::QemuServer
::load_config
($vmid);
2821 my $snap = $conf->{snapshots
}->{$snapname};
2823 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2828 __PACKAGE__-
>register_method({
2830 path
=> '{vmid}/snapshot/{snapname}/rollback',
2834 description
=> "Rollback VM state to specified snapshot.",
2836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2839 additionalProperties
=> 0,
2841 node
=> get_standard_option
('pve-node'),
2842 vmid
=> get_standard_option
('pve-vmid'),
2843 snapname
=> get_standard_option
('pve-snapshot-name'),
2848 description
=> "the task ID.",
2853 my $rpcenv = PVE
::RPCEnvironment
::get
();
2855 my $authuser = $rpcenv->get_user();
2857 my $node = extract_param
($param, 'node');
2859 my $vmid = extract_param
($param, 'vmid');
2861 my $snapname = extract_param
($param, 'snapname');
2864 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2865 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2868 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2871 __PACKAGE__-
>register_method({
2872 name
=> 'delsnapshot',
2873 path
=> '{vmid}/snapshot/{snapname}',
2877 description
=> "Delete a VM snapshot.",
2879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2882 additionalProperties
=> 0,
2884 node
=> get_standard_option
('pve-node'),
2885 vmid
=> get_standard_option
('pve-vmid'),
2886 snapname
=> get_standard_option
('pve-snapshot-name'),
2890 description
=> "For removal from config file, even if removing disk snapshots fails.",
2896 description
=> "the task ID.",
2901 my $rpcenv = PVE
::RPCEnvironment
::get
();
2903 my $authuser = $rpcenv->get_user();
2905 my $node = extract_param
($param, 'node');
2907 my $vmid = extract_param
($param, 'vmid');
2909 my $snapname = extract_param
($param, 'snapname');
2912 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2913 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2916 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2919 __PACKAGE__-
>register_method({
2921 path
=> '{vmid}/template',
2925 description
=> "Create a Template.",
2927 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2928 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2931 additionalProperties
=> 0,
2933 node
=> get_standard_option
('pve-node'),
2934 vmid
=> get_standard_option
('pve-vmid'),
2938 description
=> "If you want to convert only 1 disk to base image.",
2939 enum
=> [PVE
::QemuServer
::disknames
()],
2944 returns
=> { type
=> 'null'},
2948 my $rpcenv = PVE
::RPCEnvironment
::get
();
2950 my $authuser = $rpcenv->get_user();
2952 my $node = extract_param
($param, 'node');
2954 my $vmid = extract_param
($param, 'vmid');
2956 my $disk = extract_param
($param, 'disk');
2958 my $updatefn = sub {
2960 my $conf = PVE
::QemuServer
::load_config
($vmid);
2962 PVE
::QemuServer
::check_lock
($conf);
2964 die "unable to create template, because VM contains snapshots\n"
2965 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2967 die "you can't convert a template to a template\n"
2968 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2970 die "you can't convert a VM to template if VM is running\n"
2971 if PVE
::QemuServer
::check_running
($vmid);
2974 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2977 $conf->{template
} = 1;
2978 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2980 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2983 PVE
::QemuServer
::lock_config
($vmid, $updatefn);