]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
copy_vm: use exclusive lock for running VM
[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 },
1745
1746 },
1747 returns => {
1748 type => 'boolean'
1749 },
1750 code => sub {
1751 my ($param) = @_;
1752
1753 my $node = extract_param($param, 'node');
1754
1755 my $vmid = extract_param($param, 'vmid');
1756
1757 my $snapname = extract_param($param, 'snapname');
1758
1759 my $feature = extract_param($param, 'feature');
1760
1761 my $running = PVE::QemuServer::check_running($vmid);
1762
1763 my $conf = PVE::QemuServer::load_config($vmid);
1764
1765 if($snapname){
1766 my $snap = $conf->{snapshots}->{$snapname};
1767 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1768 $conf = $snap;
1769 }
1770 my $storecfg = PVE::Storage::config();
1771
1772 my $hasfeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
1773 my $res = $hasfeature ? 1 : 0 ;
1774 return $res;
1775 }});
1776
6116f729
DM
1777__PACKAGE__->register_method({
1778 name => 'copy_vm',
1779 path => '{vmid}/copy',
1780 method => 'POST',
1781 protected => 1,
1782 proxyto => 'node',
37329185 1783 description => "Create a copy of virtual machine/template.",
6116f729
DM
1784 permissions => {
1785 description => "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1786 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1787 "'Datastore.AllocateSpace' on any used storage.",
1788 check =>
1789 [ 'and',
1790 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1791 [ 'or',
1792 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1793 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1794 ],
1795 ]
1796 },
1797 parameters => {
1798 additionalProperties => 0,
1799 properties => {
1800 # fixme: add other parameters like name and description?
1801 node => get_standard_option('pve-node'),
1802 vmid => get_standard_option('pve-vmid'),
1803 newid => get_standard_option('pve-vmid', {
1804 description => 'VMID for the copy.' }),
1805 pool => {
1806 optional => 1,
1807 type => 'string', format => 'pve-poolid',
1808 description => "Add the new VM to the specified pool.",
1809 },
81f043eb
AD
1810 storage => get_standard_option('pve-storage-id', {
1811 description => "Target storage for full copy.",
4e4f83fe 1812 requires => 'full',
81f043eb
AD
1813 optional => 1,
1814 }),
6116f729
DM
1815 full => {
1816 optional => 1,
1817 type => 'boolean',
1818 description => "Create a full copy of all disk. This is always done when " .
1819 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1820 default => 0,
1821 },
1822 },
1823 },
1824 returns => {
1825 type => 'string',
1826 },
1827 code => sub {
1828 my ($param) = @_;
1829
1830 my $rpcenv = PVE::RPCEnvironment::get();
1831
1832 my $authuser = $rpcenv->get_user();
1833
1834 my $node = extract_param($param, 'node');
1835
1836 my $vmid = extract_param($param, 'vmid');
1837
1838 my $newid = extract_param($param, 'newid');
1839
1840 # fixme: update pool after create
1841 my $pool = extract_param($param, 'pool');
1842
1843 if (defined($pool)) {
1844 $rpcenv->check_pool_exist($pool);
1845 }
1846
81f043eb
AD
1847 my $storage = extract_param($param, 'storage');
1848
6116f729
DM
1849 my $storecfg = PVE::Storage::config();
1850
1851 PVE::Cluster::check_cfs_quorum();
1852
4e4f83fe
DM
1853 my $running = PVE::QemuServer::check_running($vmid) || 0;
1854
1855 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1856
1857 # exclusive lock if VM is running - else shared lock is enough;
1858 my $shared_lock = $running ? 0 : 1;
1859
6116f729
DM
1860 # fixme: do early checks - re-check after lock
1861
1862 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1863
1864 my $copyfn = sub {
1865
1866 # all tests after lock
1867 my $conf = PVE::QemuServer::load_config($vmid);
1868
1869 PVE::QemuServer::check_lock($conf);
1870
4e4f83fe 1871 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 1872
4e4f83fe 1873 die "unexpected state change\n" if $verify_running != $running;
6116f729 1874
81f043eb 1875 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf, $storage);
6116f729
DM
1876
1877 # fixme: snapshots??
1878
1879 my $conffile = PVE::QemuServer::config_file($newid);
1880
1881 die "unable to create VM $newid: config file already exists\n"
1882 if -f $conffile;
1883
1884 # create empty/temp config - this fails if VM already exists on other node
b83e0181 1885 PVE::Tools::file_set_contents($conffile, "# qmcopy temporary file\nlock: copy\n");
6116f729
DM
1886
1887 my $realcmd = sub {
1888 my $upid = shift;
1889
b83e0181 1890 my $newvollist = [];
6116f729 1891
b83e0181
DM
1892 eval {
1893 my $newconf = { lock => 'copy' };
6116f729
DM
1894 my $drives = {};
1895 my $vollist = [];
1896 foreach my $opt (keys %$conf) {
1897 my $value = $conf->{$opt};
1898
b83e0181
DM
1899 next if $opt eq 'snapshots'; # do not copy snapshot info
1900
6116f729
DM
1901 # always change MAC! address
1902 if ($opt =~ m/^net(\d+)$/) {
1903 my $net = PVE::QemuServer::parse_net($value);
1904 $net->{macaddr} = PVE::Tools::random_ether_addr();
1905 $newconf->{$opt} = PVE::QemuServer::print_net($net);
1906 } elsif (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
1907 if (PVE::QemuServer::drive_is_cdrom($drive)) {
1908 $newconf->{$opt} = $value; # simply copy configuration
1909 } else {
1910 $drives->{$opt} = $drive;
1911 push @$vollist, $drive->{file};
1912 }
1913 } else {
1914 # copy everything else
1915 $newconf->{$opt} = $value;
1916 }
1917 }
1918
1919 delete $newconf->{template};
1920
1921 PVE::Storage::activate_volumes($storecfg, $vollist);
1922
b83e0181
DM
1923 eval {
1924 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
6116f729 1925
b83e0181
DM
1926 foreach my $opt (keys %$drives) {
1927 my $drive = $drives->{$opt};
6116f729 1928
b83e0181
DM
1929 my $newvolid;
1930 if (!$param->{full} && PVE::Storage::volume_is_base($storecfg, $drive->{file})) {
1931 print "clone drive $opt ($drive->{file})\n";
1932 $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newid);
1933 } else {
1934 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
81f043eb 1935 $storeid = $storage if $storage;
b83e0181
DM
1936 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
1937 my $fmt = $drive->{format} || $defformat;
1938
1939 my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
1940
1941 print "copy drive $opt ($drive->{file})\n";
1942 $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1943
1944 PVE::QemuServer::qemu_img_convert($drive->{file}, $newvolid, $size);
1945 }
1946
1947 my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
1948 my $disk = { file => $newvolid, size => $size };
1949 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $disk);
1950 push @$newvollist, $newvolid;
6116f729 1951
b83e0181
DM
1952 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
1953 }
1954 };
1955 die $@ if $@;
1956
1957 delete $newconf->{lock};
1958 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
6116f729
DM
1959 };
1960 if (my $err = $@) {
6116f729
DM
1961 unlink $conffile;
1962
b83e0181
DM
1963 sleep 1; # some storage like rbd need to wait before release volume - really?
1964
1965 foreach my $volid (@$newvollist) {
1966 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
1967 warn $@ if $@;
1968 }
6116f729
DM
1969 die "copy failed: $err";
1970 }
1971
1972 return;
1973 };
1974
1975 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
1976 };
1977
4e4f83fe 1978 return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729
DM
1979 # Aquire exclusive lock lock for $newid
1980 return PVE::QemuServer::lock_config_full($newid, 1, $copyfn);
1981 });
1982
1983 }});
1984
3ea94c60 1985__PACKAGE__->register_method({
afdb31d5 1986 name => 'migrate_vm',
3ea94c60
DM
1987 path => '{vmid}/migrate',
1988 method => 'POST',
1989 protected => 1,
1990 proxyto => 'node',
1991 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
1992 permissions => {
1993 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1994 },
3ea94c60
DM
1995 parameters => {
1996 additionalProperties => 0,
1997 properties => {
1998 node => get_standard_option('pve-node'),
1999 vmid => get_standard_option('pve-vmid'),
2000 target => get_standard_option('pve-node', { description => "Target node." }),
2001 online => {
2002 type => 'boolean',
2003 description => "Use online/live migration.",
2004 optional => 1,
2005 },
2006 force => {
2007 type => 'boolean',
2008 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2009 optional => 1,
2010 },
2011 },
2012 },
afdb31d5 2013 returns => {
3ea94c60
DM
2014 type => 'string',
2015 description => "the task ID.",
2016 },
2017 code => sub {
2018 my ($param) = @_;
2019
2020 my $rpcenv = PVE::RPCEnvironment::get();
2021
a0d1b1a2 2022 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2023
2024 my $target = extract_param($param, 'target');
2025
2026 my $localnode = PVE::INotify::nodename();
2027 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2028
2029 PVE::Cluster::check_cfs_quorum();
2030
2031 PVE::Cluster::check_node_exists($target);
2032
2033 my $targetip = PVE::Cluster::remote_node_ip($target);
2034
2035 my $vmid = extract_param($param, 'vmid');
2036
afdb31d5 2037 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2038 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
2039
2040 # test if VM exists
a5ed42d3 2041 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
2042
2043 # try to detect errors early
a5ed42d3
DM
2044
2045 PVE::QemuServer::check_lock($conf);
2046
3ea94c60 2047 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2048 die "cant migrate running VM without --online\n"
3ea94c60
DM
2049 if !$param->{online};
2050 }
2051
47152e2e 2052 my $storecfg = PVE::Storage::config();
22d646a7 2053 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
47152e2e 2054
3be30d63 2055 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2056
88fc87b4
DM
2057 my $hacmd = sub {
2058 my $upid = shift;
3ea94c60 2059
88fc87b4
DM
2060 my $service = "pvevm:$vmid";
2061
2062 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2063
2064 print "Executing HA migrate for VM $vmid to node $target\n";
2065
2066 PVE::Tools::run_command($cmd);
2067
2068 return;
2069 };
2070
2071 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2072
2073 } else {
2074
2075 my $realcmd = sub {
2076 my $upid = shift;
2077
2078 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2079 };
2080
2081 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2082 }
3ea94c60 2083
3ea94c60 2084 }});
1e3baf05 2085
91c94f0a 2086__PACKAGE__->register_method({
afdb31d5
DM
2087 name => 'monitor',
2088 path => '{vmid}/monitor',
91c94f0a
DM
2089 method => 'POST',
2090 protected => 1,
2091 proxyto => 'node',
2092 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
2093 permissions => {
2094 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2095 },
91c94f0a
DM
2096 parameters => {
2097 additionalProperties => 0,
2098 properties => {
2099 node => get_standard_option('pve-node'),
2100 vmid => get_standard_option('pve-vmid'),
2101 command => {
2102 type => 'string',
2103 description => "The monitor command.",
2104 }
2105 },
2106 },
2107 returns => { type => 'string'},
2108 code => sub {
2109 my ($param) = @_;
2110
2111 my $vmid = $param->{vmid};
2112
2113 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
2114
2115 my $res = '';
2116 eval {
7b7c6d1b 2117 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2118 };
2119 $res = "ERROR: $@" if $@;
2120
2121 return $res;
2122 }});
2123
0d02881c
AD
2124__PACKAGE__->register_method({
2125 name => 'resize_vm',
614e3941 2126 path => '{vmid}/resize',
0d02881c
AD
2127 method => 'PUT',
2128 protected => 1,
2129 proxyto => 'node',
2f48a4f5 2130 description => "Extend volume size.",
0d02881c 2131 permissions => {
3b2773f6 2132 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
2133 },
2134 parameters => {
2135 additionalProperties => 0,
2f48a4f5
DM
2136 properties => {
2137 node => get_standard_option('pve-node'),
2138 vmid => get_standard_option('pve-vmid'),
2139 skiplock => get_standard_option('skiplock'),
2140 disk => {
2141 type => 'string',
2142 description => "The disk you want to resize.",
2143 enum => [PVE::QemuServer::disknames()],
2144 },
2145 size => {
2146 type => 'string',
f91b2e45 2147 pattern => '\+?\d+(\.\d+)?[KMGT]?',
2f48a4f5
DM
2148 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.",
2149 },
2150 digest => {
2151 type => 'string',
2152 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2153 maxLength => 40,
2154 optional => 1,
2155 },
2156 },
0d02881c
AD
2157 },
2158 returns => { type => 'null'},
2159 code => sub {
2160 my ($param) = @_;
2161
2162 my $rpcenv = PVE::RPCEnvironment::get();
2163
2164 my $authuser = $rpcenv->get_user();
2165
2166 my $node = extract_param($param, 'node');
2167
2168 my $vmid = extract_param($param, 'vmid');
2169
2170 my $digest = extract_param($param, 'digest');
2171
2f48a4f5
DM
2172 my $disk = extract_param($param, 'disk');
2173
2174 my $sizestr = extract_param($param, 'size');
0d02881c 2175
f91b2e45 2176 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
2177 raise_param_exc({ skiplock => "Only root may use this option." })
2178 if $skiplock && $authuser ne 'root@pam';
2179
0d02881c
AD
2180 my $storecfg = PVE::Storage::config();
2181
0d02881c
AD
2182 my $updatefn = sub {
2183
2184 my $conf = PVE::QemuServer::load_config($vmid);
2185
2186 die "checksum missmatch (file change by other user?)\n"
2187 if $digest && $digest ne $conf->{digest};
2188 PVE::QemuServer::check_lock($conf) if !$skiplock;
2189
f91b2e45
DM
2190 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2191
2192 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2193
2194 my $volid = $drive->{file};
2195
2196 die "disk '$disk' has no associated volume\n" if !$volid;
2197
2198 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2199
f2965e67
AD
2200 die "you can't online resize a virtio windows bootdisk\n"
2201 if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
2202
f91b2e45
DM
2203 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
2204
2205 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2206
2207 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
2208
2209 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2210 my ($ext, $newsize, $unit) = ($1, $2, $4);
2211 if ($unit) {
2212 if ($unit eq 'K') {
2213 $newsize = $newsize * 1024;
2214 } elsif ($unit eq 'M') {
2215 $newsize = $newsize * 1024 * 1024;
2216 } elsif ($unit eq 'G') {
2217 $newsize = $newsize * 1024 * 1024 * 1024;
2218 } elsif ($unit eq 'T') {
2219 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2220 }
2221 }
2222 $newsize += $size if $ext;
2223 $newsize = int($newsize);
2224
2225 die "unable to skrink disk size\n" if $newsize < $size;
2226
2227 return if $size == $newsize;
2228
2f48a4f5 2229 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 2230
f91b2e45
DM
2231 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2232
2233 $drive->{size} = $newsize;
2234 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
2235
2236 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2237 };
0d02881c
AD
2238
2239 PVE::QemuServer::lock_config($vmid, $updatefn);
2240 return undef;
2241 }});
2242
9dbd1ee4 2243__PACKAGE__->register_method({
7e7d7b61 2244 name => 'snapshot_list',
9dbd1ee4 2245 path => '{vmid}/snapshot',
7e7d7b61
DM
2246 method => 'GET',
2247 description => "List all snapshots.",
2248 permissions => {
2249 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2250 },
2251 proxyto => 'node',
2252 protected => 1, # qemu pid files are only readable by root
2253 parameters => {
2254 additionalProperties => 0,
2255 properties => {
2256 vmid => get_standard_option('pve-vmid'),
2257 node => get_standard_option('pve-node'),
2258 },
2259 },
2260 returns => {
2261 type => 'array',
2262 items => {
2263 type => "object",
2264 properties => {},
2265 },
2266 links => [ { rel => 'child', href => "{name}" } ],
2267 },
2268 code => sub {
2269 my ($param) = @_;
2270
6aa4651b
DM
2271 my $vmid = $param->{vmid};
2272
2273 my $conf = PVE::QemuServer::load_config($vmid);
7e7d7b61
DM
2274 my $snaphash = $conf->{snapshots} || {};
2275
2276 my $res = [];
2277
2278 foreach my $name (keys %$snaphash) {
0ea6bc69 2279 my $d = $snaphash->{$name};
982c7f12
DM
2280 my $item = {
2281 name => $name,
2282 snaptime => $d->{snaptime} || 0,
6aa4651b 2283 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
2284 description => $d->{description} || '',
2285 };
0ea6bc69 2286 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 2287 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
2288 push @$res, $item;
2289 }
2290
6aa4651b
DM
2291 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
2292 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
2293 $current->{parent} = $conf->{parent} if $conf->{parent};
2294
2295 push @$res, $current;
7e7d7b61
DM
2296
2297 return $res;
2298 }});
2299
2300__PACKAGE__->register_method({
2301 name => 'snapshot',
2302 path => '{vmid}/snapshot',
2303 method => 'POST',
9dbd1ee4
AD
2304 protected => 1,
2305 proxyto => 'node',
2306 description => "Snapshot a VM.",
2307 permissions => {
f1baf1df 2308 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
2309 },
2310 parameters => {
2311 additionalProperties => 0,
2312 properties => {
2313 node => get_standard_option('pve-node'),
2314 vmid => get_standard_option('pve-vmid'),
8abd398b 2315 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
2316 vmstate => {
2317 optional => 1,
2318 type => 'boolean',
2319 description => "Save the vmstate",
2320 },
2321 freezefs => {
2322 optional => 1,
2323 type => 'boolean',
2324 description => "Freeze the filesystem",
2325 },
782f4f75
DM
2326 description => {
2327 optional => 1,
2328 type => 'string',
2329 description => "A textual description or comment.",
2330 },
9dbd1ee4
AD
2331 },
2332 },
7e7d7b61
DM
2333 returns => {
2334 type => 'string',
2335 description => "the task ID.",
2336 },
9dbd1ee4
AD
2337 code => sub {
2338 my ($param) = @_;
2339
2340 my $rpcenv = PVE::RPCEnvironment::get();
2341
2342 my $authuser = $rpcenv->get_user();
2343
2344 my $node = extract_param($param, 'node');
2345
2346 my $vmid = extract_param($param, 'vmid');
2347
9dbd1ee4
AD
2348 my $snapname = extract_param($param, 'snapname');
2349
d1914468
DM
2350 die "unable to use snapshot name 'current' (reserved name)\n"
2351 if $snapname eq 'current';
2352
7e7d7b61 2353 my $realcmd = sub {
22c377f0 2354 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
782f4f75
DM
2355 PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
2356 $param->{freezefs}, $param->{description});
7e7d7b61
DM
2357 };
2358
2359 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2360 }});
2361
154ccdcd
DM
2362__PACKAGE__->register_method({
2363 name => 'snapshot_cmd_idx',
2364 path => '{vmid}/snapshot/{snapname}',
2365 description => '',
2366 method => 'GET',
2367 permissions => {
2368 user => 'all',
2369 },
2370 parameters => {
2371 additionalProperties => 0,
2372 properties => {
2373 vmid => get_standard_option('pve-vmid'),
2374 node => get_standard_option('pve-node'),
8abd398b 2375 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
2376 },
2377 },
2378 returns => {
2379 type => 'array',
2380 items => {
2381 type => "object",
2382 properties => {},
2383 },
2384 links => [ { rel => 'child', href => "{cmd}" } ],
2385 },
2386 code => sub {
2387 my ($param) = @_;
2388
2389 my $res = [];
2390
2391 push @$res, { cmd => 'rollback' };
d788cea6 2392 push @$res, { cmd => 'config' };
154ccdcd
DM
2393
2394 return $res;
2395 }});
2396
d788cea6
DM
2397__PACKAGE__->register_method({
2398 name => 'update_snapshot_config',
2399 path => '{vmid}/snapshot/{snapname}/config',
2400 method => 'PUT',
2401 protected => 1,
2402 proxyto => 'node',
2403 description => "Update snapshot metadata.",
2404 permissions => {
2405 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2406 },
2407 parameters => {
2408 additionalProperties => 0,
2409 properties => {
2410 node => get_standard_option('pve-node'),
2411 vmid => get_standard_option('pve-vmid'),
2412 snapname => get_standard_option('pve-snapshot-name'),
2413 description => {
2414 optional => 1,
2415 type => 'string',
2416 description => "A textual description or comment.",
2417 },
2418 },
2419 },
2420 returns => { type => 'null' },
2421 code => sub {
2422 my ($param) = @_;
2423
2424 my $rpcenv = PVE::RPCEnvironment::get();
2425
2426 my $authuser = $rpcenv->get_user();
2427
2428 my $vmid = extract_param($param, 'vmid');
2429
2430 my $snapname = extract_param($param, 'snapname');
2431
2432 return undef if !defined($param->{description});
2433
2434 my $updatefn = sub {
2435
2436 my $conf = PVE::QemuServer::load_config($vmid);
2437
2438 PVE::QemuServer::check_lock($conf);
2439
2440 my $snap = $conf->{snapshots}->{$snapname};
2441
2442 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2443
2444 $snap->{description} = $param->{description} if defined($param->{description});
2445
2446 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2447 };
2448
2449 PVE::QemuServer::lock_config($vmid, $updatefn);
2450
2451 return undef;
2452 }});
2453
2454__PACKAGE__->register_method({
2455 name => 'get_snapshot_config',
2456 path => '{vmid}/snapshot/{snapname}/config',
2457 method => 'GET',
2458 proxyto => 'node',
2459 description => "Get snapshot configuration",
2460 permissions => {
2461 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2462 },
2463 parameters => {
2464 additionalProperties => 0,
2465 properties => {
2466 node => get_standard_option('pve-node'),
2467 vmid => get_standard_option('pve-vmid'),
2468 snapname => get_standard_option('pve-snapshot-name'),
2469 },
2470 },
2471 returns => { type => "object" },
2472 code => sub {
2473 my ($param) = @_;
2474
2475 my $rpcenv = PVE::RPCEnvironment::get();
2476
2477 my $authuser = $rpcenv->get_user();
2478
2479 my $vmid = extract_param($param, 'vmid');
2480
2481 my $snapname = extract_param($param, 'snapname');
2482
2483 my $conf = PVE::QemuServer::load_config($vmid);
2484
2485 my $snap = $conf->{snapshots}->{$snapname};
2486
2487 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2488
2489 return $snap;
2490 }});
2491
7e7d7b61
DM
2492__PACKAGE__->register_method({
2493 name => 'rollback',
154ccdcd 2494 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
2495 method => 'POST',
2496 protected => 1,
2497 proxyto => 'node',
2498 description => "Rollback VM state to specified snapshot.",
2499 permissions => {
f1baf1df 2500 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2501 },
2502 parameters => {
2503 additionalProperties => 0,
2504 properties => {
2505 node => get_standard_option('pve-node'),
2506 vmid => get_standard_option('pve-vmid'),
8abd398b 2507 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
2508 },
2509 },
2510 returns => {
2511 type => 'string',
2512 description => "the task ID.",
2513 },
2514 code => sub {
2515 my ($param) = @_;
2516
2517 my $rpcenv = PVE::RPCEnvironment::get();
2518
2519 my $authuser = $rpcenv->get_user();
2520
2521 my $node = extract_param($param, 'node');
2522
2523 my $vmid = extract_param($param, 'vmid');
2524
2525 my $snapname = extract_param($param, 'snapname');
2526
7e7d7b61 2527 my $realcmd = sub {
22c377f0
DM
2528 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2529 PVE::QemuServer::snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
2530 };
2531
2532 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2533 }});
2534
2535__PACKAGE__->register_method({
2536 name => 'delsnapshot',
2537 path => '{vmid}/snapshot/{snapname}',
2538 method => 'DELETE',
2539 protected => 1,
2540 proxyto => 'node',
2541 description => "Delete a VM snapshot.",
2542 permissions => {
f1baf1df 2543 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2544 },
2545 parameters => {
2546 additionalProperties => 0,
2547 properties => {
2548 node => get_standard_option('pve-node'),
2549 vmid => get_standard_option('pve-vmid'),
8abd398b 2550 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
2551 force => {
2552 optional => 1,
2553 type => 'boolean',
2554 description => "For removal from config file, even if removing disk snapshots fails.",
2555 },
7e7d7b61
DM
2556 },
2557 },
2558 returns => {
2559 type => 'string',
2560 description => "the task ID.",
2561 },
2562 code => sub {
2563 my ($param) = @_;
2564
2565 my $rpcenv = PVE::RPCEnvironment::get();
2566
2567 my $authuser = $rpcenv->get_user();
2568
2569 my $node = extract_param($param, 'node');
2570
2571 my $vmid = extract_param($param, 'vmid');
2572
2573 my $snapname = extract_param($param, 'snapname');
2574
7e7d7b61 2575 my $realcmd = sub {
22c377f0 2576 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
3ee28e38 2577 PVE::QemuServer::snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 2578 };
9dbd1ee4 2579
7b2257a8 2580 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
2581 }});
2582
04a69bb4
AD
2583__PACKAGE__->register_method({
2584 name => 'template',
2585 path => '{vmid}/template',
2586 method => 'POST',
2587 protected => 1,
2588 proxyto => 'node',
2589 description => "Create a Template.",
b02691d8 2590 permissions => {
bef4463b 2591 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
b02691d8
SP
2592 check => [ 'or',
2593 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2594 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2595 ],
2596 },
04a69bb4
AD
2597 parameters => {
2598 additionalProperties => 0,
2599 properties => {
2600 node => get_standard_option('pve-node'),
2601 vmid => get_standard_option('pve-vmid'),
2602 disk => {
2603 optional => 1,
2604 type => 'string',
2605 description => "If you want to convert only 1 disk to base image.",
2606 enum => [PVE::QemuServer::disknames()],
2607 },
2608
2609 },
2610 },
2611 returns => { type => 'null'},
2612 code => sub {
2613 my ($param) = @_;
2614
2615 my $rpcenv = PVE::RPCEnvironment::get();
2616
2617 my $authuser = $rpcenv->get_user();
2618
2619 my $node = extract_param($param, 'node');
2620
2621 my $vmid = extract_param($param, 'vmid');
2622
2623 my $disk = extract_param($param, 'disk');
2624
2625 my $updatefn = sub {
2626
2627 my $conf = PVE::QemuServer::load_config($vmid);
2628
2629 PVE::QemuServer::check_lock($conf);
2630
0402a80b 2631 die "unable to create template, because VM contains snapshots\n"
b91c2aae 2632 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b
DM
2633
2634 die "you can't convert a template to a template\n"
03c2d0ad 2635 if PVE::QemuServer::is_template($conf) && !$disk;
0402a80b 2636
35c5fdef 2637 die "you can't convert a VM to template if VM is running\n"
218cab9a 2638 if PVE::QemuServer::check_running($vmid);
35c5fdef 2639
04a69bb4
AD
2640 my $realcmd = sub {
2641 PVE::QemuServer::template_create($vmid, $conf, $disk);
2642 };
04a69bb4 2643
75e7e997 2644 $conf->{template} = 1;
04a69bb4 2645 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
75e7e997
DM
2646
2647 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
2648 };
2649
2650 PVE::QemuServer::lock_config($vmid, $updatefn);
2651 return undef;
2652 }});
2653
2654
2655
1e3baf05 26561;