]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
add offline migration code
[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
DM
182 node => get_standard_option('pve-node'),
183 vmid => get_standard_option('pve-vmid'),
184 ostemplate => {
185 description => "The OS template or backup file.",
5c752bbf 186 type => 'string',
9c2d4ce9
DM
187 maxLength => 255,
188 },
5c752bbf
DM
189 password => {
190 optional => 1,
9c2d4ce9
DM
191 type => 'string',
192 description => "Sets root password inside container.",
168d6b07 193 minLength => 5,
9c2d4ce9
DM
194 },
195 storage => get_standard_option('pve-storage-id', {
eb35f9c0 196 description => "Default Storage.",
9c2d4ce9
DM
197 default => 'local',
198 optional => 1,
199 }),
200 force => {
5c752bbf 201 optional => 1,
9c2d4ce9
DM
202 type => 'boolean',
203 description => "Allow to overwrite existing container.",
204 },
205 restore => {
5c752bbf 206 optional => 1,
9c2d4ce9
DM
207 type => 'boolean',
208 description => "Mark this as restore task.",
209 },
5c752bbf 210 pool => {
9c2d4ce9
DM
211 optional => 1,
212 type => 'string', format => 'pve-poolid',
213 description => "Add the VM to the specified pool.",
214 },
215 }),
216 },
5c752bbf 217 returns => {
9c2d4ce9
DM
218 type => 'string',
219 },
220 code => sub {
221 my ($param) = @_;
222
223 my $rpcenv = PVE::RPCEnvironment::get();
224
225 my $authuser = $rpcenv->get_user();
226
227 my $node = extract_param($param, 'node');
228
229 my $vmid = extract_param($param, 'vmid');
230
231 my $basecfg_fn = PVE::LXC::config_file($vmid);
232
233 my $same_container_exists = -f $basecfg_fn;
234
235 my $restore = extract_param($param, 'restore');
236
148d1cb4
DM
237 if ($restore) {
238 # fixme: limit allowed parameters
239
240 }
241
9c2d4ce9
DM
242 my $force = extract_param($param, 'force');
243
244 if (!($same_container_exists && $restore && $force)) {
245 PVE::Cluster::check_vmid_unused($vmid);
246 }
5c752bbf 247
9c2d4ce9
DM
248 my $password = extract_param($param, 'password');
249
27916659
DM
250 my $storage = extract_param($param, 'storage') // 'local';
251
9c2d4ce9
DM
252 my $storage_cfg = cfs_read_file("storage.cfg");
253
254 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
255
256 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
644f2f8f 257 if !($scfg->{content}->{images} || $scfg->{content}->{rootdir});
9c2d4ce9 258
27916659
DM
259 my $pool = extract_param($param, 'pool');
260
9c2d4ce9
DM
261 if (defined($pool)) {
262 $rpcenv->check_pool_exist($pool);
263 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
5c752bbf 264 }
9c2d4ce9 265
27916659
DM
266 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
267
9c2d4ce9
DM
268 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
269 # OK
270 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
271 # OK
272 } elsif ($restore && $force && $same_container_exists &&
273 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
274 # OK: user has VM.Backup permissions, and want to restore an existing VM
275 } else {
276 raise_perm_exc();
277 }
278
52389a07 279 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
9c2d4ce9
DM
280
281 PVE::Storage::activate_storage($storage_cfg, $storage);
282
283 my $ostemplate = extract_param($param, 'ostemplate');
5c752bbf 284
9c2d4ce9
DM
285 my $archive;
286
287 if ($ostemplate eq '-') {
148d1cb4
DM
288 die "pipe requires cli environment\n"
289 if $rpcenv->{type} ne 'cli';
290 die "pipe can only be used with restore tasks\n"
291 if !$restore;
292 $archive = '-';
b51a98d4 293 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
9c2d4ce9
DM
294 } else {
295 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
296 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
297 }
298
9c2d4ce9 299 my $conf = {};
5b4657d0 300
b51a98d4
DM
301 my $no_disk_param = {};
302 foreach my $opt (keys %$param) {
78ccc99b
DM
303 my $value = $param->{$opt};
304 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
305 # allow to use simple numbers (add default storage in that case)
306 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
307 } else {
308 $no_disk_param->{$opt} = $value;
309 }
b51a98d4
DM
310 }
311 PVE::LXC::update_pct_config($vmid, $conf, 0, $no_disk_param);
9c2d4ce9 312
148d1cb4
DM
313 my $check_vmid_usage = sub {
314 if ($force) {
5c45496e 315 die "can't overwrite running container\n"
148d1cb4
DM
316 if PVE::LXC::check_running($vmid);
317 } else {
318 PVE::Cluster::check_vmid_unused($vmid);
319 }
320 };
f507c3a7 321
5b4657d0 322 my $code = sub {
148d1cb4 323 &$check_vmid_usage(); # final check after locking
87273b2b 324
148d1cb4 325 PVE::Cluster::check_cfs_quorum();
eb35f9c0 326 my $vollist = [];
27916659
DM
327
328 eval {
b51a98d4
DM
329 if (!defined($param->{rootfs})) {
330 if ($restore) {
331 my (undef, $disksize) = PVE::LXC::Create::recover_config($archive);
332 die "unable to detect disk size - please specify rootfs (size)\n"
333 if !$disksize;
78ccc99b 334 $param->{rootfs} = "$storage:$disksize";
b51a98d4 335 } else {
78ccc99b 336 $param->{rootfs} = "$storage:4"; # defaults to 4GB
b51a98d4
DM
337 }
338 }
339
78ccc99b 340 $vollist = &$create_disks($storage_cfg, $vmid, $param, $conf);
eb35f9c0
AD
341
342 PVE::LXC::Create::create_rootfs($storage_cfg, $vmid, $conf, $archive, $password, $restore);
27916659
DM
343 # set some defaults
344 $conf->{hostname} ||= "CT$vmid";
345 $conf->{memory} ||= 512;
346 $conf->{swap} //= 512;
27916659
DM
347 PVE::LXC::create_config($vmid, $conf);
348 };
349 if (my $err = $@) {
1e6c8d5b 350 &$destroy_disks($storage_cfg, $vollist);
27916659
DM
351 PVE::LXC::destroy_config($vmid);
352 die $err;
6d098bf4 353 }
87273b2b 354 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
9c2d4ce9 355 };
5c752bbf 356
9c2d4ce9
DM
357 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
358
148d1cb4
DM
359 &$check_vmid_usage(); # first check before locking
360
361 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
9c2d4ce9 362 $vmid, $authuser, $realcmd);
5c752bbf 363
9c2d4ce9
DM
364 }});
365
f76a2828
DM
366__PACKAGE__->register_method({
367 name => 'vmdiridx',
5c752bbf 368 path => '{vmid}',
f76a2828
DM
369 method => 'GET',
370 proxyto => 'node',
371 description => "Directory index",
372 permissions => {
373 user => 'all',
374 },
375 parameters => {
376 additionalProperties => 0,
377 properties => {
378 node => get_standard_option('pve-node'),
379 vmid => get_standard_option('pve-vmid'),
380 },
381 },
382 returns => {
383 type => 'array',
384 items => {
385 type => "object",
386 properties => {
387 subdir => { type => 'string' },
388 },
389 },
390 links => [ { rel => 'child', href => "{subdir}" } ],
391 },
392 code => sub {
393 my ($param) = @_;
394
395 # test if VM exists
e901d418 396 my $conf = PVE::LXC::load_config($param->{vmid});
f76a2828
DM
397
398 my $res = [
399 { subdir => 'config' },
fff3a342
DM
400 { subdir => 'status' },
401 { subdir => 'vncproxy' },
402 { subdir => 'vncwebsocket' },
403 { subdir => 'spiceproxy' },
404 { subdir => 'migrate' },
f76a2828
DM
405# { subdir => 'initlog' },
406 { subdir => 'rrd' },
407 { subdir => 'rrddata' },
408 { subdir => 'firewall' },
cc5392c8 409 { subdir => 'snapshot' },
f76a2828 410 ];
5c752bbf 411
f76a2828
DM
412 return $res;
413 }});
414
415__PACKAGE__->register_method({
5c752bbf
DM
416 name => 'rrd',
417 path => '{vmid}/rrd',
f76a2828
DM
418 method => 'GET',
419 protected => 1, # fixme: can we avoid that?
420 permissions => {
421 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
422 },
423 description => "Read VM RRD statistics (returns PNG)",
424 parameters => {
425 additionalProperties => 0,
426 properties => {
427 node => get_standard_option('pve-node'),
428 vmid => get_standard_option('pve-vmid'),
429 timeframe => {
430 description => "Specify the time frame you are interested in.",
431 type => 'string',
432 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
433 },
434 ds => {
435 description => "The list of datasources you want to display.",
436 type => 'string', format => 'pve-configid-list',
437 },
438 cf => {
439 description => "The RRD consolidation function",
440 type => 'string',
441 enum => [ 'AVERAGE', 'MAX' ],
442 optional => 1,
443 },
444 },
445 },
446 returns => {
447 type => "object",
448 properties => {
449 filename => { type => 'string' },
450 },
451 },
452 code => sub {
453 my ($param) = @_;
454
455 return PVE::Cluster::create_rrd_graph(
5c752bbf 456 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 457 $param->{ds}, $param->{cf});
5c752bbf 458
f76a2828
DM
459 }});
460
461__PACKAGE__->register_method({
5c752bbf
DM
462 name => 'rrddata',
463 path => '{vmid}/rrddata',
f76a2828
DM
464 method => 'GET',
465 protected => 1, # fixme: can we avoid that?
466 permissions => {
467 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
468 },
469 description => "Read VM RRD statistics",
470 parameters => {
471 additionalProperties => 0,
472 properties => {
473 node => get_standard_option('pve-node'),
474 vmid => get_standard_option('pve-vmid'),
475 timeframe => {
476 description => "Specify the time frame you are interested in.",
477 type => 'string',
478 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
479 },
480 cf => {
481 description => "The RRD consolidation function",
482 type => 'string',
483 enum => [ 'AVERAGE', 'MAX' ],
484 optional => 1,
485 },
486 },
487 },
488 returns => {
489 type => "array",
490 items => {
491 type => "object",
492 properties => {},
493 },
494 },
495 code => sub {
496 my ($param) = @_;
497
498 return PVE::Cluster::create_rrd_data(
499 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
500 }});
501
f76a2828 502__PACKAGE__->register_method({
5c752bbf
DM
503 name => 'destroy_vm',
504 path => '{vmid}',
f76a2828
DM
505 method => 'DELETE',
506 protected => 1,
507 proxyto => 'node',
508 description => "Destroy the container (also delete all uses files).",
509 permissions => {
510 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
511 },
512 parameters => {
513 additionalProperties => 0,
514 properties => {
515 node => get_standard_option('pve-node'),
516 vmid => get_standard_option('pve-vmid'),
517 },
518 },
5c752bbf 519 returns => {
f76a2828
DM
520 type => 'string',
521 },
522 code => sub {
523 my ($param) = @_;
524
525 my $rpcenv = PVE::RPCEnvironment::get();
526
527 my $authuser = $rpcenv->get_user();
528
529 my $vmid = $param->{vmid};
530
531 # test if container exists
673cf209 532 my $conf = PVE::LXC::load_config($vmid);
f76a2828 533
611fe3aa
DM
534 my $storage_cfg = cfs_read_file("storage.cfg");
535
9d87e069 536 die "unable to remove CT $vmid - used in HA resources\n"
b6e0b774
AG
537 if PVE::HA::Config::vm_is_ha_managed($vmid);
538
611fe3aa 539 my $code = sub {
673cf209
DM
540 # reload config after lock
541 $conf = PVE::LXC::load_config($vmid);
542 PVE::LXC::check_lock($conf);
543
27916659 544 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
be5fc936 545 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 546 PVE::Firewall::remove_vmfw_conf($vmid);
f76a2828
DM
547 };
548
611fe3aa
DM
549 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
550
f76a2828
DM
551 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
552 }});
553
fff3a342
DM
554my $sslcert;
555
556__PACKAGE__->register_method ({
5b4657d0
DM
557 name => 'vncproxy',
558 path => '{vmid}/vncproxy',
fff3a342
DM
559 method => 'POST',
560 protected => 1,
561 permissions => {
562 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
563 },
564 description => "Creates a TCP VNC proxy connections.",
565 parameters => {
566 additionalProperties => 0,
567 properties => {
568 node => get_standard_option('pve-node'),
569 vmid => get_standard_option('pve-vmid'),
570 websocket => {
571 optional => 1,
572 type => 'boolean',
573 description => "use websocket instead of standard VNC.",
574 },
575 },
576 },
5b4657d0 577 returns => {
fff3a342
DM
578 additionalProperties => 0,
579 properties => {
580 user => { type => 'string' },
581 ticket => { type => 'string' },
582 cert => { type => 'string' },
583 port => { type => 'integer' },
584 upid => { type => 'string' },
585 },
586 },
587 code => sub {
588 my ($param) = @_;
589
590 my $rpcenv = PVE::RPCEnvironment::get();
591
592 my $authuser = $rpcenv->get_user();
593
594 my $vmid = $param->{vmid};
595 my $node = $param->{node};
596
597 my $authpath = "/vms/$vmid";
598
599 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
600
601 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
602 if !$sslcert;
603
ec2c57eb 604 my ($remip, $family);
5b4657d0 605
fff3a342 606 if ($node ne PVE::INotify::nodename()) {
85ae6211 607 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
608 } else {
609 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
610 }
611
ec2c57eb
WB
612 my $port = PVE::Tools::next_vnc_port($family);
613
fff3a342
DM
614 # NOTE: vncterm VNC traffic is already TLS encrypted,
615 # so we select the fastest chipher here (or 'none'?)
5b4657d0 616 my $remcmd = $remip ?
fff3a342
DM
617 ['/usr/bin/ssh', '-t', $remip] : [];
618
d18499cf 619 my $conf = PVE::LXC::load_config($vmid, $node);
aca816ad
DM
620 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
621
5b4657d0
DM
622 my $shcmd = [ '/usr/bin/dtach', '-A',
623 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 624 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
625
626 my $realcmd = sub {
627 my $upid = shift;
628
5b4657d0 629 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 630
5b4657d0 631 my $timeout = 10;
fff3a342
DM
632
633 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 634 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
635 '-perm', 'VM.Console'];
636
637 if ($param->{websocket}) {
5b4657d0 638 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
639 push @$cmd, '-notls', '-listen', 'localhost';
640 }
641
642 push @$cmd, '-c', @$remcmd, @$shcmd;
643
644 run_command($cmd);
645
646 return;
647 };
648
649 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
650
651 PVE::Tools::wait_for_vnc_port($port);
652
653 return {
654 user => $authuser,
655 ticket => $ticket,
5b4657d0
DM
656 port => $port,
657 upid => $upid,
658 cert => $sslcert,
fff3a342
DM
659 };
660 }});
661
662__PACKAGE__->register_method({
663 name => 'vncwebsocket',
664 path => '{vmid}/vncwebsocket',
665 method => 'GET',
5b4657d0 666 permissions => {
fff3a342
DM
667 description => "You also need to pass a valid ticket (vncticket).",
668 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
669 },
670 description => "Opens a weksocket for VNC traffic.",
671 parameters => {
672 additionalProperties => 0,
673 properties => {
674 node => get_standard_option('pve-node'),
675 vmid => get_standard_option('pve-vmid'),
676 vncticket => {
677 description => "Ticket from previous call to vncproxy.",
678 type => 'string',
679 maxLength => 512,
680 },
681 port => {
682 description => "Port number returned by previous vncproxy call.",
683 type => 'integer',
684 minimum => 5900,
685 maximum => 5999,
686 },
687 },
688 },
689 returns => {
690 type => "object",
691 properties => {
692 port => { type => 'string' },
693 },
694 },
695 code => sub {
696 my ($param) = @_;
697
698 my $rpcenv = PVE::RPCEnvironment::get();
699
700 my $authuser = $rpcenv->get_user();
701
702 my $authpath = "/vms/$param->{vmid}";
703
704 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
705
706 my $port = $param->{port};
5b4657d0 707
fff3a342
DM
708 return { port => $port };
709 }});
710
711__PACKAGE__->register_method ({
5b4657d0
DM
712 name => 'spiceproxy',
713 path => '{vmid}/spiceproxy',
fff3a342
DM
714 method => 'POST',
715 protected => 1,
716 proxyto => 'node',
717 permissions => {
718 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
719 },
720 description => "Returns a SPICE configuration to connect to the CT.",
721 parameters => {
722 additionalProperties => 0,
723 properties => {
724 node => get_standard_option('pve-node'),
725 vmid => get_standard_option('pve-vmid'),
726 proxy => get_standard_option('spice-proxy', { optional => 1 }),
727 },
728 },
729 returns => get_standard_option('remote-viewer-config'),
730 code => sub {
731 my ($param) = @_;
732
733 my $vmid = $param->{vmid};
734 my $node = $param->{node};
735 my $proxy = $param->{proxy};
736
737 my $authpath = "/vms/$vmid";
738 my $permissions = 'VM.Console';
739
aca816ad 740 my $conf = PVE::LXC::load_config($vmid);
da4db334
TL
741
742 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
743
aca816ad
DM
744 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
745
5b4657d0
DM
746 my $shcmd = ['/usr/bin/dtach', '-A',
747 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 748 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
749
750 my $title = "CT $vmid";
751
752 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
753 }});
5c752bbf 754
5c752bbf
DM
755
756__PACKAGE__->register_method({
52389a07
DM
757 name => 'migrate_vm',
758 path => '{vmid}/migrate',
5c752bbf
DM
759 method => 'POST',
760 protected => 1,
761 proxyto => 'node',
52389a07 762 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 763 permissions => {
52389a07 764 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
765 },
766 parameters => {
767 additionalProperties => 0,
768 properties => {
769 node => get_standard_option('pve-node'),
770 vmid => get_standard_option('pve-vmid'),
52389a07
DM
771 target => get_standard_option('pve-node', { description => "Target node." }),
772 online => {
773 type => 'boolean',
774 description => "Use online/live migration.",
775 optional => 1,
776 },
5c752bbf
DM
777 },
778 },
779 returns => {
780 type => 'string',
52389a07 781 description => "the task ID.",
5c752bbf
DM
782 },
783 code => sub {
784 my ($param) = @_;
785
786 my $rpcenv = PVE::RPCEnvironment::get();
787
788 my $authuser = $rpcenv->get_user();
789
52389a07 790 my $target = extract_param($param, 'target');
bb1ac2de 791
52389a07
DM
792 my $localnode = PVE::INotify::nodename();
793 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 794
52389a07 795 PVE::Cluster::check_cfs_quorum();
5c752bbf 796
52389a07 797 PVE::Cluster::check_node_exists($target);
27916659 798
52389a07 799 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 800
52389a07 801 my $vmid = extract_param($param, 'vmid');
5c752bbf 802
52389a07
DM
803 # test if VM exists
804 PVE::LXC::load_config($vmid);
5c752bbf 805
52389a07
DM
806 # try to detect errors early
807 if (PVE::LXC::check_running($vmid)) {
5c45496e 808 die "can't migrate running container without --online\n"
52389a07 809 if !$param->{online};
5c752bbf 810 }
5c752bbf
DM
811
812 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
813
814 my $hacmd = sub {
815 my $upid = shift;
816
817 my $service = "ct:$vmid";
818
52389a07 819 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 820
52389a07 821 print "Executing HA migrate for CT $vmid to node $target\n";
5c752bbf
DM
822
823 PVE::Tools::run_command($cmd);
824
825 return;
826 };
827
52389a07 828 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
829
830 } else {
831
832 my $realcmd = sub {
833 my $upid = shift;
834
6f42807e 835 PVE::LXC::Migrate->migrate($target, $targetip, $vmid, $param);
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;