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