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