]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
vm_copy : add optional format parameter
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5b9d692a 5use Cwd 'abs_path';
1e3baf05 6
502d18a2 7use PVE::Cluster qw (cfs_read_file cfs_write_file);;
1e3baf05
DM
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param);
10use PVE::Exception qw(raise raise_param_exc);
11use PVE::Storage;
12use PVE::JSONSchema qw(get_standard_option);
13use PVE::RESTHandler;
14use PVE::QemuServer;
3ea94c60 15use PVE::QemuMigrate;
1e3baf05
DM
16use PVE::RPCEnvironment;
17use PVE::AccessControl;
18use PVE::INotify;
de8f60b2 19use PVE::Network;
1e3baf05
DM
20
21use Data::Dumper; # fixme: remove
22
23use base qw(PVE::RESTHandler);
24
25my $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.";
26
27my $resolve_cdrom_alias = sub {
28 my $param = shift;
29
30 if (my $value = $param->{cdrom}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2} = $value;
33 delete $param->{cdrom};
34 }
35};
36
a0d1b1a2 37
ae57f6b3 38my $check_storage_access = sub {
fcbb753e 39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
a0d1b1a2 40
ae57f6b3
DM
41 PVE::QemuServer::foreach_drive($settings, sub {
42 my ($ds, $drive) = @_;
a0d1b1a2 43
ae57f6b3 44 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
a0d1b1a2 45
ae57f6b3 46 my $volid = $drive->{file};
a0d1b1a2 47
09d0ee64
DM
48 if (!$volid || $volid eq 'none') {
49 # nothing to check
f5782fd0
DM
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
09d0ee64 52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
a0d1b1a2
DM
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
fcbb753e 55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
a0d1b1a2 56 } else {
8b192abf 57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
a0d1b1a2
DM
58 }
59 });
ae57f6b3 60};
a0d1b1a2 61
6116f729 62my $check_storage_access_copy = sub {
81f043eb 63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
6116f729
DM
64
65 PVE::QemuServer::foreach_drive($conf, sub {
66 my ($ds, $drive) = @_;
67
68 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
69
70 my $volid = $drive->{file};
71
72 return if !$volid || $volid eq 'none';
73
74 if ($isCDROM) {
75 if ($volid eq 'cdrom') {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } else {
78 # we simply allow access
79 }
80 } else {
81 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
82 die "unable to copy arbitrary files\n" if !$sid;
81f043eb 83 $sid = $storage if $storage;
6116f729
DM
84 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
85 }
86 });
87};
88
ae57f6b3
DM
89# Note: $pool is only needed when creating a VM, because pool permissions
90# are automatically inherited if VM already exists inside a pool.
91my $create_disks = sub {
92 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2
DM
93
94 my $vollist = [];
a0d1b1a2 95
ae57f6b3
DM
96 my $res = {};
97 PVE::QemuServer::foreach_drive($settings, sub {
98 my ($ds, $disk) = @_;
99
100 my $volid = $disk->{file};
101
f5782fd0 102 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
628e9a2b
AD
103 delete $disk->{size};
104 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
09d0ee64 105 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
ae57f6b3
DM
106 my ($storeid, $size) = ($2 || $default_storage, $3);
107 die "no storage ID specified (and no default storage)\n" if !$storeid;
108 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
109 my $fmt = $disk->{format} || $defformat;
a0d1b1a2
DM
110 my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
111 $fmt, undef, $size*1024*1024);
a0d1b1a2 112 $disk->{file} = $volid;
24afaca0 113 $disk->{size} = $size*1024*1024*1024;
a0d1b1a2 114 push @$vollist, $volid;
ae57f6b3
DM
115 delete $disk->{format}; # no longer needed
116 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
117 } else {
eabe0da0 118
ba68cf09 119 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
eabe0da0
DM
120
121 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
35cb731c 122
35cb731c
AD
123 my $foundvolid = undef;
124
eabe0da0
DM
125 if ($storeid) {
126 PVE::Storage::activate_volumes($storecfg, [ $volid ]);
127 my $dl = PVE::Storage::vdisk_list($storecfg, $storeid, undef);
128
129 PVE::Storage::foreach_volid($dl, sub {
130 my ($volumeid) = @_;
131 if($volumeid eq $volid) {
132 $foundvolid = 1;
133 return;
134 }
135 });
136 }
35cb731c
AD
137
138 die "image '$path' does not exists\n" if (!(-f $path || -b $path || $foundvolid));
24afaca0
DM
139
140 my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 1);
141 $disk->{size} = $size;
142 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
a0d1b1a2 143 }
ae57f6b3 144 });
a0d1b1a2
DM
145
146 # free allocated images on error
147 if (my $err = $@) {
148 syslog('err', "VM $vmid creating disks failed");
149 foreach my $volid (@$vollist) {
150 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
151 warn $@ if $@;
152 }
153 die $err;
154 }
155
156 # modify vm config if everything went well
ae57f6b3
DM
157 foreach my $ds (keys %$res) {
158 $conf->{$ds} = $res->{$ds};
a0d1b1a2
DM
159 }
160
161 return $vollist;
162};
163
164my $check_vm_modify_config_perm = sub {
ae57f6b3 165 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
a0d1b1a2 166
6e5c4da7 167 return 1 if $authuser eq 'root@pam';
a0d1b1a2 168
ae57f6b3 169 foreach my $opt (@$key_list) {
a0d1b1a2
DM
170 # disk checks need to be done somewhere else
171 next if PVE::QemuServer::valid_drivename($opt);
172
173 if ($opt eq 'sockets' || $opt eq 'cores' ||
174 $opt eq 'cpu' || $opt eq 'smp' ||
ab6b35df 175 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
a0d1b1a2
DM
176 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
177 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
178 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
ccd5438f 179 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
a0d1b1a2
DM
180 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
181 } elsif ($opt eq 'args' || $opt eq 'lock') {
182 die "only root can set '$opt' config\n";
183 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
184 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
186 } elsif ($opt =~ m/^net\d+$/) {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
188 } else {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
190 }
191 }
192
193 return 1;
194};
195
1e3baf05 196__PACKAGE__->register_method({
afdb31d5
DM
197 name => 'vmlist',
198 path => '',
1e3baf05
DM
199 method => 'GET',
200 description => "Virtual machine index (per node).",
a0d1b1a2
DM
201 permissions => {
202 description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
203 user => 'all',
204 },
1e3baf05
DM
205 proxyto => 'node',
206 protected => 1, # qemu pid files are only readable by root
207 parameters => {
208 additionalProperties => 0,
209 properties => {
210 node => get_standard_option('pve-node'),
211 },
212 },
213 returns => {
214 type => 'array',
215 items => {
216 type => "object",
217 properties => {},
218 },
219 links => [ { rel => 'child', href => "{vmid}" } ],
220 },
221 code => sub {
222 my ($param) = @_;
223
a0d1b1a2
DM
224 my $rpcenv = PVE::RPCEnvironment::get();
225 my $authuser = $rpcenv->get_user();
226
1e3baf05
DM
227 my $vmstatus = PVE::QemuServer::vmstatus();
228
a0d1b1a2
DM
229 my $res = [];
230 foreach my $vmid (keys %$vmstatus) {
231 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
232
233 my $data = $vmstatus->{$vmid};
234 $data->{vmid} = $vmid;
235 push @$res, $data;
236 }
1e3baf05 237
a0d1b1a2 238 return $res;
1e3baf05
DM
239 }});
240
241__PACKAGE__->register_method({
afdb31d5
DM
242 name => 'create_vm',
243 path => '',
1e3baf05 244 method => 'POST',
3e16d5fc 245 description => "Create or restore a virtual machine.",
a0d1b1a2
DM
246 permissions => {
247 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
248 check => [ 'or',
249 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
250 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
251 ],
252 },
1e3baf05
DM
253 protected => 1,
254 proxyto => 'node',
255 parameters => {
256 additionalProperties => 0,
257 properties => PVE::QemuServer::json_config_properties(
258 {
259 node => get_standard_option('pve-node'),
260 vmid => get_standard_option('pve-vmid'),
3e16d5fc
DM
261 archive => {
262 description => "The backup file.",
263 type => 'string',
264 optional => 1,
265 maxLength => 255,
266 },
267 storage => get_standard_option('pve-storage-id', {
268 description => "Default storage.",
269 optional => 1,
270 }),
271 force => {
afdb31d5 272 optional => 1,
3e16d5fc
DM
273 type => 'boolean',
274 description => "Allow to overwrite existing VM.",
51586c3a
DM
275 requires => 'archive',
276 },
277 unique => {
afdb31d5 278 optional => 1,
51586c3a
DM
279 type => 'boolean',
280 description => "Assign a unique random ethernet address.",
281 requires => 'archive',
3e16d5fc 282 },
a0d1b1a2
DM
283 pool => {
284 optional => 1,
285 type => 'string', format => 'pve-poolid',
286 description => "Add the VM to the specified pool.",
287 },
1e3baf05
DM
288 }),
289 },
afdb31d5 290 returns => {
5fdbe4f0
DM
291 type => 'string',
292 },
1e3baf05
DM
293 code => sub {
294 my ($param) = @_;
295
5fdbe4f0
DM
296 my $rpcenv = PVE::RPCEnvironment::get();
297
a0d1b1a2 298 my $authuser = $rpcenv->get_user();
5fdbe4f0 299
1e3baf05
DM
300 my $node = extract_param($param, 'node');
301
1e3baf05
DM
302 my $vmid = extract_param($param, 'vmid');
303
3e16d5fc
DM
304 my $archive = extract_param($param, 'archive');
305
306 my $storage = extract_param($param, 'storage');
307
51586c3a
DM
308 my $force = extract_param($param, 'force');
309
310 my $unique = extract_param($param, 'unique');
a0d1b1a2
DM
311
312 my $pool = extract_param($param, 'pool');
51586c3a 313
1e3baf05 314 my $filename = PVE::QemuServer::config_file($vmid);
afdb31d5
DM
315
316 my $storecfg = PVE::Storage::config();
1e3baf05 317
3e16d5fc 318 PVE::Cluster::check_cfs_quorum();
1e3baf05 319
a0d1b1a2
DM
320 if (defined($pool)) {
321 $rpcenv->check_pool_exist($pool);
a0d1b1a2
DM
322 }
323
fcbb753e 324 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
a0d1b1a2
DM
325 if defined($storage);
326
afdb31d5 327 if (!$archive) {
3e16d5fc 328 &$resolve_cdrom_alias($param);
1e3baf05 329
fcbb753e 330 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
ae57f6b3
DM
331
332 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
333
3e16d5fc
DM
334 foreach my $opt (keys %$param) {
335 if (PVE::QemuServer::valid_drivename($opt)) {
336 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
337 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
afdb31d5 338
3e16d5fc
DM
339 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
340 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
341 }
1e3baf05 342 }
3e16d5fc
DM
343
344 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
345 } else {
346 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
347 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
348
5b9d692a 349 if ($archive eq '-') {
afdb31d5 350 die "pipe requires cli environment\n"
d7810bc1 351 if $rpcenv->{type} ne 'cli';
5b9d692a 352 } else {
ba68cf09 353 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
d7810bc1
DM
354
355 PVE::Storage::activate_volumes($storecfg, [ $archive ])
356 if PVE::Storage::parse_volume_id ($archive, 1);
357
971f27c4
DM
358 die "can't find archive file '$archive'\n" if !($path && -f $path);
359 $archive = $path;
360 }
1e3baf05
DM
361 }
362
502d18a2
DM
363 my $addVMtoPoolFn = sub {
364 my $usercfg = cfs_read_file("user.cfg");
365 if (my $data = $usercfg->{pools}->{$pool}) {
366 $data->{vms}->{$vmid} = 1;
367 $usercfg->{vms}->{$vmid} = $pool;
368 cfs_write_file("user.cfg", $usercfg);
369 }
370 };
371
3e16d5fc
DM
372 my $restorefn = sub {
373
6116f729 374 # fixme: this test does not work if VM exists on other node!
3e16d5fc 375 if (-f $filename) {
afdb31d5 376 die "unable to restore vm $vmid: config file already exists\n"
51586c3a 377 if !$force;
3e16d5fc 378
afdb31d5 379 die "unable to restore vm $vmid: vm is running\n"
3e16d5fc
DM
380 if PVE::QemuServer::check_running($vmid);
381 }
382
383 my $realcmd = sub {
a0d1b1a2 384 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
51586c3a 385 storage => $storage,
a0d1b1a2 386 pool => $pool,
51586c3a 387 unique => $unique });
502d18a2
DM
388
389 PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
3e16d5fc
DM
390 };
391
a0d1b1a2 392 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
3e16d5fc 393 };
1e3baf05 394
1e3baf05
DM
395 my $createfn = sub {
396
191435c6 397 # test after locking
afdb31d5 398 die "unable to create vm $vmid: config file already exists\n"
1e3baf05
DM
399 if -f $filename;
400
5fdbe4f0 401 my $realcmd = sub {
1e3baf05 402
5fdbe4f0 403 my $vollist = [];
1e3baf05 404
1858638f
DM
405 my $conf = $param;
406
5fdbe4f0 407 eval {
ae57f6b3 408
1858638f 409 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 410
5fdbe4f0
DM
411 # try to be smart about bootdisk
412 my @disks = PVE::QemuServer::disknames();
413 my $firstdisk;
414 foreach my $ds (reverse @disks) {
1858638f
DM
415 next if !$conf->{$ds};
416 my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
5fdbe4f0
DM
417 next if PVE::QemuServer::drive_is_cdrom($disk);
418 $firstdisk = $ds;
419 }
1e3baf05 420
1858638f
DM
421 if (!$conf->{bootdisk} && $firstdisk) {
422 $conf->{bootdisk} = $firstdisk;
5fdbe4f0 423 }
1e3baf05 424
ae9ca91d
DM
425 PVE::QemuServer::update_config_nolock($vmid, $conf);
426
5fdbe4f0
DM
427 };
428 my $err = $@;
1e3baf05 429
5fdbe4f0
DM
430 if ($err) {
431 foreach my $volid (@$vollist) {
432 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
433 warn $@ if $@;
434 }
435 die "create failed - $err";
436 }
502d18a2
DM
437
438 PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
5fdbe4f0
DM
439 };
440
a0d1b1a2 441 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
442 };
443
191435c6 444 return PVE::QemuServer::lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
1e3baf05
DM
445 }});
446
447__PACKAGE__->register_method({
448 name => 'vmdiridx',
afdb31d5 449 path => '{vmid}',
1e3baf05
DM
450 method => 'GET',
451 proxyto => 'node',
452 description => "Directory index",
a0d1b1a2
DM
453 permissions => {
454 user => 'all',
455 },
1e3baf05
DM
456 parameters => {
457 additionalProperties => 0,
458 properties => {
459 node => get_standard_option('pve-node'),
460 vmid => get_standard_option('pve-vmid'),
461 },
462 },
463 returns => {
464 type => 'array',
465 items => {
466 type => "object",
467 properties => {
468 subdir => { type => 'string' },
469 },
470 },
471 links => [ { rel => 'child', href => "{subdir}" } ],
472 },
473 code => sub {
474 my ($param) = @_;
475
476 my $res = [
477 { subdir => 'config' },
478 { subdir => 'status' },
479 { subdir => 'unlink' },
480 { subdir => 'vncproxy' },
3ea94c60 481 { subdir => 'migrate' },
2f48a4f5 482 { subdir => 'resize' },
1e3baf05
DM
483 { subdir => 'rrd' },
484 { subdir => 'rrddata' },
91c94f0a 485 { subdir => 'monitor' },
7e7d7b61 486 { subdir => 'snapshot' },
1e3baf05 487 ];
afdb31d5 488
1e3baf05
DM
489 return $res;
490 }});
491
492__PACKAGE__->register_method({
afdb31d5
DM
493 name => 'rrd',
494 path => '{vmid}/rrd',
1e3baf05
DM
495 method => 'GET',
496 protected => 1, # fixme: can we avoid that?
497 permissions => {
378b359e 498 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
499 },
500 description => "Read VM RRD statistics (returns PNG)",
501 parameters => {
502 additionalProperties => 0,
503 properties => {
504 node => get_standard_option('pve-node'),
505 vmid => get_standard_option('pve-vmid'),
506 timeframe => {
507 description => "Specify the time frame you are interested in.",
508 type => 'string',
509 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
510 },
511 ds => {
512 description => "The list of datasources you want to display.",
513 type => 'string', format => 'pve-configid-list',
514 },
515 cf => {
516 description => "The RRD consolidation function",
517 type => 'string',
518 enum => [ 'AVERAGE', 'MAX' ],
519 optional => 1,
520 },
521 },
522 },
523 returns => {
524 type => "object",
525 properties => {
526 filename => { type => 'string' },
527 },
528 },
529 code => sub {
530 my ($param) = @_;
531
532 return PVE::Cluster::create_rrd_graph(
afdb31d5 533 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 534 $param->{ds}, $param->{cf});
afdb31d5 535
1e3baf05
DM
536 }});
537
538__PACKAGE__->register_method({
afdb31d5
DM
539 name => 'rrddata',
540 path => '{vmid}/rrddata',
1e3baf05
DM
541 method => 'GET',
542 protected => 1, # fixme: can we avoid that?
543 permissions => {
378b359e 544 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
545 },
546 description => "Read VM RRD statistics",
547 parameters => {
548 additionalProperties => 0,
549 properties => {
550 node => get_standard_option('pve-node'),
551 vmid => get_standard_option('pve-vmid'),
552 timeframe => {
553 description => "Specify the time frame you are interested in.",
554 type => 'string',
555 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
556 },
557 cf => {
558 description => "The RRD consolidation function",
559 type => 'string',
560 enum => [ 'AVERAGE', 'MAX' ],
561 optional => 1,
562 },
563 },
564 },
565 returns => {
566 type => "array",
567 items => {
568 type => "object",
569 properties => {},
570 },
571 },
572 code => sub {
573 my ($param) = @_;
574
575 return PVE::Cluster::create_rrd_data(
576 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
577 }});
578
579
580__PACKAGE__->register_method({
afdb31d5
DM
581 name => 'vm_config',
582 path => '{vmid}/config',
1e3baf05
DM
583 method => 'GET',
584 proxyto => 'node',
585 description => "Get virtual machine configuration.",
a0d1b1a2
DM
586 permissions => {
587 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
588 },
1e3baf05
DM
589 parameters => {
590 additionalProperties => 0,
591 properties => {
592 node => get_standard_option('pve-node'),
593 vmid => get_standard_option('pve-vmid'),
594 },
595 },
afdb31d5 596 returns => {
1e3baf05 597 type => "object",
554ac7e7
DM
598 properties => {
599 digest => {
600 type => 'string',
601 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
602 }
603 },
1e3baf05
DM
604 },
605 code => sub {
606 my ($param) = @_;
607
608 my $conf = PVE::QemuServer::load_config($param->{vmid});
609
22c377f0
DM
610 delete $conf->{snapshots};
611
1e3baf05
DM
612 return $conf;
613 }});
614
ae57f6b3
DM
615my $vm_is_volid_owner = sub {
616 my ($storecfg, $vmid, $volid) =@_;
617
618 if ($volid !~ m|^/|) {
619 my ($path, $owner);
620 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
621 if ($owner && ($owner == $vmid)) {
622 return 1;
623 }
624 }
625
626 return undef;
627};
628
629my $test_deallocate_drive = sub {
630 my ($storecfg, $vmid, $key, $drive, $force) = @_;
631
632 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
633 my $volid = $drive->{file};
634 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
635 if ($force || $key =~ m/^unused/) {
636 my $sid = PVE::Storage::parse_volume_id($volid);
637 return $sid;
638 }
639 }
640 }
641
642 return undef;
643};
644
5d7a6767 645my $delete_drive = sub {
1858638f 646 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
5d7a6767
DM
647
648 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
649 my $volid = $drive->{file};
ae57f6b3
DM
650 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
651 if ($force || $key =~ m/^unused/) {
652 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
7e4e69a6 653 die $@ if $@;
ae57f6b3
DM
654 } else {
655 PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
5d7a6767 656 }
ae57f6b3
DM
657 }
658 }
49f9db93
DM
659
660 delete $conf->{$key};
ae57f6b3
DM
661};
662
663my $vmconfig_delete_option = sub {
664 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
665
666 return if !defined($conf->{$opt});
667
668 my $isDisk = PVE::QemuServer::valid_drivename($opt)|| ($opt =~ m/^unused/);
669
670 if ($isDisk) {
671 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
672
673 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
674 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
fcbb753e 675 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
5d7a6767 676 }
ae57f6b3 677 }
9a8d6b66
AD
678
679 my $unplugwarning = "";
680 if($conf->{ostype} && $conf->{ostype} eq 'l26'){
681 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
682 }elsif($conf->{ostype} && $conf->{ostype} eq 'l24'){
683 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
684 }elsif(!$conf->{ostype} || ($conf->{ostype} && $conf->{ostype} eq 'other')){
685 $unplugwarning = "<br>verify that your guest support acpi hotplug";
686 }
687
e8a7e9b4
AD
688 if($opt eq 'tablet'){
689 PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, $opt);
690 }else{
691 die "error hot-unplug $opt $unplugwarning" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
692 }
cd6ecb89 693
ae57f6b3
DM
694 if ($isDisk) {
695 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
696 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
5d7a6767 697 } else {
ae57f6b3
DM
698 delete $conf->{$opt};
699 }
700
701 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
702};
703
9bf371a6 704my $safe_num_ne = sub {
93ae06e1
DM
705 my ($a, $b) = @_;
706
707 return 0 if !defined($a) && !defined($b);
708 return 1 if !defined($a);
709 return 1 if !defined($b);
710
711 return $a != $b;
712};
713
ae57f6b3
DM
714my $vmconfig_update_disk = sub {
715 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
5d7a6767 716
ae57f6b3
DM
717 my $drive = PVE::QemuServer::parse_drive($opt, $value);
718
719 if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
720 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
721 } else {
722 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
723 }
724
725 if ($conf->{$opt}) {
726
727 if (my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt})) {
728
729 my $media = $drive->{media} || 'disk';
730 my $oldmedia = $old_drive->{media} || 'disk';
731 die "unable to change media type\n" if $media ne $oldmedia;
732
733 if (!PVE::QemuServer::drive_is_cdrom($old_drive) &&
734 ($drive->{file} ne $old_drive->{file})) { # delete old disks
735
736 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
737 $conf = PVE::QemuServer::load_config($vmid); # update/reload
738 }
0f56d571 739
9bf371a6
DM
740 if(&$safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
741 &$safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
742 &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
743 &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
744 &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
745 &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr})) {
746 PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", $drive->{mbps}*1024*1024,
747 $drive->{mbps_rd}*1024*1024, $drive->{mbps_wr}*1024*1024,
748 $drive->{iops}, $drive->{iops_rd}, $drive->{iops_wr})
749 if !PVE::QemuServer::drive_is_cdrom($drive);
0f56d571 750 }
ae57f6b3
DM
751 }
752 }
753
754 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
755 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
756
757 $conf = PVE::QemuServer::load_config($vmid); # update/reload
758 $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
759
760 if (PVE::QemuServer::drive_is_cdrom($drive)) { # cdrom
761
762 if (PVE::QemuServer::check_running($vmid)) {
763 if ($drive->{file} eq 'none') {
ce156282 764 PVE::QemuServer::vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
ae57f6b3
DM
765 } else {
766 my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
ce156282
AD
767 PVE::QemuServer::vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt"); #force eject if locked
768 PVE::QemuServer::vm_mon_cmd($vmid, "change",device => "drive-$opt",target => "$path") if $path;
ae57f6b3
DM
769 }
770 }
771
772 } else { # hotplug new disks
773
774 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
5d7a6767 775 }
5d7a6767
DM
776};
777
ae57f6b3
DM
778my $vmconfig_update_net = sub {
779 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
780
de8f60b2
AD
781 if ($conf->{$opt} && PVE::QemuServer::check_running($vmid)) {
782 my $oldnet = PVE::QemuServer::parse_net($conf->{$opt});
783 my $newnet = PVE::QemuServer::parse_net($value);
784
785 if($oldnet->{model} ne $newnet->{model}){
786 #if model change, we try to hot-unplug
787 die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
788 }else{
789
790 if($newnet->{bridge} && $oldnet->{bridge}){
791 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
ae57f6b3 792
de8f60b2
AD
793 if($newnet->{rate} ne $oldnet->{rate}){
794 PVE::Network::tap_rate_limit($iface, $newnet->{rate});
795 }
796
797 if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} ne $oldnet->{tag})){
798 eval{PVE::Network::tap_unplug($iface, $oldnet->{bridge}, $oldnet->{tag});};
799 PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag});
800 }
801
802 }else{
803 #if bridge/nat mode change, we try to hot-unplug
804 die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
805 }
806 }
807
808 }
ae57f6b3
DM
809 $conf->{$opt} = $value;
810 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
811 $conf = PVE::QemuServer::load_config($vmid); # update/reload
812
813 my $net = PVE::QemuServer::parse_net($conf->{$opt});
814
815 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net);
816};
817
a0d1b1a2
DM
818my $vm_config_perm_list = [
819 'VM.Config.Disk',
820 'VM.Config.CDROM',
821 'VM.Config.CPU',
822 'VM.Config.Memory',
823 'VM.Config.Network',
824 'VM.Config.HWType',
825 'VM.Config.Options',
826 ];
827
1e3baf05 828__PACKAGE__->register_method({
afdb31d5
DM
829 name => 'update_vm',
830 path => '{vmid}/config',
1e3baf05
DM
831 method => 'PUT',
832 protected => 1,
833 proxyto => 'node',
834 description => "Set virtual machine options.",
a0d1b1a2
DM
835 permissions => {
836 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
837 },
1e3baf05
DM
838 parameters => {
839 additionalProperties => 0,
840 properties => PVE::QemuServer::json_config_properties(
841 {
842 node => get_standard_option('pve-node'),
843 vmid => get_standard_option('pve-vmid'),
3ea94c60 844 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
845 delete => {
846 type => 'string', format => 'pve-configid-list',
847 description => "A list of settings you want to delete.",
848 optional => 1,
849 },
850 force => {
851 type => 'boolean',
852 description => $opt_force_description,
853 optional => 1,
854 requires => 'delete',
855 },
554ac7e7
DM
856 digest => {
857 type => 'string',
858 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
859 maxLength => 40,
afdb31d5 860 optional => 1,
554ac7e7 861 }
1e3baf05
DM
862 }),
863 },
864 returns => { type => 'null'},
865 code => sub {
866 my ($param) = @_;
867
868 my $rpcenv = PVE::RPCEnvironment::get();
869
a0d1b1a2 870 my $authuser = $rpcenv->get_user();
1e3baf05
DM
871
872 my $node = extract_param($param, 'node');
873
1e3baf05
DM
874 my $vmid = extract_param($param, 'vmid');
875
5fdbe4f0
DM
876 my $digest = extract_param($param, 'digest');
877
878 my @paramarr = (); # used for log message
879 foreach my $key (keys %$param) {
880 push @paramarr, "-$key", $param->{$key};
881 }
882
1e3baf05 883 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 884 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 885 if $skiplock && $authuser ne 'root@pam';
1e3baf05 886
0532bc63
DM
887 my $delete_str = extract_param($param, 'delete');
888
1e3baf05
DM
889 my $force = extract_param($param, 'force');
890
0532bc63
DM
891 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
892
1e68cb19
DM
893 my $storecfg = PVE::Storage::config();
894
7bfdeb5f
DM
895 my $defaults = PVE::QemuServer::load_defaults();
896
1e68cb19
DM
897 &$resolve_cdrom_alias($param);
898
899 # now try to verify all parameters
900
0532bc63
DM
901 my @delete = ();
902 foreach my $opt (PVE::Tools::split_list($delete_str)) {
903 $opt = 'ide2' if $opt eq 'cdrom';
904 raise_param_exc({ delete => "you can't use '-$opt' and " .
905 "-delete $opt' at the same time" })
906 if defined($param->{$opt});
907
908 if (!PVE::QemuServer::option_exists($opt)) {
909 raise_param_exc({ delete => "unknown option '$opt'" });
910 }
ae57f6b3 911
0532bc63
DM
912 push @delete, $opt;
913 }
1e3baf05 914
1e68cb19
DM
915 foreach my $opt (keys %$param) {
916 if (PVE::QemuServer::valid_drivename($opt)) {
917 # cleanup drive path
918 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
919 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
920 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
1e68cb19
DM
921 } elsif ($opt =~ m/^net(\d+)$/) {
922 # add macaddr
923 my $net = PVE::QemuServer::parse_net($param->{$opt});
924 $param->{$opt} = PVE::QemuServer::print_net($net);
1e68cb19
DM
925 }
926 }
1e3baf05 927
ae57f6b3
DM
928 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
929
930 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
931
fcbb753e 932 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1e3baf05 933
5d39a182 934 my $updatefn = sub {
1e3baf05 935
5d39a182 936 my $conf = PVE::QemuServer::load_config($vmid);
1e3baf05 937
5d39a182
DM
938 die "checksum missmatch (file change by other user?)\n"
939 if $digest && $digest ne $conf->{digest};
1e3baf05 940
5d39a182 941 PVE::QemuServer::check_lock($conf) if !$skiplock;
1e3baf05 942
7bfdeb5f
DM
943 if ($param->{memory} || defined($param->{balloon})) {
944 my $maxmem = $param->{memory} || $conf->{memory} || $defaults->{memory};
945 my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{balloon};
946
947 die "balloon value too large (must be smaller than assigned memory)\n"
948 if $balloon > $maxmem;
949 }
950
a0d1b1a2 951 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
c2a64aa7 952
5d7a6767 953 foreach my $opt (@delete) { # delete
1e68cb19 954 $conf = PVE::QemuServer::load_config($vmid); # update/reload
ae57f6b3 955 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
5d39a182 956 }
1e3baf05 957
7bfdeb5f
DM
958 my $running = PVE::QemuServer::check_running($vmid);
959
5d7a6767 960 foreach my $opt (keys %$param) { # add/change
1e3baf05 961
1e68cb19 962 $conf = PVE::QemuServer::load_config($vmid); # update/reload
1e3baf05 963
5d7a6767
DM
964 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965
ae57f6b3 966 if (PVE::QemuServer::valid_drivename($opt)) {
5d7a6767 967
ae57f6b3
DM
968 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt}, $force);
1e68cb19 970
ae57f6b3 971 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1858638f 972
ae57f6b3
DM
973 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
974 $opt, $param->{$opt});
1e68cb19
DM
975
976 } else {
977
cd6ecb89
AD
978 if($opt eq 'tablet' && $param->{$opt} == 1){
979 PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, $opt);
980 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
981 PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
982 }
983
1858638f
DM
984 $conf->{$opt} = $param->{$opt};
985 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
3a1e36bb 986 }
5d39a182 987 }
7bfdeb5f
DM
988
989 # allow manual ballooning if shares is set to zero
990 if ($running && defined($param->{balloon}) &&
991 defined($conf->{shares}) && ($conf->{shares} == 0)) {
992 my $balloon = $param->{'balloon'} || $conf->{memory} || $defaults->{memory};
993 PVE::QemuServer::vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
994 }
995
5d39a182
DM
996 };
997
998 PVE::QemuServer::lock_config($vmid, $updatefn);
fcdb0117 999
1e3baf05
DM
1000 return undef;
1001 }});
1002
1003
1004__PACKAGE__->register_method({
afdb31d5
DM
1005 name => 'destroy_vm',
1006 path => '{vmid}',
1e3baf05
DM
1007 method => 'DELETE',
1008 protected => 1,
1009 proxyto => 'node',
1010 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
1011 permissions => {
1012 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1013 },
1e3baf05
DM
1014 parameters => {
1015 additionalProperties => 0,
1016 properties => {
1017 node => get_standard_option('pve-node'),
1018 vmid => get_standard_option('pve-vmid'),
3ea94c60 1019 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
1020 },
1021 },
afdb31d5 1022 returns => {
5fdbe4f0
DM
1023 type => 'string',
1024 },
1e3baf05
DM
1025 code => sub {
1026 my ($param) = @_;
1027
1028 my $rpcenv = PVE::RPCEnvironment::get();
1029
a0d1b1a2 1030 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1031
1032 my $vmid = $param->{vmid};
1033
1034 my $skiplock = $param->{skiplock};
afdb31d5 1035 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1036 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1037
5fdbe4f0
DM
1038 # test if VM exists
1039 my $conf = PVE::QemuServer::load_config($vmid);
1040
afdb31d5 1041 my $storecfg = PVE::Storage::config();
1e3baf05 1042
502d18a2
DM
1043 my $delVMfromPoolFn = sub {
1044 my $usercfg = cfs_read_file("user.cfg");
5d0094ea
DM
1045 if (my $pool = $usercfg->{vms}->{$vmid}) {
1046 if (my $data = $usercfg->{pools}->{$pool}) {
1047 delete $data->{vms}->{$vmid};
1048 delete $usercfg->{vms}->{$vmid};
1049 cfs_write_file("user.cfg", $usercfg);
1050 }
502d18a2
DM
1051 }
1052 };
1053
5fdbe4f0 1054 my $realcmd = sub {
ff1a2432
DM
1055 my $upid = shift;
1056
1057 syslog('info', "destroy VM $vmid: $upid\n");
1058
5fdbe4f0 1059 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
502d18a2
DM
1060
1061 PVE::AccessControl::lock_user_config($delVMfromPoolFn, "pool cleanup failed");
5fdbe4f0 1062 };
1e3baf05 1063
a0d1b1a2 1064 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1065 }});
1066
1067__PACKAGE__->register_method({
afdb31d5
DM
1068 name => 'unlink',
1069 path => '{vmid}/unlink',
1e3baf05
DM
1070 method => 'PUT',
1071 protected => 1,
1072 proxyto => 'node',
1073 description => "Unlink/delete disk images.",
a0d1b1a2
DM
1074 permissions => {
1075 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1076 },
1e3baf05
DM
1077 parameters => {
1078 additionalProperties => 0,
1079 properties => {
1080 node => get_standard_option('pve-node'),
1081 vmid => get_standard_option('pve-vmid'),
1082 idlist => {
1083 type => 'string', format => 'pve-configid-list',
1084 description => "A list of disk IDs you want to delete.",
1085 },
1086 force => {
1087 type => 'boolean',
1088 description => $opt_force_description,
1089 optional => 1,
1090 },
1091 },
1092 },
1093 returns => { type => 'null'},
1094 code => sub {
1095 my ($param) = @_;
1096
1097 $param->{delete} = extract_param($param, 'idlist');
1098
1099 __PACKAGE__->update_vm($param);
1100
1101 return undef;
1102 }});
1103
1104my $sslcert;
1105
1106__PACKAGE__->register_method({
afdb31d5
DM
1107 name => 'vncproxy',
1108 path => '{vmid}/vncproxy',
1e3baf05
DM
1109 method => 'POST',
1110 protected => 1,
1111 permissions => {
378b359e 1112 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
1113 },
1114 description => "Creates a TCP VNC proxy connections.",
1115 parameters => {
1116 additionalProperties => 0,
1117 properties => {
1118 node => get_standard_option('pve-node'),
1119 vmid => get_standard_option('pve-vmid'),
1120 },
1121 },
afdb31d5 1122 returns => {
1e3baf05
DM
1123 additionalProperties => 0,
1124 properties => {
1125 user => { type => 'string' },
1126 ticket => { type => 'string' },
1127 cert => { type => 'string' },
1128 port => { type => 'integer' },
1129 upid => { type => 'string' },
1130 },
1131 },
1132 code => sub {
1133 my ($param) = @_;
1134
1135 my $rpcenv = PVE::RPCEnvironment::get();
1136
a0d1b1a2 1137 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1138
1139 my $vmid = $param->{vmid};
1140 my $node = $param->{node};
1141
b6f39da2
DM
1142 my $authpath = "/vms/$vmid";
1143
a0d1b1a2 1144 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 1145
1e3baf05
DM
1146 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1147 if !$sslcert;
1148
1149 my $port = PVE::Tools::next_vnc_port();
1150
1151 my $remip;
afdb31d5 1152
4f1be36c 1153 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1e3baf05
DM
1154 $remip = PVE::Cluster::remote_node_ip($node);
1155 }
1156
6bb726c9
SP
1157 # NOTE: kvm VNC traffic is already TLS encrypted
1158 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1e3baf05 1159
afdb31d5 1160 my $timeout = 10;
1e3baf05
DM
1161
1162 my $realcmd = sub {
1163 my $upid = shift;
1164
1165 syslog('info', "starting vnc proxy $upid\n");
1166
1167 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1168
1169 my $qmstr = join(' ', @$qmcmd);
1170
1171 # also redirect stderr (else we get RFB protocol errors)
be62c45c 1172 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 1173
be62c45c 1174 PVE::Tools::run_command($cmd);
1e3baf05
DM
1175
1176 return;
1177 };
1178
a0d1b1a2 1179 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1e3baf05 1180
3da85107
DM
1181 PVE::Tools::wait_for_vnc_port($port);
1182
1e3baf05 1183 return {
a0d1b1a2 1184 user => $authuser,
1e3baf05 1185 ticket => $ticket,
afdb31d5
DM
1186 port => $port,
1187 upid => $upid,
1188 cert => $sslcert,
1e3baf05
DM
1189 };
1190 }});
1191
5fdbe4f0
DM
1192__PACKAGE__->register_method({
1193 name => 'vmcmdidx',
afdb31d5 1194 path => '{vmid}/status',
5fdbe4f0
DM
1195 method => 'GET',
1196 proxyto => 'node',
1197 description => "Directory index",
a0d1b1a2
DM
1198 permissions => {
1199 user => 'all',
1200 },
5fdbe4f0
DM
1201 parameters => {
1202 additionalProperties => 0,
1203 properties => {
1204 node => get_standard_option('pve-node'),
1205 vmid => get_standard_option('pve-vmid'),
1206 },
1207 },
1208 returns => {
1209 type => 'array',
1210 items => {
1211 type => "object",
1212 properties => {
1213 subdir => { type => 'string' },
1214 },
1215 },
1216 links => [ { rel => 'child', href => "{subdir}" } ],
1217 },
1218 code => sub {
1219 my ($param) = @_;
1220
1221 # test if VM exists
1222 my $conf = PVE::QemuServer::load_config($param->{vmid});
1223
1224 my $res = [
1225 { subdir => 'current' },
1226 { subdir => 'start' },
1227 { subdir => 'stop' },
1228 ];
afdb31d5 1229
5fdbe4f0
DM
1230 return $res;
1231 }});
1232
88fc87b4
DM
1233my $vm_is_ha_managed = sub {
1234 my ($vmid) = @_;
1235
1236 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1237 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
1238 return 1;
1239 }
1240 return 0;
1241};
1242
1e3baf05 1243__PACKAGE__->register_method({
afdb31d5 1244 name => 'vm_status',
5fdbe4f0 1245 path => '{vmid}/status/current',
1e3baf05
DM
1246 method => 'GET',
1247 proxyto => 'node',
1248 protected => 1, # qemu pid files are only readable by root
1249 description => "Get virtual machine status.",
a0d1b1a2
DM
1250 permissions => {
1251 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1252 },
1e3baf05
DM
1253 parameters => {
1254 additionalProperties => 0,
1255 properties => {
1256 node => get_standard_option('pve-node'),
1257 vmid => get_standard_option('pve-vmid'),
1258 },
1259 },
1260 returns => { type => 'object' },
1261 code => sub {
1262 my ($param) = @_;
1263
1264 # test if VM exists
1265 my $conf = PVE::QemuServer::load_config($param->{vmid});
1266
03a33f30 1267 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1268 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1269
88fc87b4 1270 $status->{ha} = &$vm_is_ha_managed($param->{vmid});
8610701a
DM
1271
1272 return $status;
1e3baf05
DM
1273 }});
1274
1275__PACKAGE__->register_method({
afdb31d5 1276 name => 'vm_start',
5fdbe4f0
DM
1277 path => '{vmid}/status/start',
1278 method => 'POST',
1e3baf05
DM
1279 protected => 1,
1280 proxyto => 'node',
5fdbe4f0 1281 description => "Start virtual machine.",
a0d1b1a2
DM
1282 permissions => {
1283 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1284 },
1e3baf05
DM
1285 parameters => {
1286 additionalProperties => 0,
1287 properties => {
1288 node => get_standard_option('pve-node'),
1289 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
1290 skiplock => get_standard_option('skiplock'),
1291 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c
AD
1292 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
1293
1e3baf05
DM
1294 },
1295 },
afdb31d5 1296 returns => {
5fdbe4f0
DM
1297 type => 'string',
1298 },
1e3baf05
DM
1299 code => sub {
1300 my ($param) = @_;
1301
1302 my $rpcenv = PVE::RPCEnvironment::get();
1303
a0d1b1a2 1304 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1305
1306 my $node = extract_param($param, 'node');
1307
1e3baf05
DM
1308 my $vmid = extract_param($param, 'vmid');
1309
3ea94c60 1310 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1311 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1312 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1313
1e3baf05 1314 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1315 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1316 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1317
7e8dcf2c
AD
1318 my $migratedfrom = extract_param($param, 'migratedfrom');
1319 raise_param_exc({ migratedfrom => "Only root may use this option." })
1320 if $migratedfrom && $authuser ne 'root@pam';
1321
afdb31d5 1322 my $storecfg = PVE::Storage::config();
5fdbe4f0 1323
cce37749
DM
1324 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1325 $rpcenv->{type} ne 'ha') {
5fdbe4f0 1326
88fc87b4
DM
1327 my $hacmd = sub {
1328 my $upid = shift;
5fdbe4f0 1329
88fc87b4 1330 my $service = "pvevm:$vmid";
5fdbe4f0 1331
88fc87b4
DM
1332 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1333
1334 print "Executing HA start for VM $vmid\n";
1335
1336 PVE::Tools::run_command($cmd);
1337
1338 return;
1339 };
1340
1341 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1342
1343 } else {
1344
1345 my $realcmd = sub {
1346 my $upid = shift;
1347
1348 syslog('info', "start VM $vmid: $upid\n");
1349
7e8dcf2c 1350 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
88fc87b4
DM
1351
1352 return;
1353 };
5fdbe4f0 1354
88fc87b4
DM
1355 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1356 }
5fdbe4f0
DM
1357 }});
1358
1359__PACKAGE__->register_method({
afdb31d5 1360 name => 'vm_stop',
5fdbe4f0
DM
1361 path => '{vmid}/status/stop',
1362 method => 'POST',
1363 protected => 1,
1364 proxyto => 'node',
1365 description => "Stop virtual machine.",
a0d1b1a2
DM
1366 permissions => {
1367 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1368 },
5fdbe4f0
DM
1369 parameters => {
1370 additionalProperties => 0,
1371 properties => {
1372 node => get_standard_option('pve-node'),
1373 vmid => get_standard_option('pve-vmid'),
1374 skiplock => get_standard_option('skiplock'),
af30308f 1375 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
c6bb9502
DM
1376 timeout => {
1377 description => "Wait maximal timeout seconds.",
1378 type => 'integer',
1379 minimum => 0,
1380 optional => 1,
254575e9
DM
1381 },
1382 keepActive => {
1383 description => "Do not decativate storage volumes.",
1384 type => 'boolean',
1385 optional => 1,
1386 default => 0,
c6bb9502 1387 }
5fdbe4f0
DM
1388 },
1389 },
afdb31d5 1390 returns => {
5fdbe4f0
DM
1391 type => 'string',
1392 },
1393 code => sub {
1394 my ($param) = @_;
1395
1396 my $rpcenv = PVE::RPCEnvironment::get();
1397
a0d1b1a2 1398 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1399
1400 my $node = extract_param($param, 'node');
1401
1402 my $vmid = extract_param($param, 'vmid');
1403
1404 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1405 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1406 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1407
254575e9 1408 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1409 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1410 if $keepActive && $authuser ne 'root@pam';
254575e9 1411
af30308f
DM
1412 my $migratedfrom = extract_param($param, 'migratedfrom');
1413 raise_param_exc({ migratedfrom => "Only root may use this option." })
1414 if $migratedfrom && $authuser ne 'root@pam';
1415
1416
ff1a2432
DM
1417 my $storecfg = PVE::Storage::config();
1418
3be30d63 1419 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
5fdbe4f0 1420
88fc87b4
DM
1421 my $hacmd = sub {
1422 my $upid = shift;
5fdbe4f0 1423
88fc87b4 1424 my $service = "pvevm:$vmid";
c6bb9502 1425
88fc87b4
DM
1426 my $cmd = ['clusvcadm', '-d', $service];
1427
1428 print "Executing HA stop for VM $vmid\n";
1429
1430 PVE::Tools::run_command($cmd);
5fdbe4f0 1431
88fc87b4
DM
1432 return;
1433 };
1434
1435 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1436
1437 } else {
1438 my $realcmd = sub {
1439 my $upid = shift;
1440
1441 syslog('info', "stop VM $vmid: $upid\n");
1442
1443 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 1444 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
1445
1446 return;
1447 };
1448
1449 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1450 }
5fdbe4f0
DM
1451 }});
1452
1453__PACKAGE__->register_method({
afdb31d5 1454 name => 'vm_reset',
5fdbe4f0
DM
1455 path => '{vmid}/status/reset',
1456 method => 'POST',
1457 protected => 1,
1458 proxyto => 'node',
1459 description => "Reset virtual machine.",
a0d1b1a2
DM
1460 permissions => {
1461 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1462 },
5fdbe4f0
DM
1463 parameters => {
1464 additionalProperties => 0,
1465 properties => {
1466 node => get_standard_option('pve-node'),
1467 vmid => get_standard_option('pve-vmid'),
1468 skiplock => get_standard_option('skiplock'),
1469 },
1470 },
afdb31d5 1471 returns => {
5fdbe4f0
DM
1472 type => 'string',
1473 },
1474 code => sub {
1475 my ($param) = @_;
1476
1477 my $rpcenv = PVE::RPCEnvironment::get();
1478
a0d1b1a2 1479 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1480
1481 my $node = extract_param($param, 'node');
1482
1483 my $vmid = extract_param($param, 'vmid');
1484
1485 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1486 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1487 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1488
ff1a2432
DM
1489 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1490
5fdbe4f0
DM
1491 my $realcmd = sub {
1492 my $upid = shift;
1493
1e3baf05 1494 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1495
1496 return;
1497 };
1498
a0d1b1a2 1499 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1500 }});
1501
1502__PACKAGE__->register_method({
afdb31d5 1503 name => 'vm_shutdown',
5fdbe4f0
DM
1504 path => '{vmid}/status/shutdown',
1505 method => 'POST',
1506 protected => 1,
1507 proxyto => 'node',
1508 description => "Shutdown virtual machine.",
a0d1b1a2
DM
1509 permissions => {
1510 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1511 },
5fdbe4f0
DM
1512 parameters => {
1513 additionalProperties => 0,
1514 properties => {
1515 node => get_standard_option('pve-node'),
1516 vmid => get_standard_option('pve-vmid'),
1517 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1518 timeout => {
1519 description => "Wait maximal timeout seconds.",
1520 type => 'integer',
1521 minimum => 0,
1522 optional => 1,
9269013a
DM
1523 },
1524 forceStop => {
1525 description => "Make sure the VM stops.",
1526 type => 'boolean',
1527 optional => 1,
1528 default => 0,
254575e9
DM
1529 },
1530 keepActive => {
1531 description => "Do not decativate storage volumes.",
1532 type => 'boolean',
1533 optional => 1,
1534 default => 0,
c6bb9502 1535 }
5fdbe4f0
DM
1536 },
1537 },
afdb31d5 1538 returns => {
5fdbe4f0
DM
1539 type => 'string',
1540 },
1541 code => sub {
1542 my ($param) = @_;
1543
1544 my $rpcenv = PVE::RPCEnvironment::get();
1545
a0d1b1a2 1546 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1547
1548 my $node = extract_param($param, 'node');
1549
1550 my $vmid = extract_param($param, 'vmid');
1551
1552 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1553 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1554 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1555
254575e9 1556 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1557 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1558 if $keepActive && $authuser ne 'root@pam';
254575e9 1559
02d07cf5
DM
1560 my $storecfg = PVE::Storage::config();
1561
5fdbe4f0
DM
1562 my $realcmd = sub {
1563 my $upid = shift;
1564
1565 syslog('info', "shutdown VM $vmid: $upid\n");
1566
afdb31d5 1567 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
254575e9 1568 1, $param->{forceStop}, $keepActive);
c6bb9502 1569
5fdbe4f0
DM
1570 return;
1571 };
1572
a0d1b1a2 1573 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1574 }});
1575
1576__PACKAGE__->register_method({
afdb31d5 1577 name => 'vm_suspend',
5fdbe4f0
DM
1578 path => '{vmid}/status/suspend',
1579 method => 'POST',
1580 protected => 1,
1581 proxyto => 'node',
1582 description => "Suspend virtual machine.",
a0d1b1a2
DM
1583 permissions => {
1584 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1585 },
5fdbe4f0
DM
1586 parameters => {
1587 additionalProperties => 0,
1588 properties => {
1589 node => get_standard_option('pve-node'),
1590 vmid => get_standard_option('pve-vmid'),
1591 skiplock => get_standard_option('skiplock'),
1592 },
1593 },
afdb31d5 1594 returns => {
5fdbe4f0
DM
1595 type => 'string',
1596 },
1597 code => sub {
1598 my ($param) = @_;
1599
1600 my $rpcenv = PVE::RPCEnvironment::get();
1601
a0d1b1a2 1602 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1603
1604 my $node = extract_param($param, 'node');
1605
1606 my $vmid = extract_param($param, 'vmid');
1607
1608 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1609 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1610 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1611
ff1a2432
DM
1612 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1613
5fdbe4f0
DM
1614 my $realcmd = sub {
1615 my $upid = shift;
1616
1617 syslog('info', "suspend VM $vmid: $upid\n");
1618
1e3baf05 1619 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1620
1621 return;
1622 };
1623
a0d1b1a2 1624 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1625 }});
1626
1627__PACKAGE__->register_method({
afdb31d5 1628 name => 'vm_resume',
5fdbe4f0
DM
1629 path => '{vmid}/status/resume',
1630 method => 'POST',
1631 protected => 1,
1632 proxyto => 'node',
1633 description => "Resume virtual machine.",
a0d1b1a2
DM
1634 permissions => {
1635 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1636 },
5fdbe4f0
DM
1637 parameters => {
1638 additionalProperties => 0,
1639 properties => {
1640 node => get_standard_option('pve-node'),
1641 vmid => get_standard_option('pve-vmid'),
1642 skiplock => get_standard_option('skiplock'),
1643 },
1644 },
afdb31d5 1645 returns => {
5fdbe4f0
DM
1646 type => 'string',
1647 },
1648 code => sub {
1649 my ($param) = @_;
1650
1651 my $rpcenv = PVE::RPCEnvironment::get();
1652
a0d1b1a2 1653 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1654
1655 my $node = extract_param($param, 'node');
1656
1657 my $vmid = extract_param($param, 'vmid');
1658
1659 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1660 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1661 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1662
b7eeab21 1663 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1664
5fdbe4f0
DM
1665 my $realcmd = sub {
1666 my $upid = shift;
1667
1668 syslog('info', "resume VM $vmid: $upid\n");
1669
1e3baf05 1670 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1671
5fdbe4f0
DM
1672 return;
1673 };
1674
a0d1b1a2 1675 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1676 }});
1677
1678__PACKAGE__->register_method({
afdb31d5 1679 name => 'vm_sendkey',
5fdbe4f0
DM
1680 path => '{vmid}/sendkey',
1681 method => 'PUT',
1682 protected => 1,
1683 proxyto => 'node',
1684 description => "Send key event to virtual machine.",
a0d1b1a2
DM
1685 permissions => {
1686 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1687 },
5fdbe4f0
DM
1688 parameters => {
1689 additionalProperties => 0,
1690 properties => {
1691 node => get_standard_option('pve-node'),
1692 vmid => get_standard_option('pve-vmid'),
1693 skiplock => get_standard_option('skiplock'),
1694 key => {
1695 description => "The key (qemu monitor encoding).",
1696 type => 'string'
1697 }
1698 },
1699 },
1700 returns => { type => 'null'},
1701 code => sub {
1702 my ($param) = @_;
1703
1704 my $rpcenv = PVE::RPCEnvironment::get();
1705
a0d1b1a2 1706 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1707
1708 my $node = extract_param($param, 'node');
1709
1710 my $vmid = extract_param($param, 'vmid');
1711
1712 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1713 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1714 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
1715
1716 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1717
1718 return;
1e3baf05
DM
1719 }});
1720
1ac0d2ee
AD
1721__PACKAGE__->register_method({
1722 name => 'vm_feature',
1723 path => '{vmid}/feature',
1724 method => 'GET',
1725 proxyto => 'node',
1726 protected => 1,
1727 description => "Check if feature for virtual machine is available.",
1728 permissions => {
1729 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1730 },
1731 parameters => {
1732 additionalProperties => 0,
1733 properties => {
1734 node => get_standard_option('pve-node'),
1735 vmid => get_standard_option('pve-vmid'),
1736 feature => {
1737 description => "Feature to check.",
1738 type => 'string',
1739 enum => [ 'snapshot', 'clone' ],
1740 },
1741 snapname => get_standard_option('pve-snapshot-name', {
1742 optional => 1,
1743 }),
1744 },
1ac0d2ee
AD
1745 },
1746 returns => {
1747 type => 'boolean'
1748 },
1749 code => sub {
1750 my ($param) = @_;
1751
1752 my $node = extract_param($param, 'node');
1753
1754 my $vmid = extract_param($param, 'vmid');
1755
1756 my $snapname = extract_param($param, 'snapname');
1757
1758 my $feature = extract_param($param, 'feature');
1759
1760 my $running = PVE::QemuServer::check_running($vmid);
1761
1762 my $conf = PVE::QemuServer::load_config($vmid);
1763
1764 if($snapname){
1765 my $snap = $conf->{snapshots}->{$snapname};
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1767 $conf = $snap;
1768 }
1769 my $storecfg = PVE::Storage::config();
1770
1771 my $hasfeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
1772 my $res = $hasfeature ? 1 : 0 ;
1773 return $res;
1774 }});
1775
6116f729
DM
1776__PACKAGE__->register_method({
1777 name => 'copy_vm',
1778 path => '{vmid}/copy',
1779 method => 'POST',
1780 protected => 1,
1781 proxyto => 'node',
37329185 1782 description => "Create a copy of virtual machine/template.",
6116f729
DM
1783 permissions => {
1784 description => "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1785 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1786 "'Datastore.AllocateSpace' on any used storage.",
1787 check =>
1788 [ 'and',
1789 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1790 [ 'or',
1791 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1792 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1793 ],
1794 ]
1795 },
1796 parameters => {
1797 additionalProperties => 0,
1798 properties => {
6116f729
DM
1799 node => get_standard_option('pve-node'),
1800 vmid => get_standard_option('pve-vmid'),
a60ab1a6
DM
1801 newid => get_standard_option('pve-vmid', { description => 'VMID for the copy.' }),
1802 name => {
1803 optional => 1,
1804 type => 'string', format => 'dns-name',
1805 description => "Set a name for the new VM.",
1806 },
1807 description => {
1808 optional => 1,
1809 type => 'string',
1810 description => "Description for the new VM.",
1811 },
6116f729
DM
1812 pool => {
1813 optional => 1,
1814 type => 'string', format => 'pve-poolid',
1815 description => "Add the new VM to the specified pool.",
1816 },
9076d880
DM
1817 snapname => get_standard_option('pve-snapshot-name', {
1818 requires => 'full',
1819 optional => 1,
1820 }),
81f043eb
AD
1821 storage => get_standard_option('pve-storage-id', {
1822 description => "Target storage for full copy.",
4e4f83fe 1823 requires => 'full',
81f043eb
AD
1824 optional => 1,
1825 }),
42a19c87
AD
1826 format => {
1827 description => "Target format for file storage.",
1828 requires => 'full',
1829 type => 'string',
1830 optional => 1,
1831 enum => [ 'raw', 'qcow2', 'vmdk'],
1832 },
6116f729
DM
1833 full => {
1834 optional => 1,
1835 type => 'boolean',
1836 description => "Create a full copy of all disk. This is always done when " .
1837 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1838 default => 0,
1839 },
1840 },
1841 },
1842 returns => {
1843 type => 'string',
1844 },
1845 code => sub {
1846 my ($param) = @_;
1847
1848 my $rpcenv = PVE::RPCEnvironment::get();
1849
1850 my $authuser = $rpcenv->get_user();
1851
1852 my $node = extract_param($param, 'node');
1853
1854 my $vmid = extract_param($param, 'vmid');
1855
1856 my $newid = extract_param($param, 'newid');
1857
1858 # fixme: update pool after create
1859 my $pool = extract_param($param, 'pool');
1860
1861 if (defined($pool)) {
1862 $rpcenv->check_pool_exist($pool);
1863 }
1864
9076d880
DM
1865 my $snapname = extract_param($param, 'snapname');
1866
81f043eb
AD
1867 my $storage = extract_param($param, 'storage');
1868
42a19c87
AD
1869 my $format = extract_param($param, 'format');
1870
6116f729
DM
1871 my $storecfg = PVE::Storage::config();
1872
1873 PVE::Cluster::check_cfs_quorum();
1874
4e4f83fe
DM
1875 my $running = PVE::QemuServer::check_running($vmid) || 0;
1876
1877 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1878
1879 # exclusive lock if VM is running - else shared lock is enough;
1880 my $shared_lock = $running ? 0 : 1;
1881
6116f729
DM
1882 # fixme: do early checks - re-check after lock
1883
1884 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1885
1886 my $copyfn = sub {
1887
1888 # all tests after lock
1889 my $conf = PVE::QemuServer::load_config($vmid);
1890
1891 PVE::QemuServer::check_lock($conf);
1892
4e4f83fe 1893 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 1894
4e4f83fe 1895 die "unexpected state change\n" if $verify_running != $running;
6116f729 1896
9076d880
DM
1897 die "snapshot '$snapname' does not exist\n"
1898 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 1899
9076d880
DM
1900 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
1901
1902 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729
DM
1903
1904 my $conffile = PVE::QemuServer::config_file($newid);
1905
1906 die "unable to create VM $newid: config file already exists\n"
1907 if -f $conffile;
1908
1909 # create empty/temp config - this fails if VM already exists on other node
b83e0181 1910 PVE::Tools::file_set_contents($conffile, "# qmcopy temporary file\nlock: copy\n");
6116f729
DM
1911
1912 my $realcmd = sub {
1913 my $upid = shift;
1914
b83e0181 1915 my $newvollist = [];
6116f729 1916
b83e0181
DM
1917 eval {
1918 my $newconf = { lock => 'copy' };
6116f729
DM
1919 my $drives = {};
1920 my $vollist = [];
6116f729 1921
9076d880
DM
1922 foreach my $opt (keys %$oldconf) {
1923 my $value = $oldconf->{$opt};
1924
1925 # do not copy snapshot related info
1926 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1927 $opt eq 'vmstate' || $opt eq 'snapstate';
b83e0181 1928
6116f729
DM
1929 # always change MAC! address
1930 if ($opt =~ m/^net(\d+)$/) {
1931 my $net = PVE::QemuServer::parse_net($value);
1932 $net->{macaddr} = PVE::Tools::random_ether_addr();
1933 $newconf->{$opt} = PVE::QemuServer::print_net($net);
1934 } elsif (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
1935 if (PVE::QemuServer::drive_is_cdrom($drive)) {
1936 $newconf->{$opt} = $value; # simply copy configuration
1937 } else {
1938 $drives->{$opt} = $drive;
1939 push @$vollist, $drive->{file};
1940 }
1941 } else {
1942 # copy everything else
1943 $newconf->{$opt} = $value;
1944 }
1945 }
1946
1947 delete $newconf->{template};
a60ab1a6
DM
1948
1949 if ($param->{name}) {
1950 $newconf->{name} = $param->{name};
1951 } else {
1952 $newconf->{name} = "Copy-of-$oldconf->{name}";
1953 }
1954
1955 if ($param->{description}) {
1956 $newconf->{description} = $param->{description};
1957 }
6116f729
DM
1958
1959 PVE::Storage::activate_volumes($storecfg, $vollist);
1960
b83e0181
DM
1961 eval {
1962 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
6116f729 1963
b83e0181
DM
1964 foreach my $opt (keys %$drives) {
1965 my $drive = $drives->{$opt};
6116f729 1966
b83e0181
DM
1967 my $newvolid;
1968 if (!$param->{full} && PVE::Storage::volume_is_base($storecfg, $drive->{file})) {
1969 print "clone drive $opt ($drive->{file})\n";
1970 $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newid);
1971 } else {
1972 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
81f043eb 1973 $storeid = $storage if $storage;
42a19c87
AD
1974
1975 my $fmt = undef;
1976 if($format){
1977 $fmt = $format;
1978 }else{
1979 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
1980 $fmt = $drive->{format} || $defformat;
1981 }
b83e0181
DM
1982
1983 my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
1984
1985 print "copy drive $opt ($drive->{file})\n";
1986 $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1987
9076d880 1988 PVE::QemuServer::qemu_img_convert($drive->{file}, $newvolid, $size, $snapname);
b83e0181
DM
1989 }
1990
1991 my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
1992 my $disk = { file => $newvolid, size => $size };
1993 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $disk);
1994 push @$newvollist, $newvolid;
6116f729 1995
b83e0181
DM
1996 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
1997 }
1998 };
1999 die $@ if $@;
2000
2001 delete $newconf->{lock};
2002 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
6116f729
DM
2003 };
2004 if (my $err = $@) {
6116f729
DM
2005 unlink $conffile;
2006
b83e0181
DM
2007 sleep 1; # some storage like rbd need to wait before release volume - really?
2008
2009 foreach my $volid (@$newvollist) {
2010 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2011 warn $@ if $@;
2012 }
6116f729
DM
2013 die "copy failed: $err";
2014 }
2015
2016 return;
2017 };
2018
2019 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
2020 };
2021
4e4f83fe 2022 return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729
DM
2023 # Aquire exclusive lock lock for $newid
2024 return PVE::QemuServer::lock_config_full($newid, 1, $copyfn);
2025 });
2026
2027 }});
2028
3ea94c60 2029__PACKAGE__->register_method({
afdb31d5 2030 name => 'migrate_vm',
3ea94c60
DM
2031 path => '{vmid}/migrate',
2032 method => 'POST',
2033 protected => 1,
2034 proxyto => 'node',
2035 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2036 permissions => {
2037 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2038 },
3ea94c60
DM
2039 parameters => {
2040 additionalProperties => 0,
2041 properties => {
2042 node => get_standard_option('pve-node'),
2043 vmid => get_standard_option('pve-vmid'),
2044 target => get_standard_option('pve-node', { description => "Target node." }),
2045 online => {
2046 type => 'boolean',
2047 description => "Use online/live migration.",
2048 optional => 1,
2049 },
2050 force => {
2051 type => 'boolean',
2052 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2053 optional => 1,
2054 },
2055 },
2056 },
afdb31d5 2057 returns => {
3ea94c60
DM
2058 type => 'string',
2059 description => "the task ID.",
2060 },
2061 code => sub {
2062 my ($param) = @_;
2063
2064 my $rpcenv = PVE::RPCEnvironment::get();
2065
a0d1b1a2 2066 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2067
2068 my $target = extract_param($param, 'target');
2069
2070 my $localnode = PVE::INotify::nodename();
2071 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2072
2073 PVE::Cluster::check_cfs_quorum();
2074
2075 PVE::Cluster::check_node_exists($target);
2076
2077 my $targetip = PVE::Cluster::remote_node_ip($target);
2078
2079 my $vmid = extract_param($param, 'vmid');
2080
afdb31d5 2081 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2082 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
2083
2084 # test if VM exists
a5ed42d3 2085 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
2086
2087 # try to detect errors early
a5ed42d3
DM
2088
2089 PVE::QemuServer::check_lock($conf);
2090
3ea94c60 2091 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2092 die "cant migrate running VM without --online\n"
3ea94c60
DM
2093 if !$param->{online};
2094 }
2095
47152e2e 2096 my $storecfg = PVE::Storage::config();
22d646a7 2097 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
47152e2e 2098
3be30d63 2099 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2100
88fc87b4
DM
2101 my $hacmd = sub {
2102 my $upid = shift;
3ea94c60 2103
88fc87b4
DM
2104 my $service = "pvevm:$vmid";
2105
2106 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2107
2108 print "Executing HA migrate for VM $vmid to node $target\n";
2109
2110 PVE::Tools::run_command($cmd);
2111
2112 return;
2113 };
2114
2115 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2116
2117 } else {
2118
2119 my $realcmd = sub {
2120 my $upid = shift;
2121
2122 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2123 };
2124
2125 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2126 }
3ea94c60 2127
3ea94c60 2128 }});
1e3baf05 2129
91c94f0a 2130__PACKAGE__->register_method({
afdb31d5
DM
2131 name => 'monitor',
2132 path => '{vmid}/monitor',
91c94f0a
DM
2133 method => 'POST',
2134 protected => 1,
2135 proxyto => 'node',
2136 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
2137 permissions => {
2138 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2139 },
91c94f0a
DM
2140 parameters => {
2141 additionalProperties => 0,
2142 properties => {
2143 node => get_standard_option('pve-node'),
2144 vmid => get_standard_option('pve-vmid'),
2145 command => {
2146 type => 'string',
2147 description => "The monitor command.",
2148 }
2149 },
2150 },
2151 returns => { type => 'string'},
2152 code => sub {
2153 my ($param) = @_;
2154
2155 my $vmid = $param->{vmid};
2156
2157 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
2158
2159 my $res = '';
2160 eval {
7b7c6d1b 2161 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2162 };
2163 $res = "ERROR: $@" if $@;
2164
2165 return $res;
2166 }});
2167
0d02881c
AD
2168__PACKAGE__->register_method({
2169 name => 'resize_vm',
614e3941 2170 path => '{vmid}/resize',
0d02881c
AD
2171 method => 'PUT',
2172 protected => 1,
2173 proxyto => 'node',
2f48a4f5 2174 description => "Extend volume size.",
0d02881c 2175 permissions => {
3b2773f6 2176 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
2177 },
2178 parameters => {
2179 additionalProperties => 0,
2f48a4f5
DM
2180 properties => {
2181 node => get_standard_option('pve-node'),
2182 vmid => get_standard_option('pve-vmid'),
2183 skiplock => get_standard_option('skiplock'),
2184 disk => {
2185 type => 'string',
2186 description => "The disk you want to resize.",
2187 enum => [PVE::QemuServer::disknames()],
2188 },
2189 size => {
2190 type => 'string',
f91b2e45 2191 pattern => '\+?\d+(\.\d+)?[KMGT]?',
2f48a4f5
DM
2192 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.",
2193 },
2194 digest => {
2195 type => 'string',
2196 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2197 maxLength => 40,
2198 optional => 1,
2199 },
2200 },
0d02881c
AD
2201 },
2202 returns => { type => 'null'},
2203 code => sub {
2204 my ($param) = @_;
2205
2206 my $rpcenv = PVE::RPCEnvironment::get();
2207
2208 my $authuser = $rpcenv->get_user();
2209
2210 my $node = extract_param($param, 'node');
2211
2212 my $vmid = extract_param($param, 'vmid');
2213
2214 my $digest = extract_param($param, 'digest');
2215
2f48a4f5
DM
2216 my $disk = extract_param($param, 'disk');
2217
2218 my $sizestr = extract_param($param, 'size');
0d02881c 2219
f91b2e45 2220 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
2221 raise_param_exc({ skiplock => "Only root may use this option." })
2222 if $skiplock && $authuser ne 'root@pam';
2223
0d02881c
AD
2224 my $storecfg = PVE::Storage::config();
2225
0d02881c
AD
2226 my $updatefn = sub {
2227
2228 my $conf = PVE::QemuServer::load_config($vmid);
2229
2230 die "checksum missmatch (file change by other user?)\n"
2231 if $digest && $digest ne $conf->{digest};
2232 PVE::QemuServer::check_lock($conf) if !$skiplock;
2233
f91b2e45
DM
2234 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2235
2236 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2237
2238 my $volid = $drive->{file};
2239
2240 die "disk '$disk' has no associated volume\n" if !$volid;
2241
2242 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2243
f2965e67
AD
2244 die "you can't online resize a virtio windows bootdisk\n"
2245 if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
2246
f91b2e45
DM
2247 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
2248
2249 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2250
2251 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
2252
2253 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2254 my ($ext, $newsize, $unit) = ($1, $2, $4);
2255 if ($unit) {
2256 if ($unit eq 'K') {
2257 $newsize = $newsize * 1024;
2258 } elsif ($unit eq 'M') {
2259 $newsize = $newsize * 1024 * 1024;
2260 } elsif ($unit eq 'G') {
2261 $newsize = $newsize * 1024 * 1024 * 1024;
2262 } elsif ($unit eq 'T') {
2263 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2264 }
2265 }
2266 $newsize += $size if $ext;
2267 $newsize = int($newsize);
2268
2269 die "unable to skrink disk size\n" if $newsize < $size;
2270
2271 return if $size == $newsize;
2272
2f48a4f5 2273 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 2274
f91b2e45
DM
2275 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2276
2277 $drive->{size} = $newsize;
2278 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
2279
2280 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2281 };
0d02881c
AD
2282
2283 PVE::QemuServer::lock_config($vmid, $updatefn);
2284 return undef;
2285 }});
2286
9dbd1ee4 2287__PACKAGE__->register_method({
7e7d7b61 2288 name => 'snapshot_list',
9dbd1ee4 2289 path => '{vmid}/snapshot',
7e7d7b61
DM
2290 method => 'GET',
2291 description => "List all snapshots.",
2292 permissions => {
2293 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2294 },
2295 proxyto => 'node',
2296 protected => 1, # qemu pid files are only readable by root
2297 parameters => {
2298 additionalProperties => 0,
2299 properties => {
2300 vmid => get_standard_option('pve-vmid'),
2301 node => get_standard_option('pve-node'),
2302 },
2303 },
2304 returns => {
2305 type => 'array',
2306 items => {
2307 type => "object",
2308 properties => {},
2309 },
2310 links => [ { rel => 'child', href => "{name}" } ],
2311 },
2312 code => sub {
2313 my ($param) = @_;
2314
6aa4651b
DM
2315 my $vmid = $param->{vmid};
2316
2317 my $conf = PVE::QemuServer::load_config($vmid);
7e7d7b61
DM
2318 my $snaphash = $conf->{snapshots} || {};
2319
2320 my $res = [];
2321
2322 foreach my $name (keys %$snaphash) {
0ea6bc69 2323 my $d = $snaphash->{$name};
982c7f12
DM
2324 my $item = {
2325 name => $name,
2326 snaptime => $d->{snaptime} || 0,
6aa4651b 2327 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
2328 description => $d->{description} || '',
2329 };
0ea6bc69 2330 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 2331 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
2332 push @$res, $item;
2333 }
2334
6aa4651b
DM
2335 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
2336 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
2337 $current->{parent} = $conf->{parent} if $conf->{parent};
2338
2339 push @$res, $current;
7e7d7b61
DM
2340
2341 return $res;
2342 }});
2343
2344__PACKAGE__->register_method({
2345 name => 'snapshot',
2346 path => '{vmid}/snapshot',
2347 method => 'POST',
9dbd1ee4
AD
2348 protected => 1,
2349 proxyto => 'node',
2350 description => "Snapshot a VM.",
2351 permissions => {
f1baf1df 2352 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
2353 },
2354 parameters => {
2355 additionalProperties => 0,
2356 properties => {
2357 node => get_standard_option('pve-node'),
2358 vmid => get_standard_option('pve-vmid'),
8abd398b 2359 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
2360 vmstate => {
2361 optional => 1,
2362 type => 'boolean',
2363 description => "Save the vmstate",
2364 },
2365 freezefs => {
2366 optional => 1,
2367 type => 'boolean',
2368 description => "Freeze the filesystem",
2369 },
782f4f75
DM
2370 description => {
2371 optional => 1,
2372 type => 'string',
2373 description => "A textual description or comment.",
2374 },
9dbd1ee4
AD
2375 },
2376 },
7e7d7b61
DM
2377 returns => {
2378 type => 'string',
2379 description => "the task ID.",
2380 },
9dbd1ee4
AD
2381 code => sub {
2382 my ($param) = @_;
2383
2384 my $rpcenv = PVE::RPCEnvironment::get();
2385
2386 my $authuser = $rpcenv->get_user();
2387
2388 my $node = extract_param($param, 'node');
2389
2390 my $vmid = extract_param($param, 'vmid');
2391
9dbd1ee4
AD
2392 my $snapname = extract_param($param, 'snapname');
2393
d1914468
DM
2394 die "unable to use snapshot name 'current' (reserved name)\n"
2395 if $snapname eq 'current';
2396
7e7d7b61 2397 my $realcmd = sub {
22c377f0 2398 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
782f4f75
DM
2399 PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
2400 $param->{freezefs}, $param->{description});
7e7d7b61
DM
2401 };
2402
2403 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2404 }});
2405
154ccdcd
DM
2406__PACKAGE__->register_method({
2407 name => 'snapshot_cmd_idx',
2408 path => '{vmid}/snapshot/{snapname}',
2409 description => '',
2410 method => 'GET',
2411 permissions => {
2412 user => 'all',
2413 },
2414 parameters => {
2415 additionalProperties => 0,
2416 properties => {
2417 vmid => get_standard_option('pve-vmid'),
2418 node => get_standard_option('pve-node'),
8abd398b 2419 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
2420 },
2421 },
2422 returns => {
2423 type => 'array',
2424 items => {
2425 type => "object",
2426 properties => {},
2427 },
2428 links => [ { rel => 'child', href => "{cmd}" } ],
2429 },
2430 code => sub {
2431 my ($param) = @_;
2432
2433 my $res = [];
2434
2435 push @$res, { cmd => 'rollback' };
d788cea6 2436 push @$res, { cmd => 'config' };
154ccdcd
DM
2437
2438 return $res;
2439 }});
2440
d788cea6
DM
2441__PACKAGE__->register_method({
2442 name => 'update_snapshot_config',
2443 path => '{vmid}/snapshot/{snapname}/config',
2444 method => 'PUT',
2445 protected => 1,
2446 proxyto => 'node',
2447 description => "Update snapshot metadata.",
2448 permissions => {
2449 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2450 },
2451 parameters => {
2452 additionalProperties => 0,
2453 properties => {
2454 node => get_standard_option('pve-node'),
2455 vmid => get_standard_option('pve-vmid'),
2456 snapname => get_standard_option('pve-snapshot-name'),
2457 description => {
2458 optional => 1,
2459 type => 'string',
2460 description => "A textual description or comment.",
2461 },
2462 },
2463 },
2464 returns => { type => 'null' },
2465 code => sub {
2466 my ($param) = @_;
2467
2468 my $rpcenv = PVE::RPCEnvironment::get();
2469
2470 my $authuser = $rpcenv->get_user();
2471
2472 my $vmid = extract_param($param, 'vmid');
2473
2474 my $snapname = extract_param($param, 'snapname');
2475
2476 return undef if !defined($param->{description});
2477
2478 my $updatefn = sub {
2479
2480 my $conf = PVE::QemuServer::load_config($vmid);
2481
2482 PVE::QemuServer::check_lock($conf);
2483
2484 my $snap = $conf->{snapshots}->{$snapname};
2485
2486 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2487
2488 $snap->{description} = $param->{description} if defined($param->{description});
2489
2490 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2491 };
2492
2493 PVE::QemuServer::lock_config($vmid, $updatefn);
2494
2495 return undef;
2496 }});
2497
2498__PACKAGE__->register_method({
2499 name => 'get_snapshot_config',
2500 path => '{vmid}/snapshot/{snapname}/config',
2501 method => 'GET',
2502 proxyto => 'node',
2503 description => "Get snapshot configuration",
2504 permissions => {
2505 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2506 },
2507 parameters => {
2508 additionalProperties => 0,
2509 properties => {
2510 node => get_standard_option('pve-node'),
2511 vmid => get_standard_option('pve-vmid'),
2512 snapname => get_standard_option('pve-snapshot-name'),
2513 },
2514 },
2515 returns => { type => "object" },
2516 code => sub {
2517 my ($param) = @_;
2518
2519 my $rpcenv = PVE::RPCEnvironment::get();
2520
2521 my $authuser = $rpcenv->get_user();
2522
2523 my $vmid = extract_param($param, 'vmid');
2524
2525 my $snapname = extract_param($param, 'snapname');
2526
2527 my $conf = PVE::QemuServer::load_config($vmid);
2528
2529 my $snap = $conf->{snapshots}->{$snapname};
2530
2531 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2532
2533 return $snap;
2534 }});
2535
7e7d7b61
DM
2536__PACKAGE__->register_method({
2537 name => 'rollback',
154ccdcd 2538 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
2539 method => 'POST',
2540 protected => 1,
2541 proxyto => 'node',
2542 description => "Rollback VM state to specified snapshot.",
2543 permissions => {
f1baf1df 2544 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2545 },
2546 parameters => {
2547 additionalProperties => 0,
2548 properties => {
2549 node => get_standard_option('pve-node'),
2550 vmid => get_standard_option('pve-vmid'),
8abd398b 2551 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
2552 },
2553 },
2554 returns => {
2555 type => 'string',
2556 description => "the task ID.",
2557 },
2558 code => sub {
2559 my ($param) = @_;
2560
2561 my $rpcenv = PVE::RPCEnvironment::get();
2562
2563 my $authuser = $rpcenv->get_user();
2564
2565 my $node = extract_param($param, 'node');
2566
2567 my $vmid = extract_param($param, 'vmid');
2568
2569 my $snapname = extract_param($param, 'snapname');
2570
7e7d7b61 2571 my $realcmd = sub {
22c377f0
DM
2572 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2573 PVE::QemuServer::snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
2574 };
2575
2576 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2577 }});
2578
2579__PACKAGE__->register_method({
2580 name => 'delsnapshot',
2581 path => '{vmid}/snapshot/{snapname}',
2582 method => 'DELETE',
2583 protected => 1,
2584 proxyto => 'node',
2585 description => "Delete a VM snapshot.",
2586 permissions => {
f1baf1df 2587 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2588 },
2589 parameters => {
2590 additionalProperties => 0,
2591 properties => {
2592 node => get_standard_option('pve-node'),
2593 vmid => get_standard_option('pve-vmid'),
8abd398b 2594 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
2595 force => {
2596 optional => 1,
2597 type => 'boolean',
2598 description => "For removal from config file, even if removing disk snapshots fails.",
2599 },
7e7d7b61
DM
2600 },
2601 },
2602 returns => {
2603 type => 'string',
2604 description => "the task ID.",
2605 },
2606 code => sub {
2607 my ($param) = @_;
2608
2609 my $rpcenv = PVE::RPCEnvironment::get();
2610
2611 my $authuser = $rpcenv->get_user();
2612
2613 my $node = extract_param($param, 'node');
2614
2615 my $vmid = extract_param($param, 'vmid');
2616
2617 my $snapname = extract_param($param, 'snapname');
2618
7e7d7b61 2619 my $realcmd = sub {
22c377f0 2620 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
3ee28e38 2621 PVE::QemuServer::snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 2622 };
9dbd1ee4 2623
7b2257a8 2624 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
2625 }});
2626
04a69bb4
AD
2627__PACKAGE__->register_method({
2628 name => 'template',
2629 path => '{vmid}/template',
2630 method => 'POST',
2631 protected => 1,
2632 proxyto => 'node',
2633 description => "Create a Template.",
b02691d8 2634 permissions => {
bef4463b 2635 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
b02691d8
SP
2636 check => [ 'or',
2637 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2638 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2639 ],
2640 },
04a69bb4
AD
2641 parameters => {
2642 additionalProperties => 0,
2643 properties => {
2644 node => get_standard_option('pve-node'),
2645 vmid => get_standard_option('pve-vmid'),
2646 disk => {
2647 optional => 1,
2648 type => 'string',
2649 description => "If you want to convert only 1 disk to base image.",
2650 enum => [PVE::QemuServer::disknames()],
2651 },
2652
2653 },
2654 },
2655 returns => { type => 'null'},
2656 code => sub {
2657 my ($param) = @_;
2658
2659 my $rpcenv = PVE::RPCEnvironment::get();
2660
2661 my $authuser = $rpcenv->get_user();
2662
2663 my $node = extract_param($param, 'node');
2664
2665 my $vmid = extract_param($param, 'vmid');
2666
2667 my $disk = extract_param($param, 'disk');
2668
2669 my $updatefn = sub {
2670
2671 my $conf = PVE::QemuServer::load_config($vmid);
2672
2673 PVE::QemuServer::check_lock($conf);
2674
0402a80b 2675 die "unable to create template, because VM contains snapshots\n"
b91c2aae 2676 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b
DM
2677
2678 die "you can't convert a template to a template\n"
03c2d0ad 2679 if PVE::QemuServer::is_template($conf) && !$disk;
0402a80b 2680
35c5fdef 2681 die "you can't convert a VM to template if VM is running\n"
218cab9a 2682 if PVE::QemuServer::check_running($vmid);
35c5fdef 2683
04a69bb4
AD
2684 my $realcmd = sub {
2685 PVE::QemuServer::template_create($vmid, $conf, $disk);
2686 };
04a69bb4 2687
75e7e997 2688 $conf->{template} = 1;
04a69bb4 2689 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
75e7e997
DM
2690
2691 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
2692 };
2693
2694 PVE::QemuServer::lock_config($vmid, $updatefn);
2695 return undef;
2696 }});
2697
2698
2699
1e3baf05 27001;