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