]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
04b9591fd37b111ce3f5951c5f1d212553e13cea
[pve-container.git] / src / PVE / API2 / LXC.pm
1 package PVE::API2::LXC;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param run_command);
8 use PVE::Exception qw(raise raise_param_exc);
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::AccessControl;
12 use PVE::Firewall;
13 use PVE::Storage;
14 use PVE::RESTHandler;
15 use PVE::RPCEnvironment;
16 use PVE::LXC;
17 use PVE::LXC::Create;
18 use PVE::API2::LXC::Config;
19 use PVE::API2::LXC::Status;
20 use PVE::API2::LXC::Snapshot;
21 use PVE::HA::Config;
22 use PVE::JSONSchema qw(get_standard_option);
23 use base qw(PVE::RESTHandler);
24
25 use Data::Dumper; # fixme: remove
26
27 __PACKAGE__->register_method ({
28 subclass => "PVE::API2::LXC::Config",
29 path => '{vmid}/config',
30 });
31
32 __PACKAGE__->register_method ({
33 subclass => "PVE::API2::LXC::Status",
34 path => '{vmid}/status',
35 });
36
37 __PACKAGE__->register_method ({
38 subclass => "PVE::API2::LXC::Snapshot",
39 path => '{vmid}/snapshot',
40 });
41
42 __PACKAGE__->register_method ({
43 subclass => "PVE::API2::Firewall::CT",
44 path => '{vmid}/firewall',
45 });
46
47 my $create_disks = sub {
48 my ($storecfg, $vmid, $settings, $conf, $default_storage) = @_;
49
50 my $vollist = [];
51
52 PVE::LXC::foreach_mountpoint($settings, sub {
53 my ($ms, $mountpoint) = @_;
54
55 my $volid = $mountpoint->{volume};
56 my $mp = $mountpoint->{mp};
57
58 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
59
60 return if !$storage;
61
62 if ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
63 my ($storeid, $size) = ($2 || $default_storage, $3);
64
65 $size = int($size*1024) * 1024;
66
67 eval {
68 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
69 # fixme: use better naming ct-$vmid-disk-X.raw?
70
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') {
80
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 });
97 }
98 });
99 # free allocated images on error
100 if (my $err = $@) {
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;
107 }
108 return $vollist;
109 };
110
111 __PACKAGE__->register_method({
112 name => 'vmlist',
113 path => '',
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;
154
155 }});
156
157 __PACKAGE__->register_method({
158 name => 'create_vm',
159 path => '',
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,
172 properties => PVE::LXC::json_config_properties({
173 node => get_standard_option('pve-node'),
174 vmid => get_standard_option('pve-vmid'),
175 ostemplate => {
176 description => "The OS template or backup file.",
177 type => 'string',
178 maxLength => 255,
179 },
180 password => {
181 optional => 1,
182 type => 'string',
183 description => "Sets root password inside container.",
184 minLength => 5,
185 },
186 storage => get_standard_option('pve-storage-id', {
187 description => "Default Storage.",
188 default => 'local',
189 optional => 1,
190 }),
191 force => {
192 optional => 1,
193 type => 'boolean',
194 description => "Allow to overwrite existing container.",
195 },
196 restore => {
197 optional => 1,
198 type => 'boolean',
199 description => "Mark this as restore task.",
200 },
201 pool => {
202 optional => 1,
203 type => 'string', format => 'pve-poolid',
204 description => "Add the VM to the specified pool.",
205 },
206 }),
207 },
208 returns => {
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
228 if ($restore) {
229 # fixme: limit allowed parameters
230
231 }
232
233 my $force = extract_param($param, 'force');
234
235 if (!($same_container_exists && $restore && $force)) {
236 PVE::Cluster::check_vmid_unused($vmid);
237 }
238
239 my $password = extract_param($param, 'password');
240
241 my $storage = extract_param($param, 'storage') // 'local';
242
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"})
248 if !($scfg->{content}->{images} || $scfg->{content}->{rootdir});
249
250 my $pool = extract_param($param, 'pool');
251
252 if (defined($pool)) {
253 $rpcenv->check_pool_exist($pool);
254 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
255 }
256
257 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
258
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
270 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
271
272 PVE::Storage::activate_storage($storage_cfg, $storage);
273
274 my $ostemplate = extract_param($param, 'ostemplate');
275
276 my $archive;
277
278 $param->{rootfs} = $storage if !$param->{rootfs};
279
280 if ($ostemplate eq '-') {
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 = '-';
286 } else {
287 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
288 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
289 }
290
291 my $conf = {};
292
293 PVE::LXC::update_pct_config($vmid, $conf, 0, $param);
294
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 };
303
304 my $code = sub {
305 &$check_vmid_usage(); # final check after locking
306
307 PVE::Cluster::check_cfs_quorum();
308 my $vollist = [];
309
310 eval {
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);
333 # set some defaults
334 $conf->{hostname} ||= "CT$vmid";
335 $conf->{memory} ||= 512;
336 $conf->{swap} //= 512;
337 PVE::LXC::create_config($vmid, $conf);
338 };
339 if (my $err = $@) {
340 foreach my $volid (@$vollist) {
341 eval { PVE::Storage::vdisk_free($storage_cfg, $volid); };
342 warn $@ if $@;
343 }
344
345 PVE::LXC::destroy_config($vmid);
346 die $err;
347 }
348 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
349 };
350
351 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
352
353 &$check_vmid_usage(); # first check before locking
354
355 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
356 $vmid, $authuser, $realcmd);
357
358 }});
359
360 __PACKAGE__->register_method({
361 name => 'vmdiridx',
362 path => '{vmid}',
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
390 my $conf = PVE::LXC::load_config($param->{vmid});
391
392 my $res = [
393 { subdir => 'config' },
394 { subdir => 'status' },
395 { subdir => 'vncproxy' },
396 { subdir => 'vncwebsocket' },
397 { subdir => 'spiceproxy' },
398 { subdir => 'migrate' },
399 # { subdir => 'initlog' },
400 { subdir => 'rrd' },
401 { subdir => 'rrddata' },
402 { subdir => 'firewall' },
403 { subdir => 'snapshot' },
404 ];
405
406 return $res;
407 }});
408
409 __PACKAGE__->register_method({
410 name => 'rrd',
411 path => '{vmid}/rrd',
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(
450 "pve2-vm/$param->{vmid}", $param->{timeframe},
451 $param->{ds}, $param->{cf});
452
453 }});
454
455 __PACKAGE__->register_method({
456 name => 'rrddata',
457 path => '{vmid}/rrddata',
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
496 __PACKAGE__->register_method({
497 name => 'destroy_vm',
498 path => '{vmid}',
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 },
513 returns => {
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
526 my $conf = PVE::LXC::load_config($vmid);
527
528 my $storage_cfg = cfs_read_file("storage.cfg");
529
530 my $code = sub {
531 # reload config after lock
532 $conf = PVE::LXC::load_config($vmid);
533 PVE::LXC::check_lock($conf);
534
535 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
536 PVE::AccessControl::remove_vm_access($vmid);
537 PVE::Firewall::remove_vmfw_conf($vmid);
538 };
539
540 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
541
542 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
543 }});
544
545 my $sslcert;
546
547 __PACKAGE__->register_method ({
548 name => 'vncproxy',
549 path => '{vmid}/vncproxy',
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 },
568 returns => {
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
595 my ($remip, $family);
596
597 if ($node ne PVE::INotify::nodename()) {
598 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
599 } else {
600 $family = PVE::Tools::get_host_address_family($node);
601 }
602
603 my $port = PVE::Tools::next_vnc_port($family);
604
605 # NOTE: vncterm VNC traffic is already TLS encrypted,
606 # so we select the fastest chipher here (or 'none'?)
607 my $remcmd = $remip ?
608 ['/usr/bin/ssh', '-t', $remip] : [];
609
610 my $conf = PVE::LXC::load_config($vmid, $node);
611 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
612
613 my $shcmd = [ '/usr/bin/dtach', '-A',
614 "/var/run/dtach/vzctlconsole$vmid",
615 '-r', 'winch', '-z', @$concmd];
616
617 my $realcmd = sub {
618 my $upid = shift;
619
620 syslog ('info', "starting lxc vnc proxy $upid\n");
621
622 my $timeout = 10;
623
624 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
625 '-timeout', $timeout, '-authpath', $authpath,
626 '-perm', 'VM.Console'];
627
628 if ($param->{websocket}) {
629 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
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,
647 port => $port,
648 upid => $upid,
649 cert => $sslcert,
650 };
651 }});
652
653 __PACKAGE__->register_method({
654 name => 'vncwebsocket',
655 path => '{vmid}/vncwebsocket',
656 method => 'GET',
657 permissions => {
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};
698
699 return { port => $port };
700 }});
701
702 __PACKAGE__->register_method ({
703 name => 'spiceproxy',
704 path => '{vmid}/spiceproxy',
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
731 my $conf = PVE::LXC::load_config($vmid);
732 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
733
734 my $shcmd = ['/usr/bin/dtach', '-A',
735 "/var/run/dtach/vzctlconsole$vmid",
736 '-r', 'winch', '-z', @$concmd];
737
738 my $title = "CT $vmid";
739
740 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
741 }});
742
743
744 __PACKAGE__->register_method({
745 name => 'migrate_vm',
746 path => '{vmid}/migrate',
747 method => 'POST',
748 protected => 1,
749 proxyto => 'node',
750 description => "Migrate the container to another node. Creates a new migration task.",
751 permissions => {
752 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
753 },
754 parameters => {
755 additionalProperties => 0,
756 properties => {
757 node => get_standard_option('pve-node'),
758 vmid => get_standard_option('pve-vmid'),
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 },
765 },
766 },
767 returns => {
768 type => 'string',
769 description => "the task ID.",
770 },
771 code => sub {
772 my ($param) = @_;
773
774 my $rpcenv = PVE::RPCEnvironment::get();
775
776 my $authuser = $rpcenv->get_user();
777
778 my $target = extract_param($param, 'target');
779
780 my $localnode = PVE::INotify::nodename();
781 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
782
783 PVE::Cluster::check_cfs_quorum();
784
785 PVE::Cluster::check_node_exists($target);
786
787 my $targetip = PVE::Cluster::remote_node_ip($target);
788
789 my $vmid = extract_param($param, 'vmid');
790
791 # test if VM exists
792 PVE::LXC::load_config($vmid);
793
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};
798 }
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
807 my $cmd = ['ha-manager', 'migrate', $service, $target];
808
809 print "Executing HA migrate for CT $vmid to node $target\n";
810
811 PVE::Tools::run_command($cmd);
812
813 return;
814 };
815
816 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
817
818 } else {
819
820 my $realcmd = sub {
821 my $upid = shift;
822
823 # fixme: implement lxc container migration
824 die "lxc container migration not implemented\n";
825
826 return;
827 };
828
829 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
830 }
831 }});
832
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 }
886 my $storage_cfg = PVE::Storage::config();
887 #Maybe include later
888 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
889 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
890
891 return {
892 hasFeature => $hasFeature,
893 #nodes => [ keys %$nodelist ],
894 };
895 }});
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
959 1;