bump version to 6.2-15
[qemu-server.git] / PVE / API2 / Qemu.pm
1 package PVE::API2::Qemu;
2
3 use strict;
4 use warnings;
5 use Cwd 'abs_path';
6
7 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
8 use PVE::SafeSyslog;
9 use PVE::Tools qw(extract_param);
10 use PVE::Exception qw(raise raise_param_exc);
11 use PVE::Storage;
12 use PVE::JSONSchema qw(get_standard_option);
13 use PVE::RESTHandler;
14 use PVE::QemuServer;
15 use PVE::QemuMigrate;
16 use PVE::RPCEnvironment;
17 use PVE::AccessControl;
18 use PVE::INotify;
19
20 use Data::Dumper; # fixme: remove
21
22 use base qw(PVE::RESTHandler);
23
24 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
25
26 my $resolve_cdrom_alias = sub {
27     my $param = shift;
28
29     if (my $value = $param->{cdrom}) {
30         $value .= ",media=cdrom" if $value !~ m/media=/;
31         $param->{ide2} = $value;
32         delete $param->{cdrom};
33     }
34 };
35
36
37 my $check_storage_access = sub {
38    my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
39
40    PVE::QemuServer::foreach_drive($settings, sub {
41         my ($ds, $drive) = @_;
42
43         my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
44
45         my $volid = $drive->{file};
46
47         if (!$volid || $volid eq 'none') {
48             # nothing to check
49         } elsif ($isCDROM && ($volid eq 'cdrom')) {
50             $rpcenv->check($authuser, "/", ['Sys.Console']);
51         } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
52             my ($storeid, $size) = ($2 || $default_storage, $3);
53             die "no storage ID specified (and no default storage)\n" if !$storeid;
54             $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
55         } else {
56             $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
57         }
58     });
59 };
60
61 # Note: $pool is only needed when creating a VM, because pool permissions
62 # are automatically inherited if VM already exists inside a pool.
63 my $create_disks = sub {
64     my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
65
66     my $vollist = [];
67
68     my $res = {};
69     PVE::QemuServer::foreach_drive($settings, sub {
70         my ($ds, $disk) = @_;
71
72         my $volid = $disk->{file};
73
74         if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
75             $res->{$ds} = $settings->{$ds};
76         } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
77             my ($storeid, $size) = ($2 || $default_storage, $3);
78             die "no storage ID specified (and no default storage)\n" if !$storeid;
79             my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
80             my $fmt = $disk->{format} || $defformat;
81             my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
82                                                   $fmt, undef, $size*1024*1024);
83             $disk->{file} = $volid;
84             push @$vollist, $volid;
85             delete $disk->{format}; # no longer needed
86             $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
87         } else {
88             my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
89             PVE::Storage::activate_volumes($storecfg, [ $volid ]);
90             die "image '$path' does not exists\n" if (!(-f $path || -b $path));
91             $res->{$ds} = $settings->{$ds};
92         }
93     });
94
95     # free allocated images on error
96     if (my $err = $@) {
97         syslog('err', "VM $vmid creating disks failed");
98         foreach my $volid (@$vollist) {
99             eval { PVE::Storage::vdisk_free($storecfg, $volid); };
100             warn $@ if $@;
101         }
102         die $err;
103     }
104
105     # modify vm config if everything went well
106     foreach my $ds (keys %$res) {
107         $conf->{$ds} = $res->{$ds};
108     }
109
110     return $vollist;
111 };
112
113 my $check_vm_modify_config_perm = sub {
114     my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
115
116     return 1 if $authuser eq 'root@pam';
117
118     foreach my $opt (@$key_list) {
119         # disk checks need to be done somewhere else
120         next if PVE::QemuServer::valid_drivename($opt);
121
122         if ($opt eq 'sockets' || $opt eq 'cores' ||
123             $opt eq 'cpu' || $opt eq 'smp' || 
124             $opt eq 'cpulimit' || $opt eq 'cpuunits') {
125             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
126         } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
127             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
128         } elsif ($opt eq 'memory' || $opt eq 'balloon') {
129             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
130         } elsif ($opt eq 'args' || $opt eq 'lock') {
131             die "only root can set '$opt' config\n";
132         } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || 
133                  $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
134             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
135         } elsif ($opt =~ m/^net\d+$/) {
136             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
137         } else {
138             $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
139         }
140     }
141
142     return 1;
143 };
144
145 __PACKAGE__->register_method({
146     name => 'vmlist',
147     path => '',
148     method => 'GET',
149     description => "Virtual machine index (per node).",
150     permissions => {
151         description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
152         user => 'all',
153     },
154     proxyto => 'node',
155     protected => 1, # qemu pid files are only readable by root
156     parameters => {
157         additionalProperties => 0,
158         properties => {
159             node => get_standard_option('pve-node'),
160         },
161     },
162     returns => {
163         type => 'array',
164         items => {
165             type => "object",
166             properties => {},
167         },
168         links => [ { rel => 'child', href => "{vmid}" } ],
169     },
170     code => sub {
171         my ($param) = @_;
172
173         my $rpcenv = PVE::RPCEnvironment::get();
174         my $authuser = $rpcenv->get_user();
175
176         my $vmstatus = PVE::QemuServer::vmstatus();
177
178         my $res = [];
179         foreach my $vmid (keys %$vmstatus) {
180             next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
181
182             my $data = $vmstatus->{$vmid};
183             $data->{vmid} = $vmid;
184             push @$res, $data;
185         }
186
187         return $res;
188     }});
189
190 __PACKAGE__->register_method({
191     name => 'create_vm',
192     path => '',
193     method => 'POST',
194     description => "Create or restore a virtual machine.",
195     permissions => {
196         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.",
197         check => [ 'or', 
198                    [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
199                    [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
200             ],
201     },
202     protected => 1,
203     proxyto => 'node',
204     parameters => {
205         additionalProperties => 0,
206         properties => PVE::QemuServer::json_config_properties(
207             {
208                 node => get_standard_option('pve-node'),
209                 vmid => get_standard_option('pve-vmid'),
210                 archive => {
211                     description => "The backup file.",
212                     type => 'string',
213                     optional => 1,
214                     maxLength => 255,
215                 },
216                 storage => get_standard_option('pve-storage-id', {
217                     description => "Default storage.",
218                     optional => 1,
219                 }),
220                 force => {
221                     optional => 1,
222                     type => 'boolean',
223                     description => "Allow to overwrite existing VM.",
224                     requires => 'archive',
225                 },
226                 unique => {
227                     optional => 1,
228                     type => 'boolean',
229                     description => "Assign a unique random ethernet address.",
230                     requires => 'archive',
231                 },
232                 pool => { 
233                     optional => 1,
234                     type => 'string', format => 'pve-poolid',
235                     description => "Add the VM to the specified pool.",
236                 },
237             }),
238     },
239     returns => {
240         type => 'string',
241     },
242     code => sub {
243         my ($param) = @_;
244
245         my $rpcenv = PVE::RPCEnvironment::get();
246
247         my $authuser = $rpcenv->get_user();
248
249         my $node = extract_param($param, 'node');
250
251         my $vmid = extract_param($param, 'vmid');
252
253         my $archive = extract_param($param, 'archive');
254
255         my $storage = extract_param($param, 'storage');
256
257         my $force = extract_param($param, 'force');
258
259         my $unique = extract_param($param, 'unique');
260         
261         my $pool = extract_param($param, 'pool');
262
263         my $filename = PVE::QemuServer::config_file($vmid);
264
265         my $storecfg = PVE::Storage::config();
266
267         PVE::Cluster::check_cfs_quorum();
268
269         if (defined($pool)) {
270             $rpcenv->check_pool_exist($pool);
271             $rpcenv->check_perm_modify($authuser, "/pool/$pool");
272         } 
273
274         $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
275             if defined($storage);
276
277         if (!$archive) {
278             &$resolve_cdrom_alias($param);
279
280             &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
281
282             &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
283
284             foreach my $opt (keys %$param) {
285                 if (PVE::QemuServer::valid_drivename($opt)) {
286                     my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
287                     raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
288
289                     PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
290                     $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
291                 }
292             }
293
294             PVE::QemuServer::add_random_macs($param);
295         } else {
296             my $keystr = join(' ', keys %$param);
297             raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
298
299             if ($archive eq '-') {
300                 die "pipe requires cli environment\n"
301                     && $rpcenv->{type} ne 'cli';
302             } else {
303                 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
304                 PVE::Storage::activate_volumes($storecfg, [ $archive ]);
305                 die "can't find archive file '$archive'\n" if !($path && -f $path);
306                 $archive = $path;
307             }
308         }
309
310         my $addVMtoPoolFn = sub {                      
311             my $usercfg = cfs_read_file("user.cfg");
312             if (my $data = $usercfg->{pools}->{$pool}) {
313                 $data->{vms}->{$vmid} = 1;
314                 $usercfg->{vms}->{$vmid} = $pool;
315                 cfs_write_file("user.cfg", $usercfg);
316             }
317         };
318
319         my $restorefn = sub {
320
321             if (-f $filename) {
322                 die "unable to restore vm $vmid: config file already exists\n"
323                     if !$force;
324
325                 die "unable to restore vm $vmid: vm is running\n"
326                     if PVE::QemuServer::check_running($vmid);
327
328                 # destroy existing data - keep empty config
329                 PVE::QemuServer::destroy_vm($storecfg, $vmid, 1);
330             }
331
332             my $realcmd = sub {
333                 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
334                     storage => $storage,
335                     pool => $pool,
336                     unique => $unique });
337
338                 PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
339             };
340
341             return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
342         };
343
344         my $createfn = sub {
345
346             # test after locking
347             die "unable to create vm $vmid: config file already exists\n"
348                 if -f $filename;
349
350             my $realcmd = sub {
351
352                 my $vollist = [];
353
354                 my $conf = $param;
355
356                 eval {
357
358                     $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
359
360                     # try to be smart about bootdisk
361                     my @disks = PVE::QemuServer::disknames();
362                     my $firstdisk;
363                     foreach my $ds (reverse @disks) {
364                         next if !$conf->{$ds};
365                         my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
366                         next if PVE::QemuServer::drive_is_cdrom($disk);
367                         $firstdisk = $ds;
368                     }
369
370                     if (!$conf->{bootdisk} && $firstdisk) {
371                         $conf->{bootdisk} = $firstdisk;
372                     }
373
374                     PVE::QemuServer::update_config_nolock($vmid, $conf);
375
376                 };
377                 my $err = $@;
378
379                 if ($err) {
380                     foreach my $volid (@$vollist) {
381                         eval { PVE::Storage::vdisk_free($storecfg, $volid); };
382                         warn $@ if $@;
383                     }
384                     die "create failed - $err";
385                 }
386
387                 PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
388             };
389
390             return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
391         };
392
393         return PVE::QemuServer::lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
394     }});
395
396 __PACKAGE__->register_method({
397     name => 'vmdiridx',
398     path => '{vmid}',
399     method => 'GET',
400     proxyto => 'node',
401     description => "Directory index",
402     permissions => {
403         user => 'all',
404     },
405     parameters => {
406         additionalProperties => 0,
407         properties => {
408             node => get_standard_option('pve-node'),
409             vmid => get_standard_option('pve-vmid'),
410         },
411     },
412     returns => {
413         type => 'array',
414         items => {
415             type => "object",
416             properties => {
417                 subdir => { type => 'string' },
418             },
419         },
420         links => [ { rel => 'child', href => "{subdir}" } ],
421     },
422     code => sub {
423         my ($param) = @_;
424
425         my $res = [
426             { subdir => 'config' },
427             { subdir => 'status' },
428             { subdir => 'unlink' },
429             { subdir => 'vncproxy' },
430             { subdir => 'migrate' },
431             { subdir => 'rrd' },
432             { subdir => 'rrddata' },
433             { subdir => 'monitor' },
434             ];
435
436         return $res;
437     }});
438
439 __PACKAGE__->register_method({
440     name => 'rrd',
441     path => '{vmid}/rrd',
442     method => 'GET',
443     protected => 1, # fixme: can we avoid that?
444     permissions => {
445         check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
446     },
447     description => "Read VM RRD statistics (returns PNG)",
448     parameters => {
449         additionalProperties => 0,
450         properties => {
451             node => get_standard_option('pve-node'),
452             vmid => get_standard_option('pve-vmid'),
453             timeframe => {
454                 description => "Specify the time frame you are interested in.",
455                 type => 'string',
456                 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
457             },
458             ds => {
459                 description => "The list of datasources you want to display.",
460                 type => 'string', format => 'pve-configid-list',
461             },
462             cf => {
463                 description => "The RRD consolidation function",
464                 type => 'string',
465                 enum => [ 'AVERAGE', 'MAX' ],
466                 optional => 1,
467             },
468         },
469     },
470     returns => {
471         type => "object",
472         properties => {
473             filename => { type => 'string' },
474         },
475     },
476     code => sub {
477         my ($param) = @_;
478
479         return PVE::Cluster::create_rrd_graph(
480             "pve2-vm/$param->{vmid}", $param->{timeframe},
481             $param->{ds}, $param->{cf});
482
483     }});
484
485 __PACKAGE__->register_method({
486     name => 'rrddata',
487     path => '{vmid}/rrddata',
488     method => 'GET',
489     protected => 1, # fixme: can we avoid that?
490     permissions => {
491         check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
492     },
493     description => "Read VM RRD statistics",
494     parameters => {
495         additionalProperties => 0,
496         properties => {
497             node => get_standard_option('pve-node'),
498             vmid => get_standard_option('pve-vmid'),
499             timeframe => {
500                 description => "Specify the time frame you are interested in.",
501                 type => 'string',
502                 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
503             },
504             cf => {
505                 description => "The RRD consolidation function",
506                 type => 'string',
507                 enum => [ 'AVERAGE', 'MAX' ],
508                 optional => 1,
509             },
510         },
511     },
512     returns => {
513         type => "array",
514         items => {
515             type => "object",
516             properties => {},
517         },
518     },
519     code => sub {
520         my ($param) = @_;
521
522         return PVE::Cluster::create_rrd_data(
523             "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
524     }});
525
526
527 __PACKAGE__->register_method({
528     name => 'vm_config',
529     path => '{vmid}/config',
530     method => 'GET',
531     proxyto => 'node',
532     description => "Get virtual machine configuration.",
533     permissions => {
534         check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
535     },
536     parameters => {
537         additionalProperties => 0,
538         properties => {
539             node => get_standard_option('pve-node'),
540             vmid => get_standard_option('pve-vmid'),
541         },
542     },
543     returns => {
544         type => "object",
545         properties => {
546             digest => {
547                 type => 'string',
548                 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
549             }
550         },
551     },
552     code => sub {
553         my ($param) = @_;
554
555         my $conf = PVE::QemuServer::load_config($param->{vmid});
556
557         return $conf;
558     }});
559
560 my $vm_is_volid_owner = sub {
561     my ($storecfg, $vmid, $volid) =@_;
562
563     if ($volid !~  m|^/|) {
564         my ($path, $owner);
565         eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
566         if ($owner && ($owner == $vmid)) {
567             return 1;
568         }
569     }
570
571     return undef;
572 };
573
574 my $test_deallocate_drive = sub {
575     my ($storecfg, $vmid, $key, $drive, $force) = @_;
576
577     if (!PVE::QemuServer::drive_is_cdrom($drive)) {
578         my $volid = $drive->{file};
579         if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
580             if ($force || $key =~ m/^unused/) {
581                 my $sid = PVE::Storage::parse_volume_id($volid);
582                 return $sid;
583             }
584         }
585     }
586
587     return undef;
588 };
589
590 my $delete_drive = sub {
591     my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
592
593     if (!PVE::QemuServer::drive_is_cdrom($drive)) {
594         my $volid = $drive->{file};
595         if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
596             if ($force || $key =~ m/^unused/) {
597                 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
598                 warn $@ if $@;
599             } else {
600                 PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
601             }
602         }
603     }
604
605     delete $conf->{$key};
606 };
607
608 my $vmconfig_delete_option = sub {
609     my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
610
611     return if !defined($conf->{$opt});
612
613     my $isDisk = PVE::QemuServer::valid_drivename($opt)|| ($opt =~ m/^unused/);
614
615     if ($isDisk) {
616         $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
617
618         my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
619         if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {  
620             $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
621         }
622     }
623                 
624     die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
625
626     if ($isDisk) {
627         my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
628         &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
629     } else {
630         delete $conf->{$opt};
631     }
632
633     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
634 };
635
636 my $vmconfig_update_disk = sub {
637     my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
638
639     my $drive = PVE::QemuServer::parse_drive($opt, $value);
640
641     if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
642         $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
643     } else {
644         $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
645     }
646
647     if ($conf->{$opt}) {
648
649         if (my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}))  {
650
651             my $media = $drive->{media} || 'disk';
652             my $oldmedia = $old_drive->{media} || 'disk';
653             die "unable to change media type\n" if $media ne $oldmedia;
654
655             if (!PVE::QemuServer::drive_is_cdrom($old_drive) &&
656                 ($drive->{file} ne $old_drive->{file})) {  # delete old disks
657
658                 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
659                 $conf = PVE::QemuServer::load_config($vmid); # update/reload
660             }
661         }
662     }
663
664     &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
665     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
666
667     $conf = PVE::QemuServer::load_config($vmid); # update/reload
668     $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
669
670     if (PVE::QemuServer::drive_is_cdrom($drive)) { # cdrom
671
672         if (PVE::QemuServer::check_running($vmid)) {
673             if ($drive->{file} eq 'none') {
674                 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0);
675             } else {
676                 my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
677                 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0); #force eject if locked
678                 PVE::QemuServer::vm_monitor_command($vmid, "change drive-$opt \"$path\"", 0) if $path;
679             }
680         }
681
682     } else { # hotplug new disks
683
684         die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
685     }
686 };
687
688 my $vmconfig_update_net = sub {
689     my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
690
691     if ($conf->{$opt}) {
692         #if online update, then unplug first
693         die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
694     }
695
696     $conf->{$opt} = $value;
697     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
698     $conf = PVE::QemuServer::load_config($vmid); # update/reload
699
700     my $net = PVE::QemuServer::parse_net($conf->{$opt});
701
702     die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net);
703 };
704
705 my $vm_config_perm_list = [
706             'VM.Config.Disk', 
707             'VM.Config.CDROM', 
708             'VM.Config.CPU', 
709             'VM.Config.Memory', 
710             'VM.Config.Network', 
711             'VM.Config.HWType',
712             'VM.Config.Options',
713     ];
714
715 __PACKAGE__->register_method({
716     name => 'update_vm',
717     path => '{vmid}/config',
718     method => 'PUT',
719     protected => 1,
720     proxyto => 'node',
721     description => "Set virtual machine options.",
722     permissions => {
723         check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
724     },
725     parameters => {
726         additionalProperties => 0,
727         properties => PVE::QemuServer::json_config_properties(
728             {
729                 node => get_standard_option('pve-node'),
730                 vmid => get_standard_option('pve-vmid'),
731                 skiplock => get_standard_option('skiplock'),
732                 delete => {
733                     type => 'string', format => 'pve-configid-list',
734                     description => "A list of settings you want to delete.",
735                     optional => 1,
736                 },
737                 force => {
738                     type => 'boolean',
739                     description => $opt_force_description,
740                     optional => 1,
741                     requires => 'delete',
742                 },
743                 digest => {
744                     type => 'string',
745                     description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
746                     maxLength => 40,
747                     optional => 1,
748                 }
749             }),
750     },
751     returns => { type => 'null'},
752     code => sub {
753         my ($param) = @_;
754
755         my $rpcenv = PVE::RPCEnvironment::get();
756
757         my $authuser = $rpcenv->get_user();
758
759         my $node = extract_param($param, 'node');
760
761         my $vmid = extract_param($param, 'vmid');
762
763         my $digest = extract_param($param, 'digest');
764
765         my @paramarr = (); # used for log message
766         foreach my $key (keys %$param) {
767             push @paramarr, "-$key", $param->{$key};
768         }
769
770         my $skiplock = extract_param($param, 'skiplock');
771         raise_param_exc({ skiplock => "Only root may use this option." })
772             if $skiplock && $authuser ne 'root@pam';
773
774         my $delete_str = extract_param($param, 'delete');
775
776         my $force = extract_param($param, 'force');
777
778         die "no options specified\n" if !$delete_str && !scalar(keys %$param);
779
780         my $storecfg = PVE::Storage::config();
781
782         &$resolve_cdrom_alias($param);
783
784         # now try to verify all parameters
785
786         my @delete = ();
787         foreach my $opt (PVE::Tools::split_list($delete_str)) {
788             $opt = 'ide2' if $opt eq 'cdrom';
789             raise_param_exc({ delete => "you can't use '-$opt' and " .
790                                   "-delete $opt' at the same time" })
791                 if defined($param->{$opt});
792
793             if (!PVE::QemuServer::option_exists($opt)) {
794                 raise_param_exc({ delete => "unknown option '$opt'" });
795             }
796
797             push @delete, $opt;
798         }
799
800         foreach my $opt (keys %$param) {
801             if (PVE::QemuServer::valid_drivename($opt)) {
802                 # cleanup drive path
803                 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
804                 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
805                 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
806             } elsif ($opt =~ m/^net(\d+)$/) { 
807                 # add macaddr
808                 my $net = PVE::QemuServer::parse_net($param->{$opt});
809                 $param->{$opt} = PVE::QemuServer::print_net($net);
810             }
811         }
812
813         &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
814
815         &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
816
817         &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
818
819         my $updatefn =  sub {
820
821             my $conf = PVE::QemuServer::load_config($vmid);
822
823             die "checksum missmatch (file change by other user?)\n"
824                 if $digest && $digest ne $conf->{digest};
825
826             PVE::QemuServer::check_lock($conf) if !$skiplock;
827
828             PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
829
830             foreach my $opt (@delete) { # delete
831                 $conf = PVE::QemuServer::load_config($vmid); # update/reload
832                 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
833             }
834
835             foreach my $opt (keys %$param) { # add/change
836
837                 $conf = PVE::QemuServer::load_config($vmid); # update/reload
838
839                 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
840
841                 if (PVE::QemuServer::valid_drivename($opt)) {
842
843                     &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid, 
844                                            $opt, $param->{$opt}, $force);
845         
846                 } elsif ($opt =~ m/^net(\d+)$/) { #nics
847
848                     &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid, 
849                                           $opt, $param->{$opt});
850
851                 } else {
852
853                     $conf->{$opt} = $param->{$opt};
854                     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
855                 }
856             }
857         };
858
859         PVE::QemuServer::lock_config($vmid, $updatefn);
860
861         return undef;
862     }});
863
864
865 __PACKAGE__->register_method({
866     name => 'destroy_vm',
867     path => '{vmid}',
868     method => 'DELETE',
869     protected => 1,
870     proxyto => 'node',
871     description => "Destroy the vm (also delete all used/owned volumes).",
872     permissions => {
873         check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
874     },
875     parameters => {
876         additionalProperties => 0,
877         properties => {
878             node => get_standard_option('pve-node'),
879             vmid => get_standard_option('pve-vmid'),
880             skiplock => get_standard_option('skiplock'),
881         },
882     },
883     returns => {
884         type => 'string',
885     },
886     code => sub {
887         my ($param) = @_;
888
889         my $rpcenv = PVE::RPCEnvironment::get();
890
891         my $authuser = $rpcenv->get_user();
892
893         my $vmid = $param->{vmid};
894
895         my $skiplock = $param->{skiplock};
896         raise_param_exc({ skiplock => "Only root may use this option." })
897             if $skiplock && $authuser ne 'root@pam';
898
899         # test if VM exists
900         my $conf = PVE::QemuServer::load_config($vmid);
901
902         my $storecfg = PVE::Storage::config();
903
904         my $delVMfromPoolFn = sub {                    
905             my $usercfg = cfs_read_file("user.cfg");
906             my $pool = $usercfg->{vms}->{$vmid};
907             if (my $data = $usercfg->{pools}->{$pool}) {
908                 delete $data->{vms}->{$vmid};
909                 delete $usercfg->{vms}->{$vmid};
910                 cfs_write_file("user.cfg", $usercfg);
911             }
912         };
913
914         my $realcmd = sub {
915             my $upid = shift;
916
917             syslog('info', "destroy VM $vmid: $upid\n");
918
919             PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
920
921             PVE::AccessControl::lock_user_config($delVMfromPoolFn, "pool cleanup failed");
922         };
923
924         return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
925     }});
926
927 __PACKAGE__->register_method({
928     name => 'unlink',
929     path => '{vmid}/unlink',
930     method => 'PUT',
931     protected => 1,
932     proxyto => 'node',
933     description => "Unlink/delete disk images.",
934     permissions => {
935         check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
936     },
937     parameters => {
938         additionalProperties => 0,
939         properties => {
940             node => get_standard_option('pve-node'),
941             vmid => get_standard_option('pve-vmid'),
942             idlist => {
943                 type => 'string', format => 'pve-configid-list',
944                 description => "A list of disk IDs you want to delete.",
945             },
946             force => {
947                 type => 'boolean',
948                 description => $opt_force_description,
949                 optional => 1,
950             },
951         },
952     },
953     returns => { type => 'null'},
954     code => sub {
955         my ($param) = @_;
956
957         $param->{delete} = extract_param($param, 'idlist');
958
959         __PACKAGE__->update_vm($param);
960
961         return undef;
962     }});
963
964 my $sslcert;
965
966 __PACKAGE__->register_method({
967     name => 'vncproxy',
968     path => '{vmid}/vncproxy',
969     method => 'POST',
970     protected => 1,
971     permissions => {
972         check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
973     },
974     description => "Creates a TCP VNC proxy connections.",
975     parameters => {
976         additionalProperties => 0,
977         properties => {
978             node => get_standard_option('pve-node'),
979             vmid => get_standard_option('pve-vmid'),
980         },
981     },
982     returns => {
983         additionalProperties => 0,
984         properties => {
985             user => { type => 'string' },
986             ticket => { type => 'string' },
987             cert => { type => 'string' },
988             port => { type => 'integer' },
989             upid => { type => 'string' },
990         },
991     },
992     code => sub {
993         my ($param) = @_;
994
995         my $rpcenv = PVE::RPCEnvironment::get();
996
997         my $authuser = $rpcenv->get_user();
998
999         my $vmid = $param->{vmid};
1000         my $node = $param->{node};
1001
1002         my $authpath = "/vms/$vmid";
1003
1004         my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
1005
1006         $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1007             if !$sslcert;
1008
1009         my $port = PVE::Tools::next_vnc_port();
1010
1011         my $remip;
1012
1013         if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1014             $remip = PVE::Cluster::remote_node_ip($node);
1015         }
1016
1017         # NOTE: kvm VNC traffic is already TLS encrypted,
1018         # so we select the fastest chipher here (or 'none'?)
1019         my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1020                                '-c', 'blowfish-cbc', $remip] : [];
1021
1022         my $timeout = 10;
1023
1024         my $realcmd = sub {
1025             my $upid = shift;
1026
1027             syslog('info', "starting vnc proxy $upid\n");
1028
1029             my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1030
1031             my $qmstr = join(' ', @$qmcmd);
1032
1033             # also redirect stderr (else we get RFB protocol errors)
1034             my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1035
1036             PVE::Tools::run_command($cmd);
1037
1038             return;
1039         };
1040
1041         my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1042
1043         return {
1044             user => $authuser,
1045             ticket => $ticket,
1046             port => $port,
1047             upid => $upid,
1048             cert => $sslcert,
1049         };
1050     }});
1051
1052 __PACKAGE__->register_method({
1053     name => 'vmcmdidx',
1054     path => '{vmid}/status',
1055     method => 'GET',
1056     proxyto => 'node',
1057     description => "Directory index",
1058     permissions => {
1059         user => 'all',
1060     },
1061     parameters => {
1062         additionalProperties => 0,
1063         properties => {
1064             node => get_standard_option('pve-node'),
1065             vmid => get_standard_option('pve-vmid'),
1066         },
1067     },
1068     returns => {
1069         type => 'array',
1070         items => {
1071             type => "object",
1072             properties => {
1073                 subdir => { type => 'string' },
1074             },
1075         },
1076         links => [ { rel => 'child', href => "{subdir}" } ],
1077     },
1078     code => sub {
1079         my ($param) = @_;
1080
1081         # test if VM exists
1082         my $conf = PVE::QemuServer::load_config($param->{vmid});
1083
1084         my $res = [
1085             { subdir => 'current' },
1086             { subdir => 'start' },
1087             { subdir => 'stop' },
1088             ];
1089
1090         return $res;
1091     }});
1092
1093 my $vm_is_ha_managed = sub {
1094     my ($vmid) = @_;
1095
1096     my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1097     if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
1098         return 1;
1099     } 
1100     return 0;
1101 };
1102
1103 __PACKAGE__->register_method({
1104     name => 'vm_status',
1105     path => '{vmid}/status/current',
1106     method => 'GET',
1107     proxyto => 'node',
1108     protected => 1, # qemu pid files are only readable by root
1109     description => "Get virtual machine status.",
1110     permissions => {
1111         check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1112     },
1113     parameters => {
1114         additionalProperties => 0,
1115         properties => {
1116             node => get_standard_option('pve-node'),
1117             vmid => get_standard_option('pve-vmid'),
1118         },
1119     },
1120     returns => { type => 'object' },
1121     code => sub {
1122         my ($param) = @_;
1123
1124         # test if VM exists
1125         my $conf = PVE::QemuServer::load_config($param->{vmid});
1126
1127         my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
1128         my $status = $vmstatus->{$param->{vmid}};
1129
1130         $status->{ha} = &$vm_is_ha_managed($param->{vmid});
1131
1132         return $status;
1133     }});
1134
1135 __PACKAGE__->register_method({
1136     name => 'vm_start',
1137     path => '{vmid}/status/start',
1138     method => 'POST',
1139     protected => 1,
1140     proxyto => 'node',
1141     description => "Start virtual machine.",
1142     permissions => {
1143         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1144     },
1145     parameters => {
1146         additionalProperties => 0,
1147         properties => {
1148             node => get_standard_option('pve-node'),
1149             vmid => get_standard_option('pve-vmid'),
1150             skiplock => get_standard_option('skiplock'),
1151             stateuri => get_standard_option('pve-qm-stateuri'),
1152         },
1153     },
1154     returns => {
1155         type => 'string',
1156     },
1157     code => sub {
1158         my ($param) = @_;
1159
1160         my $rpcenv = PVE::RPCEnvironment::get();
1161
1162         my $authuser = $rpcenv->get_user();
1163
1164         my $node = extract_param($param, 'node');
1165
1166         my $vmid = extract_param($param, 'vmid');
1167
1168         my $stateuri = extract_param($param, 'stateuri');
1169         raise_param_exc({ stateuri => "Only root may use this option." })
1170             if $stateuri && $authuser ne 'root@pam';
1171
1172         my $skiplock = extract_param($param, 'skiplock');
1173         raise_param_exc({ skiplock => "Only root may use this option." })
1174             if $skiplock && $authuser ne 'root@pam';
1175
1176         my $storecfg = PVE::Storage::config();
1177
1178         if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1179             $rpcenv->{type} ne 'ha') {
1180
1181             my $hacmd = sub {
1182                 my $upid = shift;
1183
1184                 my $service = "pvevm:$vmid";
1185
1186                 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1187
1188                 print "Executing HA start for VM $vmid\n";
1189
1190                 PVE::Tools::run_command($cmd);
1191
1192                 return;
1193             };
1194
1195             return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1196
1197         } else {
1198
1199             my $realcmd = sub {
1200                 my $upid = shift;
1201
1202                 syslog('info', "start VM $vmid: $upid\n");
1203
1204                 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
1205
1206                 return;
1207             };
1208
1209             return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1210         }
1211     }});
1212
1213 __PACKAGE__->register_method({
1214     name => 'vm_stop',
1215     path => '{vmid}/status/stop',
1216     method => 'POST',
1217     protected => 1,
1218     proxyto => 'node',
1219     description => "Stop virtual machine.",
1220     permissions => {
1221         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1222     },
1223     parameters => {
1224         additionalProperties => 0,
1225         properties => {
1226             node => get_standard_option('pve-node'),
1227             vmid => get_standard_option('pve-vmid'),
1228             skiplock => get_standard_option('skiplock'),
1229             timeout => {
1230                 description => "Wait maximal timeout seconds.",
1231                 type => 'integer',
1232                 minimum => 0,
1233                 optional => 1,
1234             },
1235             keepActive => {
1236                 description => "Do not decativate storage volumes.",
1237                 type => 'boolean',
1238                 optional => 1,
1239                 default => 0,
1240             }
1241         },
1242     },
1243     returns => {
1244         type => 'string',
1245     },
1246     code => sub {
1247         my ($param) = @_;
1248
1249         my $rpcenv = PVE::RPCEnvironment::get();
1250
1251         my $authuser = $rpcenv->get_user();
1252
1253         my $node = extract_param($param, 'node');
1254
1255         my $vmid = extract_param($param, 'vmid');
1256
1257         my $skiplock = extract_param($param, 'skiplock');
1258         raise_param_exc({ skiplock => "Only root may use this option." })
1259             if $skiplock && $authuser ne 'root@pam';
1260
1261         my $keepActive = extract_param($param, 'keepActive');
1262         raise_param_exc({ keepActive => "Only root may use this option." })
1263             if $keepActive && $authuser ne 'root@pam';
1264
1265         my $storecfg = PVE::Storage::config();
1266
1267         if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1268
1269             my $hacmd = sub {
1270                 my $upid = shift;
1271
1272                 my $service = "pvevm:$vmid";
1273
1274                 my $cmd = ['clusvcadm', '-d', $service];
1275
1276                 print "Executing HA stop for VM $vmid\n";
1277
1278                 PVE::Tools::run_command($cmd);
1279
1280                 return;
1281             };
1282
1283             return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1284
1285         } else {
1286             my $realcmd = sub {
1287                 my $upid = shift;
1288
1289                 syslog('info', "stop VM $vmid: $upid\n");
1290
1291                 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
1292                                          $param->{timeout}, 0, 1, $keepActive);
1293
1294                 return;
1295             };
1296
1297             return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1298         }
1299     }});
1300
1301 __PACKAGE__->register_method({
1302     name => 'vm_reset',
1303     path => '{vmid}/status/reset',
1304     method => 'POST',
1305     protected => 1,
1306     proxyto => 'node',
1307     description => "Reset virtual machine.",
1308     permissions => {
1309         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1310     },
1311     parameters => {
1312         additionalProperties => 0,
1313         properties => {
1314             node => get_standard_option('pve-node'),
1315             vmid => get_standard_option('pve-vmid'),
1316             skiplock => get_standard_option('skiplock'),
1317         },
1318     },
1319     returns => {
1320         type => 'string',
1321     },
1322     code => sub {
1323         my ($param) = @_;
1324
1325         my $rpcenv = PVE::RPCEnvironment::get();
1326
1327         my $authuser = $rpcenv->get_user();
1328
1329         my $node = extract_param($param, 'node');
1330
1331         my $vmid = extract_param($param, 'vmid');
1332
1333         my $skiplock = extract_param($param, 'skiplock');
1334         raise_param_exc({ skiplock => "Only root may use this option." })
1335             if $skiplock && $authuser ne 'root@pam';
1336
1337         die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1338
1339         my $realcmd = sub {
1340             my $upid = shift;
1341
1342             PVE::QemuServer::vm_reset($vmid, $skiplock);
1343
1344             return;
1345         };
1346
1347         return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1348     }});
1349
1350 __PACKAGE__->register_method({
1351     name => 'vm_shutdown',
1352     path => '{vmid}/status/shutdown',
1353     method => 'POST',
1354     protected => 1,
1355     proxyto => 'node',
1356     description => "Shutdown virtual machine.",
1357     permissions => {
1358         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1359     },
1360     parameters => {
1361         additionalProperties => 0,
1362         properties => {
1363             node => get_standard_option('pve-node'),
1364             vmid => get_standard_option('pve-vmid'),
1365             skiplock => get_standard_option('skiplock'),
1366             timeout => {
1367                 description => "Wait maximal timeout seconds.",
1368                 type => 'integer',
1369                 minimum => 0,
1370                 optional => 1,
1371             },
1372             forceStop => {
1373                 description => "Make sure the VM stops.",
1374                 type => 'boolean',
1375                 optional => 1,
1376                 default => 0,
1377             },
1378             keepActive => {
1379                 description => "Do not decativate storage volumes.",
1380                 type => 'boolean',
1381                 optional => 1,
1382                 default => 0,
1383             }
1384         },
1385     },
1386     returns => {
1387         type => 'string',
1388     },
1389     code => sub {
1390         my ($param) = @_;
1391
1392         my $rpcenv = PVE::RPCEnvironment::get();
1393
1394         my $authuser = $rpcenv->get_user();
1395
1396         my $node = extract_param($param, 'node');
1397
1398         my $vmid = extract_param($param, 'vmid');
1399
1400         my $skiplock = extract_param($param, 'skiplock');
1401         raise_param_exc({ skiplock => "Only root may use this option." })
1402             if $skiplock && $authuser ne 'root@pam';
1403
1404         my $keepActive = extract_param($param, 'keepActive');
1405         raise_param_exc({ keepActive => "Only root may use this option." })
1406             if $keepActive && $authuser ne 'root@pam';
1407
1408         my $storecfg = PVE::Storage::config();
1409
1410         my $realcmd = sub {
1411             my $upid = shift;
1412
1413             syslog('info', "shutdown VM $vmid: $upid\n");
1414
1415             PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
1416                                      1, $param->{forceStop}, $keepActive);
1417
1418             return;
1419         };
1420
1421         return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1422     }});
1423
1424 __PACKAGE__->register_method({
1425     name => 'vm_suspend',
1426     path => '{vmid}/status/suspend',
1427     method => 'POST',
1428     protected => 1,
1429     proxyto => 'node',
1430     description => "Suspend virtual machine.",
1431     permissions => {
1432         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1433     },
1434     parameters => {
1435         additionalProperties => 0,
1436         properties => {
1437             node => get_standard_option('pve-node'),
1438             vmid => get_standard_option('pve-vmid'),
1439             skiplock => get_standard_option('skiplock'),
1440         },
1441     },
1442     returns => {
1443         type => 'string',
1444     },
1445     code => sub {
1446         my ($param) = @_;
1447
1448         my $rpcenv = PVE::RPCEnvironment::get();
1449
1450         my $authuser = $rpcenv->get_user();
1451
1452         my $node = extract_param($param, 'node');
1453
1454         my $vmid = extract_param($param, 'vmid');
1455
1456         my $skiplock = extract_param($param, 'skiplock');
1457         raise_param_exc({ skiplock => "Only root may use this option." })
1458             if $skiplock && $authuser ne 'root@pam';
1459
1460         die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1461
1462         my $realcmd = sub {
1463             my $upid = shift;
1464
1465             syslog('info', "suspend VM $vmid: $upid\n");
1466
1467             PVE::QemuServer::vm_suspend($vmid, $skiplock);
1468
1469             return;
1470         };
1471
1472         return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1473     }});
1474
1475 __PACKAGE__->register_method({
1476     name => 'vm_resume',
1477     path => '{vmid}/status/resume',
1478     method => 'POST',
1479     protected => 1,
1480     proxyto => 'node',
1481     description => "Resume virtual machine.",
1482     permissions => {
1483         check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1484     },
1485     parameters => {
1486         additionalProperties => 0,
1487         properties => {
1488             node => get_standard_option('pve-node'),
1489             vmid => get_standard_option('pve-vmid'),
1490             skiplock => get_standard_option('skiplock'),
1491         },
1492     },
1493     returns => {
1494         type => 'string',
1495     },
1496     code => sub {
1497         my ($param) = @_;
1498
1499         my $rpcenv = PVE::RPCEnvironment::get();
1500
1501         my $authuser = $rpcenv->get_user();
1502
1503         my $node = extract_param($param, 'node');
1504
1505         my $vmid = extract_param($param, 'vmid');
1506
1507         my $skiplock = extract_param($param, 'skiplock');
1508         raise_param_exc({ skiplock => "Only root may use this option." })
1509             if $skiplock && $authuser ne 'root@pam';
1510
1511         die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1512
1513         my $realcmd = sub {
1514             my $upid = shift;
1515
1516             syslog('info', "resume VM $vmid: $upid\n");
1517
1518             PVE::QemuServer::vm_resume($vmid, $skiplock);
1519
1520             return;
1521         };
1522
1523         return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1524     }});
1525
1526 __PACKAGE__->register_method({
1527     name => 'vm_sendkey',
1528     path => '{vmid}/sendkey',
1529     method => 'PUT',
1530     protected => 1,
1531     proxyto => 'node',
1532     description => "Send key event to virtual machine.",
1533     permissions => {
1534         check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1535     },
1536     parameters => {
1537         additionalProperties => 0,
1538         properties => {
1539             node => get_standard_option('pve-node'),
1540             vmid => get_standard_option('pve-vmid'),
1541             skiplock => get_standard_option('skiplock'),
1542             key => {
1543                 description => "The key (qemu monitor encoding).",
1544                 type => 'string'
1545             }
1546         },
1547     },
1548     returns => { type => 'null'},
1549     code => sub {
1550         my ($param) = @_;
1551
1552         my $rpcenv = PVE::RPCEnvironment::get();
1553
1554         my $authuser = $rpcenv->get_user();
1555
1556         my $node = extract_param($param, 'node');
1557
1558         my $vmid = extract_param($param, 'vmid');
1559
1560         my $skiplock = extract_param($param, 'skiplock');
1561         raise_param_exc({ skiplock => "Only root may use this option." })
1562             if $skiplock && $authuser ne 'root@pam';
1563
1564         PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1565
1566         return;
1567     }});
1568
1569 __PACKAGE__->register_method({
1570     name => 'migrate_vm',
1571     path => '{vmid}/migrate',
1572     method => 'POST',
1573     protected => 1,
1574     proxyto => 'node',
1575     description => "Migrate virtual machine. Creates a new migration task.",
1576     permissions => {
1577         check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1578     },
1579     parameters => {
1580         additionalProperties => 0,
1581         properties => {
1582             node => get_standard_option('pve-node'),
1583             vmid => get_standard_option('pve-vmid'),
1584             target => get_standard_option('pve-node', { description => "Target node." }),
1585             online => {
1586                 type => 'boolean',
1587                 description => "Use online/live migration.",
1588                 optional => 1,
1589             },
1590             force => {
1591                 type => 'boolean',
1592                 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1593                 optional => 1,
1594             },
1595         },
1596     },
1597     returns => {
1598         type => 'string',
1599         description => "the task ID.",
1600     },
1601     code => sub {
1602         my ($param) = @_;
1603
1604         my $rpcenv = PVE::RPCEnvironment::get();
1605
1606         my $authuser = $rpcenv->get_user();
1607
1608         my $target = extract_param($param, 'target');
1609
1610         my $localnode = PVE::INotify::nodename();
1611         raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1612
1613         PVE::Cluster::check_cfs_quorum();
1614
1615         PVE::Cluster::check_node_exists($target);
1616
1617         my $targetip = PVE::Cluster::remote_node_ip($target);
1618
1619         my $vmid = extract_param($param, 'vmid');
1620
1621         raise_param_exc({ force => "Only root may use this option." })
1622             if $param->{force} && $authuser ne 'root@pam';
1623
1624         # test if VM exists
1625         my $conf = PVE::QemuServer::load_config($vmid);
1626
1627         # try to detect errors early
1628
1629         PVE::QemuServer::check_lock($conf);
1630
1631         if (PVE::QemuServer::check_running($vmid)) {
1632             die "cant migrate running VM without --online\n"
1633                 if !$param->{online};
1634         }
1635
1636         my $storecfg = PVE::Storage::config();
1637         PVE::QemuServer::check_storage_availability($storecfg, $conf, "test");
1638
1639         if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1640
1641             my $hacmd = sub {
1642                 my $upid = shift;
1643
1644                 my $service = "pvevm:$vmid";
1645
1646                 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1647
1648                 print "Executing HA migrate for VM $vmid to node $target\n";
1649
1650                 PVE::Tools::run_command($cmd);
1651
1652                 return;
1653             };
1654
1655             return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1656
1657         } else {
1658
1659             my $realcmd = sub {
1660                 my $upid = shift;
1661
1662                 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
1663             };
1664
1665             return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1666         }
1667
1668     }});
1669
1670 __PACKAGE__->register_method({
1671     name => 'monitor',
1672     path => '{vmid}/monitor',
1673     method => 'POST',
1674     protected => 1,
1675     proxyto => 'node',
1676     description => "Execute Qemu monitor commands.",
1677     permissions => {
1678         check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1679     },
1680     parameters => {
1681         additionalProperties => 0,
1682         properties => {
1683             node => get_standard_option('pve-node'),
1684             vmid => get_standard_option('pve-vmid'),
1685             command => {
1686                 type => 'string',
1687                 description => "The monitor command.",
1688             }
1689         },
1690     },
1691     returns => { type => 'string'},
1692     code => sub {
1693         my ($param) = @_;
1694
1695         my $vmid = $param->{vmid};
1696
1697         my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1698
1699         my $res = '';
1700         eval {
1701             $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1702         };
1703         $res = "ERROR: $@" if $@;
1704
1705         return $res;
1706     }});
1707
1708 1;