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