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