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