]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
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
;
14 use PVE
::RPCEnvironment
;
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use base
qw(PVE::RESTHandler);
19 use Data
::Dumper
; # fixme: remove
21 my $get_container_storage = sub {
22 my ($stcfg, $vmid, $lxc_conf) = @_;
24 my $path = $lxc_conf->{'lxc.rootfs'};
25 my ($vtype, $volid) = PVE
::Storage
::path_to_volume_id
($stcfg, $path);
26 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1) if $volid;
27 return wantarray ?
($sid, $volname, $path) : $sid;
30 my $check_ct_modify_config_perm = sub {
31 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
33 return 1 if $authuser ne 'root@pam';
35 foreach my $opt (@$key_list) {
37 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
38 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
39 } elsif ($opt eq 'disk') {
40 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
41 } elsif ($opt eq 'memory' || $opt eq 'swap') {
42 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
43 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
44 $opt eq 'searchdomain' || $opt eq 'hostname') {
45 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
47 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
55 __PACKAGE__-
>register_method({
59 description
=> "LXC container index (per node).",
61 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
65 protected
=> 1, # /proc files are only readable by root
67 additionalProperties
=> 0,
69 node
=> get_standard_option
('pve-node'),
78 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
83 my $rpcenv = PVE
::RPCEnvironment
::get
();
84 my $authuser = $rpcenv->get_user();
86 my $vmstatus = PVE
::LXC
::vmstatus
();
89 foreach my $vmid (keys %$vmstatus) {
90 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
92 my $data = $vmstatus->{$vmid};
93 $data->{vmid
} = $vmid;
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::json_config_properties
({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid'),
120 description
=> "The OS template or backup file.",
127 description
=> "Sets root password inside container.",
129 storage
=> get_standard_option
('pve-storage-id', {
130 description
=> "Target storage.",
137 description
=> "Allow to overwrite existing container.",
142 description
=> "Mark this as restore task.",
146 type
=> 'string', format
=> 'pve-poolid',
147 description
=> "Add the VM to the specified pool.",
157 my $rpcenv = PVE
::RPCEnvironment
::get
();
159 my $authuser = $rpcenv->get_user();
161 my $node = extract_param
($param, 'node');
163 my $vmid = extract_param
($param, 'vmid');
165 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
167 my $same_container_exists = -f
$basecfg_fn;
169 my $restore = extract_param
($param, 'restore');
171 my $force = extract_param
($param, 'force');
173 if (!($same_container_exists && $restore && $force)) {
174 PVE
::Cluster
::check_vmid_unused
($vmid);
177 my $password = extract_param
($param, 'password');
179 my $storage = extract_param
($param, 'storage') || 'local';
181 my $pool = extract_param
($param, 'pool');
183 my $storage_cfg = cfs_read_file
("storage.cfg");
185 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
187 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
188 if !$scfg->{content
}->{rootdir
};
190 my $private = PVE
::Storage
::get_private_dir
($storage_cfg, $storage, $vmid);
192 if (defined($pool)) {
193 $rpcenv->check_pool_exist($pool);
194 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
197 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
199 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
201 } elsif ($restore && $force && $same_container_exists &&
202 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
203 # OK: user has VM.Backup permissions, and want to restore an existing VM
208 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
210 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
212 my $ostemplate = extract_param
($param, 'ostemplate');
216 if ($ostemplate eq '-') {
217 die "archive pipe not implemented\n"
220 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
221 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
224 my $memory = $param->{memory
} || 512;
225 my $hostname = $param->{hostname
} || "T$vmid";
228 $conf->{'lxc.utsname'} = $param->{hostname
} || "CT$vmid";
229 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = "${memory}M";
232 my $temp_conf_fn = PVE
::LXC
::write_temp_config
($vmid, $conf);
234 my $cmd = ['lxc-create', '-f', $temp_conf_fn, '-t', 'pve', '-n', $vmid,
235 '--', '--archive', $archive];
237 eval { PVE
::Tools
::run_command
($cmd); };
240 unlink $temp_conf_fn;
245 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
247 return $rpcenv->fork_worker($param->{restore
} ?
'vzrestore' : 'vzcreate',
248 $vmid, $authuser, $realcmd);
252 my $vm_config_perm_list = [
260 __PACKAGE__-
>register_method({
262 path
=> '{vmid}/config',
266 description
=> "Set container options.",
268 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
271 additionalProperties
=> 0,
272 properties
=> PVE
::LXC
::json_config_properties
(
274 node
=> get_standard_option
('pve-node'),
275 vmid
=> get_standard_option
('pve-vmid'),
277 type
=> 'string', format
=> 'pve-configid-list',
278 description
=> "A list of settings you want to delete.",
283 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
289 returns
=> { type
=> 'null'},
293 my $rpcenv = PVE
::RPCEnvironment
::get
();
295 my $authuser = $rpcenv->get_user();
297 my $node = extract_param
($param, 'node');
299 my $vmid = extract_param
($param, 'vmid');
301 my $digest = extract_param
($param, 'digest');
303 die "no options specified\n" if !scalar(keys %$param);
305 my $delete_str = extract_param
($param, 'delete');
306 my @delete = PVE
::Tools
::split_list
($delete_str);
308 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
310 foreach my $opt (@delete) {
311 raise_param_exc
({ delete => "you can't use '-$opt' and " .
312 "-delete $opt' at the same time" })
313 if defined($param->{$opt});
315 if (!PVE
::LXC
::option_exists
($opt)) {
316 raise_param_exc
({ delete => "unknown option '$opt'" });
320 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
324 my $conf = PVE
::LXC
::load_config
($vmid);
326 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
330 foreach my $opt (@delete) {
331 if ($opt eq 'hostname') {
332 die "unable to delete required option '$opt'\n";
333 } elsif ($opt =~ m/^net\d$/) {
334 delete $conf->{$opt};
340 foreach my $opt (keys %$param) {
341 my $value = $param->{$opt};
342 if ($opt eq 'hostname') {
343 $conf->{'lxc.utsname'} = $value;
344 } elsif ($opt eq 'memory') {
345 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
346 } elsif ($opt eq 'swap') {
347 $conf->{'lxc.cgroup.memory.memsw.usage_in_bytes'} = $value*1024*1024;
348 } elsif ($opt =~ m/^net(\d+)$/) {
350 my $net = PVE
::LXC
::parse_lxc_network
($value);
351 $net->{'veth.pair'} = "veth${vmid}.$netid";
352 $conf->{$opt} = $net;
358 PVE
::LXC
::write_config
($vmid, $conf);
361 PVE
::LXC
::lock_container
($vmid, undef, $code);
366 __PACKAGE__-
>register_method ({
367 subclass
=> "PVE::API2::Firewall::CT",
368 path
=> '{vmid}/firewall',
371 __PACKAGE__-
>register_method({
376 description
=> "Directory index",
381 additionalProperties
=> 0,
383 node
=> get_standard_option
('pve-node'),
384 vmid
=> get_standard_option
('pve-vmid'),
392 subdir
=> { type
=> 'string' },
395 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
401 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
404 { subdir
=> 'config' },
405 # { subdir => 'status' },
406 # { subdir => 'vncproxy' },
407 # { subdir => 'spiceproxy' },
408 # { subdir => 'migrate' },
409 # { subdir => 'initlog' },
411 { subdir
=> 'rrddata' },
412 { subdir
=> 'firewall' },
418 __PACKAGE__-
>register_method({
420 path
=> '{vmid}/rrd',
422 protected
=> 1, # fixme: can we avoid that?
424 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
426 description
=> "Read VM RRD statistics (returns PNG)",
428 additionalProperties
=> 0,
430 node
=> get_standard_option
('pve-node'),
431 vmid
=> get_standard_option
('pve-vmid'),
433 description
=> "Specify the time frame you are interested in.",
435 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
438 description
=> "The list of datasources you want to display.",
439 type
=> 'string', format
=> 'pve-configid-list',
442 description
=> "The RRD consolidation function",
444 enum
=> [ 'AVERAGE', 'MAX' ],
452 filename
=> { type
=> 'string' },
458 return PVE
::Cluster
::create_rrd_graph
(
459 "pve2-vm/$param->{vmid}", $param->{timeframe
},
460 $param->{ds
}, $param->{cf
});
464 __PACKAGE__-
>register_method({
466 path
=> '{vmid}/rrddata',
468 protected
=> 1, # fixme: can we avoid that?
470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
472 description
=> "Read VM RRD statistics",
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
477 vmid
=> get_standard_option
('pve-vmid'),
479 description
=> "Specify the time frame you are interested in.",
481 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
484 description
=> "The RRD consolidation function",
486 enum
=> [ 'AVERAGE', 'MAX' ],
501 return PVE
::Cluster
::create_rrd_data
(
502 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/config',
511 description
=> "Get container configuration.",
513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
527 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
534 my $lxc_conf = PVE
::LXC
::load_config
($param->{vmid
});
536 # NOTE: we only return selected/converted values
538 my $conf = { digest
=> $lxc_conf->{digest
} };
540 my $stcfg = PVE
::Cluster
::cfs_read_file
("storage.cfg");
542 my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid
}, $lxc_conf);
543 $conf->{storage
} = $sid || $path;
545 my $properties = PVE
::LXC
::json_config_properties
();
547 foreach my $k (keys %$properties) {
549 if ($k eq 'description') {
550 if (my $raw = $lxc_conf->{'pve.comment'}) {
551 $conf->{$k} = PVE
::Tools
::decode_text
($raw);
553 } elsif ($k eq 'hostname') {
554 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
555 } elsif ($k eq 'memory') {
556 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
557 $conf->{$k} = int($value / (1024*1024));
559 } elsif ($k eq 'swap') {
560 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.usage_in_bytes'}) {
561 $conf->{$k} = int($value / (1024*1024));
563 } elsif ($k =~ m/^net\d$/) {
564 my $net = $lxc_conf->{$k};
566 $conf->{$k} = PVE
::LXC
::print_lxc_network
($net);
573 __PACKAGE__-
>register_method({
574 name
=> 'destroy_vm',
579 description
=> "Destroy the container (also delete all uses files).",
581 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
584 additionalProperties
=> 0,
586 node
=> get_standard_option
('pve-node'),
587 vmid
=> get_standard_option
('pve-vmid'),
596 my $rpcenv = PVE
::RPCEnvironment
::get
();
598 my $authuser = $rpcenv->get_user();
600 my $vmid = $param->{vmid
};
602 # test if container exists
603 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
606 my $cmd = ['lxc-destroy', '-n', $vmid ];
610 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
613 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);