]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
underscore is a valid lxc config key character
[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 11use PVE::AccessControl;
2f9b5ead 12use PVE::Firewall;
f76a2828
DM
13use PVE::Storage;
14use PVE::RESTHandler;
15use PVE::RPCEnvironment;
16use PVE::LXC;
7af97ad5 17use PVE::LXC::Create;
52389a07
DM
18use PVE::API2::LXC::Config;
19use PVE::API2::LXC::Status;
20use PVE::API2::LXC::Snapshot;
5c752bbf 21use PVE::HA::Config;
f76a2828
DM
22use PVE::JSONSchema qw(get_standard_option);
23use base qw(PVE::RESTHandler);
24
25use Data::Dumper; # fixme: remove
26
52389a07
DM
27__PACKAGE__->register_method ({
28 subclass => "PVE::API2::LXC::Config",
29 path => '{vmid}/config',
30});
f76a2828 31
52389a07
DM
32__PACKAGE__->register_method ({
33 subclass => "PVE::API2::LXC::Status",
34 path => '{vmid}/status',
35});
f76a2828 36
52389a07
DM
37__PACKAGE__->register_method ({
38 subclass => "PVE::API2::LXC::Snapshot",
39 path => '{vmid}/snapshot',
40});
f76a2828 41
52389a07
DM
42__PACKAGE__->register_method ({
43 subclass => "PVE::API2::Firewall::CT",
44 path => '{vmid}/firewall',
45});
1e6c8d5b
DM
46
47my $destroy_disks = sub {
48 my ($storecfg, $vollist) = @_;
49
50 foreach my $volid (@$vollist) {
51 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
52 warn $@ if $@;
53 }
54};
52389a07 55
eb35f9c0 56my $create_disks = sub {
78ccc99b 57 my ($storecfg, $vmid, $settings, $conf) = @_;
27916659 58
eb35f9c0 59 my $vollist = [];
27916659 60
1e6c8d5b
DM
61 eval {
62 PVE::LXC::foreach_mountpoint($settings, sub {
63 my ($ms, $mountpoint) = @_;
27916659 64
1e6c8d5b
DM
65 my $volid = $mountpoint->{volume};
66 my $mp = $mountpoint->{mp};
27916659 67
1e6c8d5b 68 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
27916659 69
1e6c8d5b 70 return if !$storage;
27916659 71
78ccc99b
DM
72 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
73 my ($storeid, $size) = ($1, $2);
644f2f8f 74
1e6c8d5b 75 $size = int($size*1024) * 1024;
63f2ddfb 76
eb35f9c0
AD
77 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
78 # fixme: use better naming ct-$vmid-disk-X.raw?
63f2ddfb 79
eb35f9c0
AD
80 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
81 if ($size > 0) {
82 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
83 undef, $size);
84 } else {
85 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
86 undef, 0);
87 }
88 } elsif ($scfg->{type} eq 'zfspool') {
63f2ddfb 89
eb35f9c0
AD
90 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
91 undef, $size);
92 } elsif ($scfg->{type} eq 'drbd') {
93
94 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
95
96 } elsif ($scfg->{type} eq 'rbd') {
97
98 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
99 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
100 } else {
101 die "unable to create containers on storage type '$scfg->{type}'\n";
102 }
1e6c8d5b
DM
103 push @$vollist, $volid;
104 $conf->{$ms} = PVE::LXC::print_ct_mountpoint({volume => $volid, size => $size, mp => $mp });
105 } else {
106 # use specified/existing volid
107 }
108 });
109 };
eb35f9c0 110 # free allocated images on error
27916659 111 if (my $err = $@) {
eb35f9c0 112 syslog('err', "VM $vmid creating disks failed");
1e6c8d5b 113 &$destroy_disks($storecfg, $vollist);
eb35f9c0 114 die $err;
27916659 115 }
eb35f9c0 116 return $vollist;
27916659
DM
117};
118
f76a2828 119__PACKAGE__->register_method({
5c752bbf
DM
120 name => 'vmlist',
121 path => '',
f76a2828
DM
122 method => 'GET',
123 description => "LXC container index (per node).",
124 permissions => {
125 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
126 user => 'all',
127 },
128 proxyto => 'node',
129 protected => 1, # /proc files are only readable by root
130 parameters => {
131 additionalProperties => 0,
132 properties => {
133 node => get_standard_option('pve-node'),
134 },
135 },
136 returns => {
137 type => 'array',
138 items => {
139 type => "object",
140 properties => {},
141 },
142 links => [ { rel => 'child', href => "{vmid}" } ],
143 },
144 code => sub {
145 my ($param) = @_;
146
147 my $rpcenv = PVE::RPCEnvironment::get();
148 my $authuser = $rpcenv->get_user();
149
150 my $vmstatus = PVE::LXC::vmstatus();
151
152 my $res = [];
153 foreach my $vmid (keys %$vmstatus) {
154 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
155
156 my $data = $vmstatus->{$vmid};
157 $data->{vmid} = $vmid;
158 push @$res, $data;
159 }
160
161 return $res;
5c752bbf 162
f76a2828
DM
163 }});
164
9c2d4ce9 165__PACKAGE__->register_method({
5c752bbf
DM
166 name => 'create_vm',
167 path => '',
9c2d4ce9
DM
168 method => 'POST',
169 description => "Create or restore a container.",
170 permissions => {
171 user => 'all', # check inside
172 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
173 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
174 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
175 },
176 protected => 1,
177 proxyto => 'node',
178 parameters => {
179 additionalProperties => 0,
eb35f9c0 180 properties => PVE::LXC::json_config_properties({
9c2d4ce9
DM
181 node => get_standard_option('pve-node'),
182 vmid => get_standard_option('pve-vmid'),
183 ostemplate => {
184 description => "The OS template or backup file.",
5c752bbf 185 type => 'string',
9c2d4ce9
DM
186 maxLength => 255,
187 },
5c752bbf
DM
188 password => {
189 optional => 1,
9c2d4ce9
DM
190 type => 'string',
191 description => "Sets root password inside container.",
168d6b07 192 minLength => 5,
9c2d4ce9
DM
193 },
194 storage => get_standard_option('pve-storage-id', {
eb35f9c0 195 description => "Default Storage.",
9c2d4ce9
DM
196 default => 'local',
197 optional => 1,
198 }),
199 force => {
5c752bbf 200 optional => 1,
9c2d4ce9
DM
201 type => 'boolean',
202 description => "Allow to overwrite existing container.",
203 },
204 restore => {
5c752bbf 205 optional => 1,
9c2d4ce9
DM
206 type => 'boolean',
207 description => "Mark this as restore task.",
208 },
5c752bbf 209 pool => {
9c2d4ce9
DM
210 optional => 1,
211 type => 'string', format => 'pve-poolid',
212 description => "Add the VM to the specified pool.",
213 },
214 }),
215 },
5c752bbf 216 returns => {
9c2d4ce9
DM
217 type => 'string',
218 },
219 code => sub {
220 my ($param) = @_;
221
222 my $rpcenv = PVE::RPCEnvironment::get();
223
224 my $authuser = $rpcenv->get_user();
225
226 my $node = extract_param($param, 'node');
227
228 my $vmid = extract_param($param, 'vmid');
229
230 my $basecfg_fn = PVE::LXC::config_file($vmid);
231
232 my $same_container_exists = -f $basecfg_fn;
233
234 my $restore = extract_param($param, 'restore');
235
148d1cb4
DM
236 if ($restore) {
237 # fixme: limit allowed parameters
238
239 }
240
9c2d4ce9
DM
241 my $force = extract_param($param, 'force');
242
243 if (!($same_container_exists && $restore && $force)) {
244 PVE::Cluster::check_vmid_unused($vmid);
245 }
5c752bbf 246
9c2d4ce9
DM
247 my $password = extract_param($param, 'password');
248
27916659
DM
249 my $storage = extract_param($param, 'storage') // 'local';
250
9c2d4ce9
DM
251 my $storage_cfg = cfs_read_file("storage.cfg");
252
253 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
254
255 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
644f2f8f 256 if !($scfg->{content}->{images} || $scfg->{content}->{rootdir});
9c2d4ce9 257
27916659
DM
258 my $pool = extract_param($param, 'pool');
259
9c2d4ce9
DM
260 if (defined($pool)) {
261 $rpcenv->check_pool_exist($pool);
262 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
5c752bbf 263 }
9c2d4ce9 264
27916659
DM
265 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
266
9c2d4ce9
DM
267 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
268 # OK
269 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
270 # OK
271 } elsif ($restore && $force && $same_container_exists &&
272 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
273 # OK: user has VM.Backup permissions, and want to restore an existing VM
274 } else {
275 raise_perm_exc();
276 }
277
52389a07 278 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
9c2d4ce9
DM
279
280 PVE::Storage::activate_storage($storage_cfg, $storage);
281
282 my $ostemplate = extract_param($param, 'ostemplate');
5c752bbf 283
9c2d4ce9
DM
284 my $archive;
285
286 if ($ostemplate eq '-') {
148d1cb4
DM
287 die "pipe requires cli environment\n"
288 if $rpcenv->{type} ne 'cli';
289 die "pipe can only be used with restore tasks\n"
290 if !$restore;
291 $archive = '-';
b51a98d4 292 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
9c2d4ce9
DM
293 } else {
294 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
295 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
296 }
297
9c2d4ce9 298 my $conf = {};
5b4657d0 299
b51a98d4
DM
300 my $no_disk_param = {};
301 foreach my $opt (keys %$param) {
78ccc99b
DM
302 my $value = $param->{$opt};
303 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
304 # allow to use simple numbers (add default storage in that case)
305 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
306 } else {
307 $no_disk_param->{$opt} = $value;
308 }
b51a98d4
DM
309 }
310 PVE::LXC::update_pct_config($vmid, $conf, 0, $no_disk_param);
9c2d4ce9 311
148d1cb4
DM
312 my $check_vmid_usage = sub {
313 if ($force) {
314 die "cant overwrite running container\n"
315 if PVE::LXC::check_running($vmid);
316 } else {
317 PVE::Cluster::check_vmid_unused($vmid);
318 }
319 };
f507c3a7 320
5b4657d0 321 my $code = sub {
148d1cb4 322 &$check_vmid_usage(); # final check after locking
87273b2b 323
148d1cb4 324 PVE::Cluster::check_cfs_quorum();
eb35f9c0 325 my $vollist = [];
27916659
DM
326
327 eval {
b51a98d4
DM
328 if (!defined($param->{rootfs})) {
329 if ($restore) {
330 my (undef, $disksize) = PVE::LXC::Create::recover_config($archive);
331 die "unable to detect disk size - please specify rootfs (size)\n"
332 if !$disksize;
78ccc99b 333 $param->{rootfs} = "$storage:$disksize";
b51a98d4 334 } else {
78ccc99b 335 $param->{rootfs} = "$storage:4"; # defaults to 4GB
b51a98d4
DM
336 }
337 }
338
78ccc99b 339 $vollist = &$create_disks($storage_cfg, $vmid, $param, $conf);
eb35f9c0
AD
340
341 PVE::LXC::Create::create_rootfs($storage_cfg, $vmid, $conf, $archive, $password, $restore);
27916659
DM
342 # set some defaults
343 $conf->{hostname} ||= "CT$vmid";
344 $conf->{memory} ||= 512;
345 $conf->{swap} //= 512;
27916659
DM
346 PVE::LXC::create_config($vmid, $conf);
347 };
348 if (my $err = $@) {
1e6c8d5b 349 &$destroy_disks($storage_cfg, $vollist);
27916659
DM
350 PVE::LXC::destroy_config($vmid);
351 die $err;
6d098bf4 352 }
87273b2b 353 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
9c2d4ce9 354 };
5c752bbf 355
9c2d4ce9
DM
356 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
357
148d1cb4
DM
358 &$check_vmid_usage(); # first check before locking
359
360 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
9c2d4ce9 361 $vmid, $authuser, $realcmd);
5c752bbf 362
9c2d4ce9
DM
363 }});
364
f76a2828
DM
365__PACKAGE__->register_method({
366 name => 'vmdiridx',
5c752bbf 367 path => '{vmid}',
f76a2828
DM
368 method => 'GET',
369 proxyto => 'node',
370 description => "Directory index",
371 permissions => {
372 user => 'all',
373 },
374 parameters => {
375 additionalProperties => 0,
376 properties => {
377 node => get_standard_option('pve-node'),
378 vmid => get_standard_option('pve-vmid'),
379 },
380 },
381 returns => {
382 type => 'array',
383 items => {
384 type => "object",
385 properties => {
386 subdir => { type => 'string' },
387 },
388 },
389 links => [ { rel => 'child', href => "{subdir}" } ],
390 },
391 code => sub {
392 my ($param) = @_;
393
394 # test if VM exists
e901d418 395 my $conf = PVE::LXC::load_config($param->{vmid});
f76a2828
DM
396
397 my $res = [
398 { subdir => 'config' },
fff3a342
DM
399 { subdir => 'status' },
400 { subdir => 'vncproxy' },
401 { subdir => 'vncwebsocket' },
402 { subdir => 'spiceproxy' },
403 { subdir => 'migrate' },
f76a2828
DM
404# { subdir => 'initlog' },
405 { subdir => 'rrd' },
406 { subdir => 'rrddata' },
407 { subdir => 'firewall' },
cc5392c8 408 { subdir => 'snapshot' },
f76a2828 409 ];
5c752bbf 410
f76a2828
DM
411 return $res;
412 }});
413
414__PACKAGE__->register_method({
5c752bbf
DM
415 name => 'rrd',
416 path => '{vmid}/rrd',
f76a2828
DM
417 method => 'GET',
418 protected => 1, # fixme: can we avoid that?
419 permissions => {
420 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
421 },
422 description => "Read VM RRD statistics (returns PNG)",
423 parameters => {
424 additionalProperties => 0,
425 properties => {
426 node => get_standard_option('pve-node'),
427 vmid => get_standard_option('pve-vmid'),
428 timeframe => {
429 description => "Specify the time frame you are interested in.",
430 type => 'string',
431 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
432 },
433 ds => {
434 description => "The list of datasources you want to display.",
435 type => 'string', format => 'pve-configid-list',
436 },
437 cf => {
438 description => "The RRD consolidation function",
439 type => 'string',
440 enum => [ 'AVERAGE', 'MAX' ],
441 optional => 1,
442 },
443 },
444 },
445 returns => {
446 type => "object",
447 properties => {
448 filename => { type => 'string' },
449 },
450 },
451 code => sub {
452 my ($param) = @_;
453
454 return PVE::Cluster::create_rrd_graph(
5c752bbf 455 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 456 $param->{ds}, $param->{cf});
5c752bbf 457
f76a2828
DM
458 }});
459
460__PACKAGE__->register_method({
5c752bbf
DM
461 name => 'rrddata',
462 path => '{vmid}/rrddata',
f76a2828
DM
463 method => 'GET',
464 protected => 1, # fixme: can we avoid that?
465 permissions => {
466 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
467 },
468 description => "Read VM RRD statistics",
469 parameters => {
470 additionalProperties => 0,
471 properties => {
472 node => get_standard_option('pve-node'),
473 vmid => get_standard_option('pve-vmid'),
474 timeframe => {
475 description => "Specify the time frame you are interested in.",
476 type => 'string',
477 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
478 },
479 cf => {
480 description => "The RRD consolidation function",
481 type => 'string',
482 enum => [ 'AVERAGE', 'MAX' ],
483 optional => 1,
484 },
485 },
486 },
487 returns => {
488 type => "array",
489 items => {
490 type => "object",
491 properties => {},
492 },
493 },
494 code => sub {
495 my ($param) = @_;
496
497 return PVE::Cluster::create_rrd_data(
498 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
499 }});
500
f76a2828 501__PACKAGE__->register_method({
5c752bbf
DM
502 name => 'destroy_vm',
503 path => '{vmid}',
f76a2828
DM
504 method => 'DELETE',
505 protected => 1,
506 proxyto => 'node',
507 description => "Destroy the container (also delete all uses files).",
508 permissions => {
509 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
510 },
511 parameters => {
512 additionalProperties => 0,
513 properties => {
514 node => get_standard_option('pve-node'),
515 vmid => get_standard_option('pve-vmid'),
516 },
517 },
5c752bbf 518 returns => {
f76a2828
DM
519 type => 'string',
520 },
521 code => sub {
522 my ($param) = @_;
523
524 my $rpcenv = PVE::RPCEnvironment::get();
525
526 my $authuser = $rpcenv->get_user();
527
528 my $vmid = $param->{vmid};
529
530 # test if container exists
673cf209 531 my $conf = PVE::LXC::load_config($vmid);
f76a2828 532
611fe3aa
DM
533 my $storage_cfg = cfs_read_file("storage.cfg");
534
9d87e069 535 die "unable to remove CT $vmid - used in HA resources\n"
b6e0b774
AG
536 if PVE::HA::Config::vm_is_ha_managed($vmid);
537
611fe3aa 538 my $code = sub {
673cf209
DM
539 # reload config after lock
540 $conf = PVE::LXC::load_config($vmid);
541 PVE::LXC::check_lock($conf);
542
27916659 543 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
be5fc936 544 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 545 PVE::Firewall::remove_vmfw_conf($vmid);
f76a2828
DM
546 };
547
611fe3aa
DM
548 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
549
f76a2828
DM
550 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
551 }});
552
fff3a342
DM
553my $sslcert;
554
555__PACKAGE__->register_method ({
5b4657d0
DM
556 name => 'vncproxy',
557 path => '{vmid}/vncproxy',
fff3a342
DM
558 method => 'POST',
559 protected => 1,
560 permissions => {
561 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
562 },
563 description => "Creates a TCP VNC proxy connections.",
564 parameters => {
565 additionalProperties => 0,
566 properties => {
567 node => get_standard_option('pve-node'),
568 vmid => get_standard_option('pve-vmid'),
569 websocket => {
570 optional => 1,
571 type => 'boolean',
572 description => "use websocket instead of standard VNC.",
573 },
574 },
575 },
5b4657d0 576 returns => {
fff3a342
DM
577 additionalProperties => 0,
578 properties => {
579 user => { type => 'string' },
580 ticket => { type => 'string' },
581 cert => { type => 'string' },
582 port => { type => 'integer' },
583 upid => { type => 'string' },
584 },
585 },
586 code => sub {
587 my ($param) = @_;
588
589 my $rpcenv = PVE::RPCEnvironment::get();
590
591 my $authuser = $rpcenv->get_user();
592
593 my $vmid = $param->{vmid};
594 my $node = $param->{node};
595
596 my $authpath = "/vms/$vmid";
597
598 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
599
600 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
601 if !$sslcert;
602
ec2c57eb 603 my ($remip, $family);
5b4657d0 604
fff3a342 605 if ($node ne PVE::INotify::nodename()) {
85ae6211 606 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
607 } else {
608 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
609 }
610
ec2c57eb
WB
611 my $port = PVE::Tools::next_vnc_port($family);
612
fff3a342
DM
613 # NOTE: vncterm VNC traffic is already TLS encrypted,
614 # so we select the fastest chipher here (or 'none'?)
5b4657d0 615 my $remcmd = $remip ?
fff3a342
DM
616 ['/usr/bin/ssh', '-t', $remip] : [];
617
d18499cf 618 my $conf = PVE::LXC::load_config($vmid, $node);
aca816ad
DM
619 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
620
5b4657d0
DM
621 my $shcmd = [ '/usr/bin/dtach', '-A',
622 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 623 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
624
625 my $realcmd = sub {
626 my $upid = shift;
627
5b4657d0 628 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 629
5b4657d0 630 my $timeout = 10;
fff3a342
DM
631
632 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 633 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
634 '-perm', 'VM.Console'];
635
636 if ($param->{websocket}) {
5b4657d0 637 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
638 push @$cmd, '-notls', '-listen', 'localhost';
639 }
640
641 push @$cmd, '-c', @$remcmd, @$shcmd;
642
643 run_command($cmd);
644
645 return;
646 };
647
648 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
649
650 PVE::Tools::wait_for_vnc_port($port);
651
652 return {
653 user => $authuser,
654 ticket => $ticket,
5b4657d0
DM
655 port => $port,
656 upid => $upid,
657 cert => $sslcert,
fff3a342
DM
658 };
659 }});
660
661__PACKAGE__->register_method({
662 name => 'vncwebsocket',
663 path => '{vmid}/vncwebsocket',
664 method => 'GET',
5b4657d0 665 permissions => {
fff3a342
DM
666 description => "You also need to pass a valid ticket (vncticket).",
667 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
668 },
669 description => "Opens a weksocket for VNC traffic.",
670 parameters => {
671 additionalProperties => 0,
672 properties => {
673 node => get_standard_option('pve-node'),
674 vmid => get_standard_option('pve-vmid'),
675 vncticket => {
676 description => "Ticket from previous call to vncproxy.",
677 type => 'string',
678 maxLength => 512,
679 },
680 port => {
681 description => "Port number returned by previous vncproxy call.",
682 type => 'integer',
683 minimum => 5900,
684 maximum => 5999,
685 },
686 },
687 },
688 returns => {
689 type => "object",
690 properties => {
691 port => { type => 'string' },
692 },
693 },
694 code => sub {
695 my ($param) = @_;
696
697 my $rpcenv = PVE::RPCEnvironment::get();
698
699 my $authuser = $rpcenv->get_user();
700
701 my $authpath = "/vms/$param->{vmid}";
702
703 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
704
705 my $port = $param->{port};
5b4657d0 706
fff3a342
DM
707 return { port => $port };
708 }});
709
710__PACKAGE__->register_method ({
5b4657d0
DM
711 name => 'spiceproxy',
712 path => '{vmid}/spiceproxy',
fff3a342
DM
713 method => 'POST',
714 protected => 1,
715 proxyto => 'node',
716 permissions => {
717 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
718 },
719 description => "Returns a SPICE configuration to connect to the CT.",
720 parameters => {
721 additionalProperties => 0,
722 properties => {
723 node => get_standard_option('pve-node'),
724 vmid => get_standard_option('pve-vmid'),
725 proxy => get_standard_option('spice-proxy', { optional => 1 }),
726 },
727 },
728 returns => get_standard_option('remote-viewer-config'),
729 code => sub {
730 my ($param) = @_;
731
732 my $vmid = $param->{vmid};
733 my $node = $param->{node};
734 my $proxy = $param->{proxy};
735
736 my $authpath = "/vms/$vmid";
737 my $permissions = 'VM.Console';
738
aca816ad 739 my $conf = PVE::LXC::load_config($vmid);
da4db334
TL
740
741 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
742
aca816ad
DM
743 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
744
5b4657d0
DM
745 my $shcmd = ['/usr/bin/dtach', '-A',
746 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 747 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
748
749 my $title = "CT $vmid";
750
751 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
752 }});
5c752bbf 753
5c752bbf
DM
754
755__PACKAGE__->register_method({
52389a07
DM
756 name => 'migrate_vm',
757 path => '{vmid}/migrate',
5c752bbf
DM
758 method => 'POST',
759 protected => 1,
760 proxyto => 'node',
52389a07 761 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 762 permissions => {
52389a07 763 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
764 },
765 parameters => {
766 additionalProperties => 0,
767 properties => {
768 node => get_standard_option('pve-node'),
769 vmid => get_standard_option('pve-vmid'),
52389a07
DM
770 target => get_standard_option('pve-node', { description => "Target node." }),
771 online => {
772 type => 'boolean',
773 description => "Use online/live migration.",
774 optional => 1,
775 },
5c752bbf
DM
776 },
777 },
778 returns => {
779 type => 'string',
52389a07 780 description => "the task ID.",
5c752bbf
DM
781 },
782 code => sub {
783 my ($param) = @_;
784
785 my $rpcenv = PVE::RPCEnvironment::get();
786
787 my $authuser = $rpcenv->get_user();
788
52389a07 789 my $target = extract_param($param, 'target');
bb1ac2de 790
52389a07
DM
791 my $localnode = PVE::INotify::nodename();
792 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 793
52389a07 794 PVE::Cluster::check_cfs_quorum();
5c752bbf 795
52389a07 796 PVE::Cluster::check_node_exists($target);
27916659 797
52389a07 798 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 799
52389a07 800 my $vmid = extract_param($param, 'vmid');
5c752bbf 801
52389a07
DM
802 # test if VM exists
803 PVE::LXC::load_config($vmid);
5c752bbf 804
52389a07
DM
805 # try to detect errors early
806 if (PVE::LXC::check_running($vmid)) {
807 die "cant migrate running container without --online\n"
808 if !$param->{online};
5c752bbf 809 }
5c752bbf
DM
810
811 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
812
813 my $hacmd = sub {
814 my $upid = shift;
815
816 my $service = "ct:$vmid";
817
52389a07 818 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 819
52389a07 820 print "Executing HA migrate for CT $vmid to node $target\n";
5c752bbf
DM
821
822 PVE::Tools::run_command($cmd);
823
824 return;
825 };
826
52389a07 827 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
828
829 } else {
830
831 my $realcmd = sub {
832 my $upid = shift;
833
52389a07
DM
834 # fixme: implement lxc container migration
835 die "lxc container migration not implemented\n";
5c752bbf
DM
836
837 return;
838 };
839
52389a07 840 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
5c752bbf
DM
841 }
842 }});
843
cc5392c8
WL
844__PACKAGE__->register_method({
845 name => 'vm_feature',
846 path => '{vmid}/feature',
847 method => 'GET',
848 proxyto => 'node',
849 protected => 1,
850 description => "Check if feature for virtual machine is available.",
851 permissions => {
852 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
853 },
854 parameters => {
855 additionalProperties => 0,
856 properties => {
857 node => get_standard_option('pve-node'),
858 vmid => get_standard_option('pve-vmid'),
859 feature => {
860 description => "Feature to check.",
861 type => 'string',
862 enum => [ 'snapshot' ],
863 },
864 snapname => get_standard_option('pve-lxc-snapshot-name', {
865 optional => 1,
866 }),
867 },
868 },
869 returns => {
870 type => "object",
871 properties => {
872 hasFeature => { type => 'boolean' },
873 #nodes => {
874 #type => 'array',
875 #items => { type => 'string' },
876 #}
877 },
878 },
879 code => sub {
880 my ($param) = @_;
881
882 my $node = extract_param($param, 'node');
883
884 my $vmid = extract_param($param, 'vmid');
885
886 my $snapname = extract_param($param, 'snapname');
887
888 my $feature = extract_param($param, 'feature');
889
890 my $conf = PVE::LXC::load_config($vmid);
891
892 if($snapname){
893 my $snap = $conf->{snapshots}->{$snapname};
894 die "snapshot '$snapname' does not exist\n" if !defined($snap);
895 $conf = $snap;
896 }
ef241384 897 my $storage_cfg = PVE::Storage::config();
cc5392c8 898 #Maybe include later
ef241384
DM
899 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
900 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
cc5392c8
WL
901
902 return {
903 hasFeature => $hasFeature,
904 #nodes => [ keys %$nodelist ],
905 };
906 }});
bb1ac2de
DM
907
908__PACKAGE__->register_method({
909 name => 'template',
910 path => '{vmid}/template',
911 method => 'POST',
912 protected => 1,
913 proxyto => 'node',
914 description => "Create a Template.",
915 permissions => {
916 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
917 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
918 },
919 parameters => {
920 additionalProperties => 0,
921 properties => {
922 node => get_standard_option('pve-node'),
923 vmid => get_standard_option('pve-vmid'),
924 },
925 },
926 returns => { type => 'null'},
927 code => sub {
928 my ($param) = @_;
929
930 my $rpcenv = PVE::RPCEnvironment::get();
931
932 my $authuser = $rpcenv->get_user();
933
934 my $node = extract_param($param, 'node');
935
936 my $vmid = extract_param($param, 'vmid');
937
938 my $updatefn = sub {
939
940 my $conf = PVE::LXC::load_config($vmid);
941 PVE::LXC::check_lock($conf);
942
943 die "unable to create template, because CT contains snapshots\n"
944 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
945
946 die "you can't convert a template to a template\n"
947 if PVE::LXC::is_template($conf);
948
949 die "you can't convert a CT to template if the CT is running\n"
950 if PVE::LXC::check_running($vmid);
951
952 my $realcmd = sub {
953 PVE::LXC::template_create($vmid, $conf);
954 };
955
956 $conf->{template} = 1;
957
958 PVE::LXC::write_config($vmid, $conf);
959 # and remove lxc config
960 PVE::LXC::update_lxc_config(undef, $vmid, $conf);
961
962 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
963 };
964
965 PVE::LXC::lock_container($vmid, undef, $updatefn);
966
967 return undef;
968 }});
969
f76a2828 9701;