1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
18 use PVE
::API2
::LXC
::Config
;
19 use PVE
::API2
::LXC
::Status
;
20 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::JSONSchema
qw(get_standard_option);
23 use base
qw(PVE::RESTHandler);
25 use Data
::Dumper
; # fixme: remove
27 __PACKAGE__-
>register_method ({
28 subclass
=> "PVE::API2::LXC::Config",
29 path
=> '{vmid}/config',
32 __PACKAGE__-
>register_method ({
33 subclass
=> "PVE::API2::LXC::Status",
34 path
=> '{vmid}/status',
37 __PACKAGE__-
>register_method ({
38 subclass
=> "PVE::API2::LXC::Snapshot",
39 path
=> '{vmid}/snapshot',
42 __PACKAGE__-
>register_method ({
43 subclass
=> "PVE::API2::Firewall::CT",
44 path
=> '{vmid}/firewall',
47 my $create_disks = sub {
48 my ($storecfg, $vmid, $settings, $conf, $default_storage) = @_;
52 PVE
::LXC
::foreach_mountpoint
($settings, sub {
53 my ($ms, $mountpoint) = @_;
55 my $volid = $mountpoint->{volume
};
56 my $mp = $mountpoint->{mp
};
58 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
62 if ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
63 my ($storeid, $size) = ($2 || $default_storage, $3);
65 $size = int($size*1024) * 1024;
68 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
69 # fixme: use better naming ct-$vmid-disk-X.raw?
71 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
73 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
76 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
79 } elsif ($scfg->{type
} eq 'zfspool') {
81 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
83 } elsif ($scfg->{type
} eq 'drbd') {
85 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
87 } elsif ($scfg->{type
} eq 'rbd') {
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);
92 die "unable to create containers on storage type '$scfg->{type}'\n";
95 push @$vollist, $volid;
96 $conf->{$ms} = PVE
::LXC
::print_ct_mountpoint
({volume
=> $volid, size
=> $size, mp
=> $mp });
99 # free allocated images on error
101 syslog
('err', "VM $vmid creating disks failed");
102 foreach my $volid (@$vollist) {
103 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
111 __PACKAGE__-
>register_method({
115 description
=> "LXC container index (per node).",
117 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
121 protected
=> 1, # /proc files are only readable by root
123 additionalProperties
=> 0,
125 node
=> get_standard_option
('pve-node'),
134 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
139 my $rpcenv = PVE
::RPCEnvironment
::get
();
140 my $authuser = $rpcenv->get_user();
142 my $vmstatus = PVE
::LXC
::vmstatus
();
145 foreach my $vmid (keys %$vmstatus) {
146 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
148 my $data = $vmstatus->{$vmid};
149 $data->{vmid
} = $vmid;
157 __PACKAGE__-
>register_method({
161 description
=> "Create or restore a container.",
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.",
171 additionalProperties
=> 0,
172 properties
=> PVE
::LXC
::json_config_properties
({
173 node
=> get_standard_option
('pve-node'),
174 vmid
=> get_standard_option
('pve-vmid'),
176 description
=> "The OS template or backup file.",
183 description
=> "Sets root password inside container.",
186 storage
=> get_standard_option
('pve-storage-id', {
187 description
=> "Default Storage.",
194 description
=> "Allow to overwrite existing container.",
199 description
=> "Mark this as restore task.",
203 type
=> 'string', format
=> 'pve-poolid',
204 description
=> "Add the VM to the specified pool.",
214 my $rpcenv = PVE
::RPCEnvironment
::get
();
216 my $authuser = $rpcenv->get_user();
218 my $node = extract_param
($param, 'node');
220 my $vmid = extract_param
($param, 'vmid');
222 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
224 my $same_container_exists = -f
$basecfg_fn;
226 my $restore = extract_param
($param, 'restore');
229 # fixme: limit allowed parameters
233 my $force = extract_param
($param, 'force');
235 if (!($same_container_exists && $restore && $force)) {
236 PVE
::Cluster
::check_vmid_unused
($vmid);
239 my $password = extract_param
($param, 'password');
241 my $storage = extract_param
($param, 'storage') // 'local';
243 my $storage_cfg = cfs_read_file
("storage.cfg");
245 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
247 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
248 if !($scfg->{content
}->{images
} || $scfg->{content
}->{rootdir
});
250 my $pool = extract_param
($param, 'pool');
252 if (defined($pool)) {
253 $rpcenv->check_pool_exist($pool);
254 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
257 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
259 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
261 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
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
270 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
272 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
274 my $ostemplate = extract_param
($param, 'ostemplate');
278 $param->{rootfs
} = $storage if !$param->{rootfs
};
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"
287 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
288 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
293 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $param);
295 my $check_vmid_usage = sub {
297 die "cant overwrite running container\n"
298 if PVE
::LXC
::check_running
($vmid);
300 PVE
::Cluster
::check_vmid_unused
($vmid);
305 &$check_vmid_usage(); # final check after locking
307 PVE
::Cluster
::check_cfs_quorum
();
311 my $rootmp = PVE
::LXC
::parse_ct_mountpoint
($param->{rootfs
});
312 my $root_volid = $rootmp->{volume
};
313 my $disksize = undef;
315 if ($root_volid =~ m/^(([^:\s]+):)?(\d+)?/) {
317 my ($storeid, $disksize) = ($2 || $storage, $3);
319 if (!defined($disksize)) {
321 (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
322 die "unable to detect disk size - please specify rootfs size\n"
327 $param->{rootfs
} = "$storeid:$disksize";
330 $vollist = &$create_disks($storage_cfg, $vmid, $param, $conf, $storage);
332 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore);
334 $conf->{hostname
} ||= "CT$vmid";
335 $conf->{memory
} ||= 512;
336 $conf->{swap
} //= 512;
337 PVE
::LXC
::create_config
($vmid, $conf);
340 foreach my $volid (@$vollist) {
341 eval { PVE
::Storage
::vdisk_free
($storage_cfg, $volid); };
345 PVE
::LXC
::destroy_config
($vmid);
348 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
351 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
353 &$check_vmid_usage(); # first check before locking
355 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
356 $vmid, $authuser, $realcmd);
360 __PACKAGE__-
>register_method({
365 description
=> "Directory index",
370 additionalProperties
=> 0,
372 node
=> get_standard_option
('pve-node'),
373 vmid
=> get_standard_option
('pve-vmid'),
381 subdir
=> { type
=> 'string' },
384 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
390 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
393 { subdir
=> 'config' },
394 { subdir
=> 'status' },
395 { subdir
=> 'vncproxy' },
396 { subdir
=> 'vncwebsocket' },
397 { subdir
=> 'spiceproxy' },
398 { subdir
=> 'migrate' },
399 # { subdir => 'initlog' },
401 { subdir
=> 'rrddata' },
402 { subdir
=> 'firewall' },
403 { subdir
=> 'snapshot' },
409 __PACKAGE__-
>register_method({
411 path
=> '{vmid}/rrd',
413 protected
=> 1, # fixme: can we avoid that?
415 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
417 description
=> "Read VM RRD statistics (returns PNG)",
419 additionalProperties
=> 0,
421 node
=> get_standard_option
('pve-node'),
422 vmid
=> get_standard_option
('pve-vmid'),
424 description
=> "Specify the time frame you are interested in.",
426 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
429 description
=> "The list of datasources you want to display.",
430 type
=> 'string', format
=> 'pve-configid-list',
433 description
=> "The RRD consolidation function",
435 enum
=> [ 'AVERAGE', 'MAX' ],
443 filename
=> { type
=> 'string' },
449 return PVE
::Cluster
::create_rrd_graph
(
450 "pve2-vm/$param->{vmid}", $param->{timeframe
},
451 $param->{ds
}, $param->{cf
});
455 __PACKAGE__-
>register_method({
457 path
=> '{vmid}/rrddata',
459 protected
=> 1, # fixme: can we avoid that?
461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
463 description
=> "Read VM RRD statistics",
465 additionalProperties
=> 0,
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid'),
470 description
=> "Specify the time frame you are interested in.",
472 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
475 description
=> "The RRD consolidation function",
477 enum
=> [ 'AVERAGE', 'MAX' ],
492 return PVE
::Cluster
::create_rrd_data
(
493 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
496 __PACKAGE__-
>register_method({
497 name
=> 'destroy_vm',
502 description
=> "Destroy the container (also delete all uses files).",
504 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
507 additionalProperties
=> 0,
509 node
=> get_standard_option
('pve-node'),
510 vmid
=> get_standard_option
('pve-vmid'),
519 my $rpcenv = PVE
::RPCEnvironment
::get
();
521 my $authuser = $rpcenv->get_user();
523 my $vmid = $param->{vmid
};
525 # test if container exists
526 my $conf = PVE
::LXC
::load_config
($vmid);
528 my $storage_cfg = cfs_read_file
("storage.cfg");
531 # reload config after lock
532 $conf = PVE
::LXC
::load_config
($vmid);
533 PVE
::LXC
::check_lock
($conf);
535 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
536 PVE
::AccessControl
::remove_vm_access
($vmid);
537 PVE
::Firewall
::remove_vmfw_conf
($vmid);
540 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
542 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
547 __PACKAGE__-
>register_method ({
549 path
=> '{vmid}/vncproxy',
553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
555 description
=> "Creates a TCP VNC proxy connections.",
557 additionalProperties
=> 0,
559 node
=> get_standard_option
('pve-node'),
560 vmid
=> get_standard_option
('pve-vmid'),
564 description
=> "use websocket instead of standard VNC.",
569 additionalProperties
=> 0,
571 user
=> { type
=> 'string' },
572 ticket
=> { type
=> 'string' },
573 cert
=> { type
=> 'string' },
574 port
=> { type
=> 'integer' },
575 upid
=> { type
=> 'string' },
581 my $rpcenv = PVE
::RPCEnvironment
::get
();
583 my $authuser = $rpcenv->get_user();
585 my $vmid = $param->{vmid
};
586 my $node = $param->{node
};
588 my $authpath = "/vms/$vmid";
590 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
592 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
595 my ($remip, $family);
597 if ($node ne PVE
::INotify
::nodename
()) {
598 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
600 $family = PVE
::Tools
::get_host_address_family
($node);
603 my $port = PVE
::Tools
::next_vnc_port
($family);
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] : [];
610 my $conf = PVE
::LXC
::load_config
($vmid, $node);
611 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
613 my $shcmd = [ '/usr/bin/dtach', '-A',
614 "/var/run/dtach/vzctlconsole$vmid",
615 '-r', 'winch', '-z', @$concmd];
620 syslog
('info', "starting lxc vnc proxy $upid\n");
624 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
625 '-timeout', $timeout, '-authpath', $authpath,
626 '-perm', 'VM.Console'];
628 if ($param->{websocket
}) {
629 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
630 push @$cmd, '-notls', '-listen', 'localhost';
633 push @$cmd, '-c', @$remcmd, @$shcmd;
640 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
642 PVE
::Tools
::wait_for_vnc_port
($port);
653 __PACKAGE__-
>register_method({
654 name
=> 'vncwebsocket',
655 path
=> '{vmid}/vncwebsocket',
658 description
=> "You also need to pass a valid ticket (vncticket).",
659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
661 description
=> "Opens a weksocket for VNC traffic.",
663 additionalProperties
=> 0,
665 node
=> get_standard_option
('pve-node'),
666 vmid
=> get_standard_option
('pve-vmid'),
668 description
=> "Ticket from previous call to vncproxy.",
673 description
=> "Port number returned by previous vncproxy call.",
683 port
=> { type
=> 'string' },
689 my $rpcenv = PVE
::RPCEnvironment
::get
();
691 my $authuser = $rpcenv->get_user();
693 my $authpath = "/vms/$param->{vmid}";
695 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
697 my $port = $param->{port
};
699 return { port
=> $port };
702 __PACKAGE__-
>register_method ({
703 name
=> 'spiceproxy',
704 path
=> '{vmid}/spiceproxy',
709 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
711 description
=> "Returns a SPICE configuration to connect to the CT.",
713 additionalProperties
=> 0,
715 node
=> get_standard_option
('pve-node'),
716 vmid
=> get_standard_option
('pve-vmid'),
717 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
720 returns
=> get_standard_option
('remote-viewer-config'),
724 my $vmid = $param->{vmid
};
725 my $node = $param->{node
};
726 my $proxy = $param->{proxy
};
728 my $authpath = "/vms/$vmid";
729 my $permissions = 'VM.Console';
731 my $conf = PVE
::LXC
::load_config
($vmid);
732 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
734 my $shcmd = ['/usr/bin/dtach', '-A',
735 "/var/run/dtach/vzctlconsole$vmid",
736 '-r', 'winch', '-z', @$concmd];
738 my $title = "CT $vmid";
740 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
744 __PACKAGE__-
>register_method({
745 name
=> 'migrate_vm',
746 path
=> '{vmid}/migrate',
750 description
=> "Migrate the container to another node. Creates a new migration task.",
752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
755 additionalProperties
=> 0,
757 node
=> get_standard_option
('pve-node'),
758 vmid
=> get_standard_option
('pve-vmid'),
759 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
762 description
=> "Use online/live migration.",
769 description
=> "the task ID.",
774 my $rpcenv = PVE
::RPCEnvironment
::get
();
776 my $authuser = $rpcenv->get_user();
778 my $target = extract_param
($param, 'target');
780 my $localnode = PVE
::INotify
::nodename
();
781 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
783 PVE
::Cluster
::check_cfs_quorum
();
785 PVE
::Cluster
::check_node_exists
($target);
787 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
789 my $vmid = extract_param
($param, 'vmid');
792 PVE
::LXC
::load_config
($vmid);
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
};
800 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
805 my $service = "ct:$vmid";
807 my $cmd = ['ha-manager', 'migrate', $service, $target];
809 print "Executing HA migrate for CT $vmid to node $target\n";
811 PVE
::Tools
::run_command
($cmd);
816 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
823 # fixme: implement lxc container migration
824 die "lxc container migration not implemented\n";
829 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
833 __PACKAGE__-
>register_method({
834 name
=> 'vm_feature',
835 path
=> '{vmid}/feature',
839 description
=> "Check if feature for virtual machine is available.",
841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
844 additionalProperties
=> 0,
846 node
=> get_standard_option
('pve-node'),
847 vmid
=> get_standard_option
('pve-vmid'),
849 description
=> "Feature to check.",
851 enum
=> [ 'snapshot' ],
853 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
861 hasFeature
=> { type
=> 'boolean' },
864 #items => { type => 'string' },
871 my $node = extract_param
($param, 'node');
873 my $vmid = extract_param
($param, 'vmid');
875 my $snapname = extract_param
($param, 'snapname');
877 my $feature = extract_param
($param, 'feature');
879 my $conf = PVE
::LXC
::load_config
($vmid);
882 my $snap = $conf->{snapshots
}->{$snapname};
883 die "snapshot '$snapname' does not exist\n" if !defined($snap);
886 my $storage_cfg = PVE
::Storage
::config
();
888 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
889 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
892 hasFeature
=> $hasFeature,
893 #nodes => [ keys %$nodelist ],
897 __PACKAGE__-
>register_method({
899 path
=> '{vmid}/template',
903 description
=> "Create a Template.",
905 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
906 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
909 additionalProperties
=> 0,
911 node
=> get_standard_option
('pve-node'),
912 vmid
=> get_standard_option
('pve-vmid'),
915 returns
=> { type
=> 'null'},
919 my $rpcenv = PVE
::RPCEnvironment
::get
();
921 my $authuser = $rpcenv->get_user();
923 my $node = extract_param
($param, 'node');
925 my $vmid = extract_param
($param, 'vmid');
929 my $conf = PVE
::LXC
::load_config
($vmid);
930 PVE
::LXC
::check_lock
($conf);
932 die "unable to create template, because CT contains snapshots\n"
933 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
935 die "you can't convert a template to a template\n"
936 if PVE
::LXC
::is_template
($conf);
938 die "you can't convert a CT to template if the CT is running\n"
939 if PVE
::LXC
::check_running
($vmid);
942 PVE
::LXC
::template_create
($vmid, $conf);
945 $conf->{template
} = 1;
947 PVE
::LXC
::write_config
($vmid, $conf);
948 # and remove lxc config
949 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
951 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
954 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);