]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
bump version to 0.9-14
[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
535 my $code = sub {
673cf209
DM
536 # reload config after lock
537 $conf = PVE::LXC::load_config($vmid);
538 PVE::LXC::check_lock($conf);
539
27916659 540 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
be5fc936 541 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 542 PVE::Firewall::remove_vmfw_conf($vmid);
f76a2828
DM
543 };
544
611fe3aa
DM
545 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
546
f76a2828
DM
547 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
548 }});
549
fff3a342
DM
550my $sslcert;
551
552__PACKAGE__->register_method ({
5b4657d0
DM
553 name => 'vncproxy',
554 path => '{vmid}/vncproxy',
fff3a342
DM
555 method => 'POST',
556 protected => 1,
557 permissions => {
558 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
559 },
560 description => "Creates a TCP VNC proxy connections.",
561 parameters => {
562 additionalProperties => 0,
563 properties => {
564 node => get_standard_option('pve-node'),
565 vmid => get_standard_option('pve-vmid'),
566 websocket => {
567 optional => 1,
568 type => 'boolean',
569 description => "use websocket instead of standard VNC.",
570 },
571 },
572 },
5b4657d0 573 returns => {
fff3a342
DM
574 additionalProperties => 0,
575 properties => {
576 user => { type => 'string' },
577 ticket => { type => 'string' },
578 cert => { type => 'string' },
579 port => { type => 'integer' },
580 upid => { type => 'string' },
581 },
582 },
583 code => sub {
584 my ($param) = @_;
585
586 my $rpcenv = PVE::RPCEnvironment::get();
587
588 my $authuser = $rpcenv->get_user();
589
590 my $vmid = $param->{vmid};
591 my $node = $param->{node};
592
593 my $authpath = "/vms/$vmid";
594
595 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
596
597 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
598 if !$sslcert;
599
ec2c57eb 600 my ($remip, $family);
5b4657d0 601
fff3a342 602 if ($node ne PVE::INotify::nodename()) {
85ae6211 603 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
604 } else {
605 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
606 }
607
ec2c57eb
WB
608 my $port = PVE::Tools::next_vnc_port($family);
609
fff3a342
DM
610 # NOTE: vncterm VNC traffic is already TLS encrypted,
611 # so we select the fastest chipher here (or 'none'?)
5b4657d0 612 my $remcmd = $remip ?
fff3a342
DM
613 ['/usr/bin/ssh', '-t', $remip] : [];
614
d18499cf 615 my $conf = PVE::LXC::load_config($vmid, $node);
aca816ad
DM
616 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
617
5b4657d0
DM
618 my $shcmd = [ '/usr/bin/dtach', '-A',
619 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 620 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
621
622 my $realcmd = sub {
623 my $upid = shift;
624
5b4657d0 625 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 626
5b4657d0 627 my $timeout = 10;
fff3a342
DM
628
629 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 630 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
631 '-perm', 'VM.Console'];
632
633 if ($param->{websocket}) {
5b4657d0 634 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
635 push @$cmd, '-notls', '-listen', 'localhost';
636 }
637
638 push @$cmd, '-c', @$remcmd, @$shcmd;
639
640 run_command($cmd);
641
642 return;
643 };
644
645 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
646
647 PVE::Tools::wait_for_vnc_port($port);
648
649 return {
650 user => $authuser,
651 ticket => $ticket,
5b4657d0
DM
652 port => $port,
653 upid => $upid,
654 cert => $sslcert,
fff3a342
DM
655 };
656 }});
657
658__PACKAGE__->register_method({
659 name => 'vncwebsocket',
660 path => '{vmid}/vncwebsocket',
661 method => 'GET',
5b4657d0 662 permissions => {
fff3a342
DM
663 description => "You also need to pass a valid ticket (vncticket).",
664 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
665 },
666 description => "Opens a weksocket for VNC traffic.",
667 parameters => {
668 additionalProperties => 0,
669 properties => {
670 node => get_standard_option('pve-node'),
671 vmid => get_standard_option('pve-vmid'),
672 vncticket => {
673 description => "Ticket from previous call to vncproxy.",
674 type => 'string',
675 maxLength => 512,
676 },
677 port => {
678 description => "Port number returned by previous vncproxy call.",
679 type => 'integer',
680 minimum => 5900,
681 maximum => 5999,
682 },
683 },
684 },
685 returns => {
686 type => "object",
687 properties => {
688 port => { type => 'string' },
689 },
690 },
691 code => sub {
692 my ($param) = @_;
693
694 my $rpcenv = PVE::RPCEnvironment::get();
695
696 my $authuser = $rpcenv->get_user();
697
698 my $authpath = "/vms/$param->{vmid}";
699
700 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
701
702 my $port = $param->{port};
5b4657d0 703
fff3a342
DM
704 return { port => $port };
705 }});
706
707__PACKAGE__->register_method ({
5b4657d0
DM
708 name => 'spiceproxy',
709 path => '{vmid}/spiceproxy',
fff3a342
DM
710 method => 'POST',
711 protected => 1,
712 proxyto => 'node',
713 permissions => {
714 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
715 },
716 description => "Returns a SPICE configuration to connect to the CT.",
717 parameters => {
718 additionalProperties => 0,
719 properties => {
720 node => get_standard_option('pve-node'),
721 vmid => get_standard_option('pve-vmid'),
722 proxy => get_standard_option('spice-proxy', { optional => 1 }),
723 },
724 },
725 returns => get_standard_option('remote-viewer-config'),
726 code => sub {
727 my ($param) = @_;
728
729 my $vmid = $param->{vmid};
730 my $node = $param->{node};
731 my $proxy = $param->{proxy};
732
733 my $authpath = "/vms/$vmid";
734 my $permissions = 'VM.Console';
735
aca816ad 736 my $conf = PVE::LXC::load_config($vmid);
da4db334
TL
737
738 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
739
aca816ad
DM
740 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
741
5b4657d0
DM
742 my $shcmd = ['/usr/bin/dtach', '-A',
743 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 744 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
745
746 my $title = "CT $vmid";
747
748 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
749 }});
5c752bbf 750
5c752bbf
DM
751
752__PACKAGE__->register_method({
52389a07
DM
753 name => 'migrate_vm',
754 path => '{vmid}/migrate',
5c752bbf
DM
755 method => 'POST',
756 protected => 1,
757 proxyto => 'node',
52389a07 758 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 759 permissions => {
52389a07 760 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
761 },
762 parameters => {
763 additionalProperties => 0,
764 properties => {
765 node => get_standard_option('pve-node'),
766 vmid => get_standard_option('pve-vmid'),
52389a07
DM
767 target => get_standard_option('pve-node', { description => "Target node." }),
768 online => {
769 type => 'boolean',
770 description => "Use online/live migration.",
771 optional => 1,
772 },
5c752bbf
DM
773 },
774 },
775 returns => {
776 type => 'string',
52389a07 777 description => "the task ID.",
5c752bbf
DM
778 },
779 code => sub {
780 my ($param) = @_;
781
782 my $rpcenv = PVE::RPCEnvironment::get();
783
784 my $authuser = $rpcenv->get_user();
785
52389a07 786 my $target = extract_param($param, 'target');
bb1ac2de 787
52389a07
DM
788 my $localnode = PVE::INotify::nodename();
789 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 790
52389a07 791 PVE::Cluster::check_cfs_quorum();
5c752bbf 792
52389a07 793 PVE::Cluster::check_node_exists($target);
27916659 794
52389a07 795 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 796
52389a07 797 my $vmid = extract_param($param, 'vmid');
5c752bbf 798
52389a07
DM
799 # test if VM exists
800 PVE::LXC::load_config($vmid);
5c752bbf 801
52389a07
DM
802 # try to detect errors early
803 if (PVE::LXC::check_running($vmid)) {
804 die "cant migrate running container without --online\n"
805 if !$param->{online};
5c752bbf 806 }
5c752bbf
DM
807
808 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
809
810 my $hacmd = sub {
811 my $upid = shift;
812
813 my $service = "ct:$vmid";
814
52389a07 815 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 816
52389a07 817 print "Executing HA migrate for CT $vmid to node $target\n";
5c752bbf
DM
818
819 PVE::Tools::run_command($cmd);
820
821 return;
822 };
823
52389a07 824 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
825
826 } else {
827
828 my $realcmd = sub {
829 my $upid = shift;
830
52389a07
DM
831 # fixme: implement lxc container migration
832 die "lxc container migration not implemented\n";
5c752bbf
DM
833
834 return;
835 };
836
52389a07 837 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
5c752bbf
DM
838 }
839 }});
840
cc5392c8
WL
841__PACKAGE__->register_method({
842 name => 'vm_feature',
843 path => '{vmid}/feature',
844 method => 'GET',
845 proxyto => 'node',
846 protected => 1,
847 description => "Check if feature for virtual machine is available.",
848 permissions => {
849 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
850 },
851 parameters => {
852 additionalProperties => 0,
853 properties => {
854 node => get_standard_option('pve-node'),
855 vmid => get_standard_option('pve-vmid'),
856 feature => {
857 description => "Feature to check.",
858 type => 'string',
859 enum => [ 'snapshot' ],
860 },
861 snapname => get_standard_option('pve-lxc-snapshot-name', {
862 optional => 1,
863 }),
864 },
865 },
866 returns => {
867 type => "object",
868 properties => {
869 hasFeature => { type => 'boolean' },
870 #nodes => {
871 #type => 'array',
872 #items => { type => 'string' },
873 #}
874 },
875 },
876 code => sub {
877 my ($param) = @_;
878
879 my $node = extract_param($param, 'node');
880
881 my $vmid = extract_param($param, 'vmid');
882
883 my $snapname = extract_param($param, 'snapname');
884
885 my $feature = extract_param($param, 'feature');
886
887 my $conf = PVE::LXC::load_config($vmid);
888
889 if($snapname){
890 my $snap = $conf->{snapshots}->{$snapname};
891 die "snapshot '$snapname' does not exist\n" if !defined($snap);
892 $conf = $snap;
893 }
ef241384 894 my $storage_cfg = PVE::Storage::config();
cc5392c8 895 #Maybe include later
ef241384
DM
896 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
897 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
cc5392c8
WL
898
899 return {
900 hasFeature => $hasFeature,
901 #nodes => [ keys %$nodelist ],
902 };
903 }});
bb1ac2de
DM
904
905__PACKAGE__->register_method({
906 name => 'template',
907 path => '{vmid}/template',
908 method => 'POST',
909 protected => 1,
910 proxyto => 'node',
911 description => "Create a Template.",
912 permissions => {
913 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
914 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
915 },
916 parameters => {
917 additionalProperties => 0,
918 properties => {
919 node => get_standard_option('pve-node'),
920 vmid => get_standard_option('pve-vmid'),
921 },
922 },
923 returns => { type => 'null'},
924 code => sub {
925 my ($param) = @_;
926
927 my $rpcenv = PVE::RPCEnvironment::get();
928
929 my $authuser = $rpcenv->get_user();
930
931 my $node = extract_param($param, 'node');
932
933 my $vmid = extract_param($param, 'vmid');
934
935 my $updatefn = sub {
936
937 my $conf = PVE::LXC::load_config($vmid);
938 PVE::LXC::check_lock($conf);
939
940 die "unable to create template, because CT contains snapshots\n"
941 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
942
943 die "you can't convert a template to a template\n"
944 if PVE::LXC::is_template($conf);
945
946 die "you can't convert a CT to template if the CT is running\n"
947 if PVE::LXC::check_running($vmid);
948
949 my $realcmd = sub {
950 PVE::LXC::template_create($vmid, $conf);
951 };
952
953 $conf->{template} = 1;
954
955 PVE::LXC::write_config($vmid, $conf);
956 # and remove lxc config
957 PVE::LXC::update_lxc_config(undef, $vmid, $conf);
958
959 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
960 };
961
962 PVE::LXC::lock_container($vmid, undef, $updatefn);
963
964 return undef;
965 }});
966
f76a2828 9671;