]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
bump version to 0.4-1
[pve-container.git] / src / PVE / API2 / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::API2::LXC;
2
3use strict;
4use warnings;
5
6use PVE::SafeSyslog;
7use PVE::Tools qw(extract_param run_command);
8use PVE::Exception qw(raise raise_param_exc);
9use PVE::INotify;
9c2d4ce9 10use PVE::Cluster qw(cfs_read_file);
f76a2828
DM
11use PVE::AccessControl;
12use PVE::Storage;
13use PVE::RESTHandler;
14use PVE::RPCEnvironment;
15use PVE::LXC;
5c752bbf 16use PVE::HA::Config;
f76a2828
DM
17use PVE::JSONSchema qw(get_standard_option);
18use base qw(PVE::RESTHandler);
19
20use Data::Dumper; # fixme: remove
21
22my $get_container_storage = sub {
23 my ($stcfg, $vmid, $lxc_conf) = @_;
24
25 my $path = $lxc_conf->{'lxc.rootfs'};
26 my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path);
27 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid;
28 return wantarray ? ($sid, $volname, $path) : $sid;
29};
30
31my $check_ct_modify_config_perm = sub {
32 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
5c752bbf 33
f76a2828
DM
34 return 1 if $authuser ne 'root@pam';
35
36 foreach my $opt (@$key_list) {
37
38 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
39 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
40 } elsif ($opt eq 'disk') {
41 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
42 } elsif ($opt eq 'memory' || $opt eq 'swap') {
43 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
5c752bbf 44 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
f76a2828
DM
45 $opt eq 'searchdomain' || $opt eq 'hostname') {
46 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
47 } else {
48 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
49 }
50 }
51
52 return 1;
53};
54
55
56__PACKAGE__->register_method({
5c752bbf
DM
57 name => 'vmlist',
58 path => '',
f76a2828
DM
59 method => 'GET',
60 description => "LXC container index (per node).",
61 permissions => {
62 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
63 user => 'all',
64 },
65 proxyto => 'node',
66 protected => 1, # /proc files are only readable by root
67 parameters => {
68 additionalProperties => 0,
69 properties => {
70 node => get_standard_option('pve-node'),
71 },
72 },
73 returns => {
74 type => 'array',
75 items => {
76 type => "object",
77 properties => {},
78 },
79 links => [ { rel => 'child', href => "{vmid}" } ],
80 },
81 code => sub {
82 my ($param) = @_;
83
84 my $rpcenv = PVE::RPCEnvironment::get();
85 my $authuser = $rpcenv->get_user();
86
87 my $vmstatus = PVE::LXC::vmstatus();
88
89 my $res = [];
90 foreach my $vmid (keys %$vmstatus) {
91 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
92
93 my $data = $vmstatus->{$vmid};
94 $data->{vmid} = $vmid;
95 push @$res, $data;
96 }
97
98 return $res;
5c752bbf 99
f76a2828
DM
100 }});
101
9c2d4ce9 102__PACKAGE__->register_method({
5c752bbf
DM
103 name => 'create_vm',
104 path => '',
9c2d4ce9
DM
105 method => 'POST',
106 description => "Create or restore a container.",
107 permissions => {
108 user => 'all', # check inside
109 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
110 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
111 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
112 },
113 protected => 1,
114 proxyto => 'node',
115 parameters => {
116 additionalProperties => 0,
117 properties => PVE::LXC::json_config_properties({
118 node => get_standard_option('pve-node'),
119 vmid => get_standard_option('pve-vmid'),
120 ostemplate => {
121 description => "The OS template or backup file.",
5c752bbf 122 type => 'string',
9c2d4ce9
DM
123 maxLength => 255,
124 },
5c752bbf
DM
125 password => {
126 optional => 1,
9c2d4ce9
DM
127 type => 'string',
128 description => "Sets root password inside container.",
168d6b07 129 minLength => 5,
9c2d4ce9
DM
130 },
131 storage => get_standard_option('pve-storage-id', {
132 description => "Target storage.",
133 default => 'local',
134 optional => 1,
135 }),
136 force => {
5c752bbf 137 optional => 1,
9c2d4ce9
DM
138 type => 'boolean',
139 description => "Allow to overwrite existing container.",
140 },
141 restore => {
5c752bbf 142 optional => 1,
9c2d4ce9
DM
143 type => 'boolean',
144 description => "Mark this as restore task.",
145 },
5c752bbf 146 pool => {
9c2d4ce9
DM
147 optional => 1,
148 type => 'string', format => 'pve-poolid',
149 description => "Add the VM to the specified pool.",
150 },
151 }),
152 },
5c752bbf 153 returns => {
9c2d4ce9
DM
154 type => 'string',
155 },
156 code => sub {
157 my ($param) = @_;
158
159 my $rpcenv = PVE::RPCEnvironment::get();
160
161 my $authuser = $rpcenv->get_user();
162
163 my $node = extract_param($param, 'node');
164
165 my $vmid = extract_param($param, 'vmid');
166
167 my $basecfg_fn = PVE::LXC::config_file($vmid);
168
169 my $same_container_exists = -f $basecfg_fn;
170
171 my $restore = extract_param($param, 'restore');
172
173 my $force = extract_param($param, 'force');
174
175 if (!($same_container_exists && $restore && $force)) {
176 PVE::Cluster::check_vmid_unused($vmid);
177 }
5c752bbf 178
9c2d4ce9
DM
179 my $password = extract_param($param, 'password');
180
181 my $storage = extract_param($param, 'storage') || 'local';
182
183 my $pool = extract_param($param, 'pool');
5c752bbf 184
9c2d4ce9
DM
185 my $storage_cfg = cfs_read_file("storage.cfg");
186
187 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
188
189 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
190 if !$scfg->{content}->{rootdir};
191
192 my $private = PVE::Storage::get_private_dir($storage_cfg, $storage, $vmid);
193
194 if (defined($pool)) {
195 $rpcenv->check_pool_exist($pool);
196 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
5c752bbf 197 }
9c2d4ce9
DM
198
199 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
200 # OK
201 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
202 # OK
203 } elsif ($restore && $force && $same_container_exists &&
204 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
205 # OK: user has VM.Backup permissions, and want to restore an existing VM
206 } else {
207 raise_perm_exc();
208 }
209
210 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
211
212 PVE::Storage::activate_storage($storage_cfg, $storage);
213
214 my $ostemplate = extract_param($param, 'ostemplate');
5c752bbf 215
9c2d4ce9
DM
216 my $archive;
217
218 if ($ostemplate eq '-') {
5c752bbf 219 die "archive pipe not implemented\n"
9c2d4ce9
DM
220 # $archive = '-';
221 } else {
222 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
223 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
224 }
225
9c2d4ce9 226 my $conf = {};
5c752bbf 227
93285df8
DM
228 $param->{hostname} ||= "CT$vmid";
229 $param->{memory} ||= 512;
44da0641 230 $param->{swap} = 512 if !defined($param->{swap});
93285df8
DM
231
232 PVE::LXC::update_lxc_config($vmid, $conf, 0, $param);
233
234 # assigng default names, so that we can configure network with LXCSetup
235 foreach my $k (keys %$conf) {
236 next if $k !~ m/^net(\d+)$/;
237 my $d = $conf->{$k};
238 my $ind = $1;
239 $d->{name} = "eth$ind";
240 }
241
9c2d4ce9
DM
242 my $code = sub {
243 my $temp_conf_fn = PVE::LXC::write_temp_config($vmid, $conf);
244
245 my $cmd = ['lxc-create', '-f', $temp_conf_fn, '-t', 'pve', '-n', $vmid,
246 '--', '--archive', $archive];
247
168d6b07
DM
248 if (defined($password)) {
249 push $cmd, '--password', $password
250 }
251
9c2d4ce9
DM
252 eval { PVE::Tools::run_command($cmd); };
253 my $err = $@;
254
255 unlink $temp_conf_fn;
256
257 die $err if $err;
258 };
5c752bbf 259
9c2d4ce9
DM
260 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
261
5c752bbf 262 return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate',
9c2d4ce9 263 $vmid, $authuser, $realcmd);
5c752bbf 264
9c2d4ce9
DM
265 }});
266
f76a2828 267my $vm_config_perm_list = [
5c752bbf
DM
268 'VM.Config.Disk',
269 'VM.Config.CPU',
270 'VM.Config.Memory',
271 'VM.Config.Network',
f76a2828
DM
272 'VM.Config.Options',
273 ];
274
275__PACKAGE__->register_method({
5c752bbf
DM
276 name => 'update_vm',
277 path => '{vmid}/config',
f76a2828
DM
278 method => 'PUT',
279 protected => 1,
280 proxyto => 'node',
281 description => "Set container options.",
282 permissions => {
283 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
284 },
285 parameters => {
286 additionalProperties => 0,
287 properties => PVE::LXC::json_config_properties(
288 {
289 node => get_standard_option('pve-node'),
290 vmid => get_standard_option('pve-vmid'),
ec52ac21
DM
291 delete => {
292 type => 'string', format => 'pve-configid-list',
293 description => "A list of settings you want to delete.",
294 optional => 1,
295 },
f76a2828
DM
296 digest => {
297 type => 'string',
298 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
299 maxLength => 40,
5c752bbf 300 optional => 1,
f76a2828
DM
301 }
302 }),
303 },
304 returns => { type => 'null'},
305 code => sub {
306 my ($param) = @_;
307
308 my $rpcenv = PVE::RPCEnvironment::get();
309
310 my $authuser = $rpcenv->get_user();
311
312 my $node = extract_param($param, 'node');
313
314 my $vmid = extract_param($param, 'vmid');
315
316 my $digest = extract_param($param, 'digest');
317
318 die "no options specified\n" if !scalar(keys %$param);
319
ec52ac21
DM
320 my $delete_str = extract_param($param, 'delete');
321 my @delete = PVE::Tools::split_list($delete_str);
5c752bbf 322
ec52ac21 323 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
5c752bbf 324
ec52ac21
DM
325 foreach my $opt (@delete) {
326 raise_param_exc({ delete => "you can't use '-$opt' and " .
327 "-delete $opt' at the same time" })
328 if defined($param->{$opt});
5c752bbf 329
ec52ac21
DM
330 if (!PVE::LXC::option_exists($opt)) {
331 raise_param_exc({ delete => "unknown option '$opt'" });
332 }
333 }
334
f76a2828
DM
335 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
336
337 my $code = sub {
338
339 my $conf = PVE::LXC::load_config($vmid);
340
341 PVE::Tools::assert_if_modified($digest, $conf->{digest});
342
93285df8 343 my $running = PVE::LXC::check_running($vmid);
ec52ac21 344
93285df8 345 PVE::LXC::update_lxc_config($vmid, $conf, $running, $param, \@delete);
ec52ac21
DM
346
347 PVE::LXC::write_config($vmid, $conf);
f76a2828
DM
348 };
349
350 PVE::LXC::lock_container($vmid, undef, $code);
351
352 return undef;
353 }});
354
355__PACKAGE__->register_method ({
5c752bbf 356 subclass => "PVE::API2::Firewall::CT",
f76a2828
DM
357 path => '{vmid}/firewall',
358});
359
360__PACKAGE__->register_method({
361 name => 'vmdiridx',
5c752bbf 362 path => '{vmid}',
f76a2828
DM
363 method => 'GET',
364 proxyto => 'node',
365 description => "Directory index",
366 permissions => {
367 user => 'all',
368 },
369 parameters => {
370 additionalProperties => 0,
371 properties => {
372 node => get_standard_option('pve-node'),
373 vmid => get_standard_option('pve-vmid'),
374 },
375 },
376 returns => {
377 type => 'array',
378 items => {
379 type => "object",
380 properties => {
381 subdir => { type => 'string' },
382 },
383 },
384 links => [ { rel => 'child', href => "{subdir}" } ],
385 },
386 code => sub {
387 my ($param) = @_;
388
389 # test if VM exists
e901d418 390 my $conf = PVE::LXC::load_config($param->{vmid});
f76a2828
DM
391
392 my $res = [
393 { subdir => 'config' },
fff3a342
DM
394 { subdir => 'status' },
395 { subdir => 'vncproxy' },
396 { subdir => 'vncwebsocket' },
397 { subdir => 'spiceproxy' },
398 { subdir => 'migrate' },
f76a2828
DM
399# { subdir => 'initlog' },
400 { subdir => 'rrd' },
401 { subdir => 'rrddata' },
402 { subdir => 'firewall' },
403 ];
5c752bbf 404
f76a2828
DM
405 return $res;
406 }});
407
408__PACKAGE__->register_method({
5c752bbf
DM
409 name => 'rrd',
410 path => '{vmid}/rrd',
f76a2828
DM
411 method => 'GET',
412 protected => 1, # fixme: can we avoid that?
413 permissions => {
414 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
415 },
416 description => "Read VM RRD statistics (returns PNG)",
417 parameters => {
418 additionalProperties => 0,
419 properties => {
420 node => get_standard_option('pve-node'),
421 vmid => get_standard_option('pve-vmid'),
422 timeframe => {
423 description => "Specify the time frame you are interested in.",
424 type => 'string',
425 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
426 },
427 ds => {
428 description => "The list of datasources you want to display.",
429 type => 'string', format => 'pve-configid-list',
430 },
431 cf => {
432 description => "The RRD consolidation function",
433 type => 'string',
434 enum => [ 'AVERAGE', 'MAX' ],
435 optional => 1,
436 },
437 },
438 },
439 returns => {
440 type => "object",
441 properties => {
442 filename => { type => 'string' },
443 },
444 },
445 code => sub {
446 my ($param) = @_;
447
448 return PVE::Cluster::create_rrd_graph(
5c752bbf 449 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 450 $param->{ds}, $param->{cf});
5c752bbf 451
f76a2828
DM
452 }});
453
454__PACKAGE__->register_method({
5c752bbf
DM
455 name => 'rrddata',
456 path => '{vmid}/rrddata',
f76a2828
DM
457 method => 'GET',
458 protected => 1, # fixme: can we avoid that?
459 permissions => {
460 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
461 },
462 description => "Read VM RRD statistics",
463 parameters => {
464 additionalProperties => 0,
465 properties => {
466 node => get_standard_option('pve-node'),
467 vmid => get_standard_option('pve-vmid'),
468 timeframe => {
469 description => "Specify the time frame you are interested in.",
470 type => 'string',
471 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
472 },
473 cf => {
474 description => "The RRD consolidation function",
475 type => 'string',
476 enum => [ 'AVERAGE', 'MAX' ],
477 optional => 1,
478 },
479 },
480 },
481 returns => {
482 type => "array",
483 items => {
484 type => "object",
485 properties => {},
486 },
487 },
488 code => sub {
489 my ($param) = @_;
490
491 return PVE::Cluster::create_rrd_data(
492 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
493 }});
494
495
496__PACKAGE__->register_method({
5c752bbf
DM
497 name => 'vm_config',
498 path => '{vmid}/config',
f76a2828
DM
499 method => 'GET',
500 proxyto => 'node',
501 description => "Get container configuration.",
502 permissions => {
503 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
504 },
505 parameters => {
506 additionalProperties => 0,
507 properties => {
508 node => get_standard_option('pve-node'),
509 vmid => get_standard_option('pve-vmid'),
510 },
511 },
5c752bbf 512 returns => {
f76a2828
DM
513 type => "object",
514 properties => {
515 digest => {
516 type => 'string',
517 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
518 }
519 },
520 },
521 code => sub {
522 my ($param) = @_;
523
524 my $lxc_conf = PVE::LXC::load_config($param->{vmid});
525
526 # NOTE: we only return selected/converted values
5c752bbf 527
b80dd50a 528 my $conf = PVE::LXC::lxc_conf_to_pve($param->{vmid}, $lxc_conf);
f76a2828
DM
529
530 my $stcfg = PVE::Cluster::cfs_read_file("storage.cfg");
531
532 my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $lxc_conf);
533 $conf->{storage} = $sid || $path;
534
f76a2828
DM
535 return $conf;
536 }});
537
538__PACKAGE__->register_method({
5c752bbf
DM
539 name => 'destroy_vm',
540 path => '{vmid}',
f76a2828
DM
541 method => 'DELETE',
542 protected => 1,
543 proxyto => 'node',
544 description => "Destroy the container (also delete all uses files).",
545 permissions => {
546 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
547 },
548 parameters => {
549 additionalProperties => 0,
550 properties => {
551 node => get_standard_option('pve-node'),
552 vmid => get_standard_option('pve-vmid'),
553 },
554 },
5c752bbf 555 returns => {
f76a2828
DM
556 type => 'string',
557 },
558 code => sub {
559 my ($param) = @_;
560
561 my $rpcenv = PVE::RPCEnvironment::get();
562
563 my $authuser = $rpcenv->get_user();
564
565 my $vmid = $param->{vmid};
566
567 # test if container exists
568 my $conf = PVE::LXC::load_config($param->{vmid});
569
570 my $realcmd = sub {
571 my $cmd = ['lxc-destroy', '-n', $vmid ];
572
573 run_command($cmd);
574
575 PVE::AccessControl::remove_vm_from_pool($vmid);
576 };
577
578 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
579 }});
580
fff3a342
DM
581my $sslcert;
582
583__PACKAGE__->register_method ({
584 name => 'vncproxy',
585 path => '{vmid}/vncproxy',
586 method => 'POST',
587 protected => 1,
588 permissions => {
589 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
590 },
591 description => "Creates a TCP VNC proxy connections.",
592 parameters => {
593 additionalProperties => 0,
594 properties => {
595 node => get_standard_option('pve-node'),
596 vmid => get_standard_option('pve-vmid'),
597 websocket => {
598 optional => 1,
599 type => 'boolean',
600 description => "use websocket instead of standard VNC.",
601 },
602 },
603 },
604 returns => {
605 additionalProperties => 0,
606 properties => {
607 user => { type => 'string' },
608 ticket => { type => 'string' },
609 cert => { type => 'string' },
610 port => { type => 'integer' },
611 upid => { type => 'string' },
612 },
613 },
614 code => sub {
615 my ($param) = @_;
616
617 my $rpcenv = PVE::RPCEnvironment::get();
618
619 my $authuser = $rpcenv->get_user();
620
621 my $vmid = $param->{vmid};
622 my $node = $param->{node};
623
624 my $authpath = "/vms/$vmid";
625
626 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
627
628 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
629 if !$sslcert;
630
631 my $port = PVE::Tools::next_vnc_port();
632
633 my $remip;
634
635 if ($node ne PVE::INotify::nodename()) {
636 $remip = PVE::Cluster::remote_node_ip($node);
637 }
638
639 # NOTE: vncterm VNC traffic is already TLS encrypted,
640 # so we select the fastest chipher here (or 'none'?)
641 my $remcmd = $remip ?
642 ['/usr/bin/ssh', '-t', $remip] : [];
643
644 my $shcmd = [ '/usr/bin/dtach', '-A',
645 "/var/run/dtach/vzctlconsole$vmid",
646 '-r', 'winch', '-z',
647 'lxc-console', '-n', $vmid ];
648
649 my $realcmd = sub {
650 my $upid = shift;
651
652 syslog ('info', "starting openvz vnc proxy $upid\n");
653
654 my $timeout = 10;
655
656 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
657 '-timeout', $timeout, '-authpath', $authpath,
658 '-perm', 'VM.Console'];
659
660 if ($param->{websocket}) {
661 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
662 push @$cmd, '-notls', '-listen', 'localhost';
663 }
664
665 push @$cmd, '-c', @$remcmd, @$shcmd;
666
667 run_command($cmd);
668
669 return;
670 };
671
672 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
673
674 PVE::Tools::wait_for_vnc_port($port);
675
676 return {
677 user => $authuser,
678 ticket => $ticket,
679 port => $port,
680 upid => $upid,
681 cert => $sslcert,
682 };
683 }});
684
685__PACKAGE__->register_method({
686 name => 'vncwebsocket',
687 path => '{vmid}/vncwebsocket',
688 method => 'GET',
689 permissions => {
690 description => "You also need to pass a valid ticket (vncticket).",
691 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
692 },
693 description => "Opens a weksocket for VNC traffic.",
694 parameters => {
695 additionalProperties => 0,
696 properties => {
697 node => get_standard_option('pve-node'),
698 vmid => get_standard_option('pve-vmid'),
699 vncticket => {
700 description => "Ticket from previous call to vncproxy.",
701 type => 'string',
702 maxLength => 512,
703 },
704 port => {
705 description => "Port number returned by previous vncproxy call.",
706 type => 'integer',
707 minimum => 5900,
708 maximum => 5999,
709 },
710 },
711 },
712 returns => {
713 type => "object",
714 properties => {
715 port => { type => 'string' },
716 },
717 },
718 code => sub {
719 my ($param) = @_;
720
721 my $rpcenv = PVE::RPCEnvironment::get();
722
723 my $authuser = $rpcenv->get_user();
724
725 my $authpath = "/vms/$param->{vmid}";
726
727 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
728
729 my $port = $param->{port};
730
731 return { port => $port };
732 }});
733
734__PACKAGE__->register_method ({
735 name => 'spiceproxy',
736 path => '{vmid}/spiceproxy',
737 method => 'POST',
738 protected => 1,
739 proxyto => 'node',
740 permissions => {
741 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
742 },
743 description => "Returns a SPICE configuration to connect to the CT.",
744 parameters => {
745 additionalProperties => 0,
746 properties => {
747 node => get_standard_option('pve-node'),
748 vmid => get_standard_option('pve-vmid'),
749 proxy => get_standard_option('spice-proxy', { optional => 1 }),
750 },
751 },
752 returns => get_standard_option('remote-viewer-config'),
753 code => sub {
754 my ($param) = @_;
755
756 my $vmid = $param->{vmid};
757 my $node = $param->{node};
758 my $proxy = $param->{proxy};
759
760 my $authpath = "/vms/$vmid";
761 my $permissions = 'VM.Console';
762
763 my $shcmd = ['/usr/bin/dtach', '-A',
764 "/var/run/dtach/vzctlconsole$vmid",
765 '-r', 'winch', '-z',
766 'lxc-console', '-n', $vmid];
767
768 my $title = "CT $vmid";
769
770 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
771 }});
5c752bbf
DM
772
773__PACKAGE__->register_method({
774 name => 'vmcmdidx',
775 path => '{vmid}/status',
776 method => 'GET',
777 proxyto => 'node',
778 description => "Directory index",
779 permissions => {
780 user => 'all',
781 },
782 parameters => {
783 additionalProperties => 0,
784 properties => {
785 node => get_standard_option('pve-node'),
786 vmid => get_standard_option('pve-vmid'),
787 },
788 },
789 returns => {
790 type => 'array',
791 items => {
792 type => "object",
793 properties => {
794 subdir => { type => 'string' },
795 },
796 },
797 links => [ { rel => 'child', href => "{subdir}" } ],
798 },
799 code => sub {
800 my ($param) = @_;
801
802 # test if VM exists
803 my $conf = PVE::OpenVZ::load_config($param->{vmid});
804
805 my $res = [
806 { subdir => 'current' },
807 { subdir => 'start' },
808 { subdir => 'stop' },
809 { subdir => 'shutdown' },
810 { subdir => 'migrate' },
811 ];
812
813 return $res;
814 }});
815
816__PACKAGE__->register_method({
817 name => 'vm_status',
818 path => '{vmid}/status/current',
819 method => 'GET',
820 proxyto => 'node',
821 protected => 1, # openvz /proc entries are only readable by root
822 description => "Get virtual machine status.",
823 permissions => {
824 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
825 },
826 parameters => {
827 additionalProperties => 0,
828 properties => {
829 node => get_standard_option('pve-node'),
830 vmid => get_standard_option('pve-vmid'),
831 },
832 },
833 returns => { type => 'object' },
834 code => sub {
835 my ($param) = @_;
836
837 # test if VM exists
838 my $conf = PVE::LXC::load_config($param->{vmid});
839
840 my $vmstatus = PVE::LXC::vmstatus($param->{vmid});
841 my $status = $vmstatus->{$param->{vmid}};
842
843 $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0;
844
845 return $status;
846 }});
847
848__PACKAGE__->register_method({
849 name => 'vm_start',
850 path => '{vmid}/status/start',
851 method => 'POST',
852 protected => 1,
853 proxyto => 'node',
854 description => "Start the container.",
855 permissions => {
856 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
857 },
858 parameters => {
859 additionalProperties => 0,
860 properties => {
861 node => get_standard_option('pve-node'),
862 vmid => get_standard_option('pve-vmid'),
863 },
864 },
865 returns => {
866 type => 'string',
867 },
868 code => sub {
869 my ($param) = @_;
870
871 my $rpcenv = PVE::RPCEnvironment::get();
872
873 my $authuser = $rpcenv->get_user();
874
875 my $node = extract_param($param, 'node');
876
877 my $vmid = extract_param($param, 'vmid');
878
879 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
880
881 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
882
883 my $hacmd = sub {
884 my $upid = shift;
885
886 my $service = "ct:$vmid";
887
888 my $cmd = ['ha-manager', 'enable', $service];
889
890 print "Executing HA start for CT $vmid\n";
891
892 PVE::Tools::run_command($cmd);
893
894 return;
895 };
896
897 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
898
899 } else {
900
901 my $realcmd = sub {
902 my $upid = shift;
903
904 syslog('info', "starting CT $vmid: $upid\n");
905
906 my $conf = PVE::LXC::load_config($vmid);
907 my $stcfg = cfs_read_file("storage.cfg");
908 if (my $sid = &$get_container_storage($stcfg, $vmid, $conf)) {
909 PVE::Storage::activate_storage($stcfg, $sid);
910 }
911
912 my $cmd = ['lxc-start', '-n', $vmid];
913
914 run_command($cmd);
915
916 return;
917 };
918
919 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
920 }
921 }});
922
923__PACKAGE__->register_method({
924 name => 'vm_stop',
925 path => '{vmid}/status/stop',
926 method => 'POST',
927 protected => 1,
928 proxyto => 'node',
929 description => "Stop the container.",
930 permissions => {
931 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
932 },
933 parameters => {
934 additionalProperties => 0,
935 properties => {
936 node => get_standard_option('pve-node'),
937 vmid => get_standard_option('pve-vmid'),
938 },
939 },
940 returns => {
941 type => 'string',
942 },
943 code => sub {
944 my ($param) = @_;
945
946 my $rpcenv = PVE::RPCEnvironment::get();
947
948 my $authuser = $rpcenv->get_user();
949
950 my $node = extract_param($param, 'node');
951
952 my $vmid = extract_param($param, 'vmid');
953
954 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
955
956 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
957
958 my $hacmd = sub {
959 my $upid = shift;
960
961 my $service = "ct:$vmid";
962
963 my $cmd = ['ha-manager', 'disable', $service];
964
965 print "Executing HA stop for CT $vmid\n";
966
967 PVE::Tools::run_command($cmd);
968
969 return;
970 };
971
972 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
973
974 } else {
975
976 my $realcmd = sub {
977 my $upid = shift;
978
979 syslog('info', "stoping CT $vmid: $upid\n");
980
981 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
982
983 run_command($cmd);
984
985 return;
986 };
987
988 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
989 }
990 }});
991
992__PACKAGE__->register_method({
993 name => 'vm_shutdown',
994 path => '{vmid}/status/shutdown',
995 method => 'POST',
996 protected => 1,
997 proxyto => 'node',
998 description => "Shutdown the container.",
999 permissions => {
1000 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1001 },
1002 parameters => {
1003 additionalProperties => 0,
1004 properties => {
1005 node => get_standard_option('pve-node'),
1006 vmid => get_standard_option('pve-vmid'),
1007 timeout => {
1008 description => "Wait maximal timeout seconds.",
1009 type => 'integer',
1010 minimum => 0,
1011 optional => 1,
1012 default => 60,
1013 },
1014 forceStop => {
1015 description => "Make sure the Container stops.",
1016 type => 'boolean',
1017 optional => 1,
1018 default => 0,
1019 }
1020 },
1021 },
1022 returns => {
1023 type => 'string',
1024 },
1025 code => sub {
1026 my ($param) = @_;
1027
1028 my $rpcenv = PVE::RPCEnvironment::get();
1029
1030 my $authuser = $rpcenv->get_user();
1031
1032 my $node = extract_param($param, 'node');
1033
1034 my $vmid = extract_param($param, 'vmid');
1035
1036 my $timeout = extract_param($param, 'timeout');
1037
1038 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1039
1040 my $realcmd = sub {
1041 my $upid = shift;
1042
1043 syslog('info', "shutdown CT $vmid: $upid\n");
1044
1045 my $cmd = ['lxc-stop', '-n', $vmid];
1046
1047 $timeout = 60 if !defined($timeout);
1048
1049 push @$cmd, '--timeout', $timeout;
1050
1051 eval { run_command($cmd, timeout => $timeout+5); };
1052 my $err = $@;
1053 return if !$err;
1054
1055 die $err if !$param->{forceStop};
1056
1057 warn "shutdown failed - forcing stop now\n";
1058
1059 push @$cmd, '--kill';
1060 run_command($cmd);
1061
1062 return;
1063 };
1064
1065 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1066
1067 return $upid;
1068 }});
1069
1070__PACKAGE__->register_method({
1071 name => 'vm_suspend',
1072 path => '{vmid}/status/suspend',
1073 method => 'POST',
1074 protected => 1,
1075 proxyto => 'node',
1076 description => "Suspend the container.",
1077 permissions => {
1078 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1079 },
1080 parameters => {
1081 additionalProperties => 0,
1082 properties => {
1083 node => get_standard_option('pve-node'),
1084 vmid => get_standard_option('pve-vmid'),
1085 },
1086 },
1087 returns => {
1088 type => 'string',
1089 },
1090 code => sub {
1091 my ($param) = @_;
1092
1093 my $rpcenv = PVE::RPCEnvironment::get();
1094
1095 my $authuser = $rpcenv->get_user();
1096
1097 my $node = extract_param($param, 'node');
1098
1099 my $vmid = extract_param($param, 'vmid');
1100
1101 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1102
1103 my $realcmd = sub {
1104 my $upid = shift;
1105
1106 syslog('info', "suspend CT $vmid: $upid\n");
1107
1108 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1109
1110 run_command($cmd);
1111
1112 return;
1113 };
1114
1115 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1116
1117 return $upid;
1118 }});
1119
1120__PACKAGE__->register_method({
1121 name => 'vm_resume',
1122 path => '{vmid}/status/resume',
1123 method => 'POST',
1124 protected => 1,
1125 proxyto => 'node',
1126 description => "Resume the container.",
1127 permissions => {
1128 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1129 },
1130 parameters => {
1131 additionalProperties => 0,
1132 properties => {
1133 node => get_standard_option('pve-node'),
1134 vmid => get_standard_option('pve-vmid'),
1135 },
1136 },
1137 returns => {
1138 type => 'string',
1139 },
1140 code => sub {
1141 my ($param) = @_;
1142
1143 my $rpcenv = PVE::RPCEnvironment::get();
1144
1145 my $authuser = $rpcenv->get_user();
1146
1147 my $node = extract_param($param, 'node');
1148
1149 my $vmid = extract_param($param, 'vmid');
1150
1151 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1152
1153 my $realcmd = sub {
1154 my $upid = shift;
1155
1156 syslog('info', "resume CT $vmid: $upid\n");
1157
1158 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1159 '-D', '/var/liv/vz/dump'];
1160
1161 run_command($cmd);
1162
1163 return;
1164 };
1165
1166 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1167
1168 return $upid;
1169 }});
1170
1171__PACKAGE__->register_method({
1172 name => 'migrate_vm',
1173 path => '{vmid}/migrate',
1174 method => 'POST',
1175 protected => 1,
1176 proxyto => 'node',
1177 description => "Migrate the container to another node. Creates a new migration task.",
1178 permissions => {
1179 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1180 },
1181 parameters => {
1182 additionalProperties => 0,
1183 properties => {
1184 node => get_standard_option('pve-node'),
1185 vmid => get_standard_option('pve-vmid'),
1186 target => get_standard_option('pve-node', { description => "Target node." }),
1187 online => {
1188 type => 'boolean',
1189 description => "Use online/live migration.",
1190 optional => 1,
1191 },
1192 },
1193 },
1194 returns => {
1195 type => 'string',
1196 description => "the task ID.",
1197 },
1198 code => sub {
1199 my ($param) = @_;
1200
1201 my $rpcenv = PVE::RPCEnvironment::get();
1202
1203 my $authuser = $rpcenv->get_user();
1204
1205 my $target = extract_param($param, 'target');
1206
1207 my $localnode = PVE::INotify::nodename();
1208 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1209
1210 PVE::Cluster::check_cfs_quorum();
1211
1212 PVE::Cluster::check_node_exists($target);
1213
1214 my $targetip = PVE::Cluster::remote_node_ip($target);
1215
1216 my $vmid = extract_param($param, 'vmid');
1217
1218 # test if VM exists
1219 PVE::LXC::load_config($vmid);
1220
1221 # try to detect errors early
1222 if (PVE::LXC::check_running($vmid)) {
1223 die "cant migrate running container without --online\n"
1224 if !$param->{online};
1225 }
1226
1227 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1228
1229 my $hacmd = sub {
1230 my $upid = shift;
1231
1232 my $service = "ct:$vmid";
1233
1234 my $cmd = ['ha-manager', 'migrate', $service, $target];
1235
1236 print "Executing HA migrate for CT $vmid to node $target\n";
1237
1238 PVE::Tools::run_command($cmd);
1239
1240 return;
1241 };
1242
1243 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1244
1245 } else {
1246
1247 my $realcmd = sub {
1248 my $upid = shift;
1249
1250 # fixme: implement lxc container migration
1251 die "lxc container migration not implemented\n";
1252
1253 return;
1254 };
1255
1256 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1257 }
1258 }});
1259
f76a2828 12601;