]> git.proxmox.com Git - pve-container.git/commitdiff
initial commit
authorDietmar Maurer <dietmar@proxmox.com>
Thu, 16 Apr 2015 05:10:26 +0000 (07:10 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 16 Apr 2015 05:34:12 +0000 (07:34 +0200)
13 files changed:
Makefile [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
src/Makefile [new file with mode: 0644]
src/PVE/API2/LXC.pm [new file with mode: 0644]
src/PVE/API2/Makefile [new file with mode: 0644]
src/PVE/LXC.pm [new file with mode: 0644]
src/PVE/Makefile [new file with mode: 0644]
src/lxc-pve [new file with mode: 0755]
src/pct [new file with mode: 0755]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c9009a7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,47 @@
+RELEASE=4.0
+
+VERSION=0.1
+PACKAGE=pve-container
+PKGREL=1
+
+GITVERSION:=$(shell cat .git/refs/heads/master)
+
+ARCH:=all
+
+DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
+
+all: ${DEB}
+
+.PHONY: dinstall
+dinstall: ${DEB}
+       dpkg -i ${DEB}
+
+.PHONY: deb
+deb ${DEB}:
+       rm -rf build
+       mkdir build
+       rsync -a src/ build
+       rsync -a debian/ build/debian
+       echo "git clone git://git.proxmox.com/git/pve-container\\ngit checkout ${GITVERSION}" > build/debian/SOURCE
+       cd build; dpkg-buildpackage -rfakeroot -b -us -uc
+       lintian ${DEB}
+
+.PHONY: clean
+clean:
+       make -C src clean
+       rm -rf build *.deb ${PACKAGE}-*.tar.gz *.changes 
+       find . -name '*~' -exec rm {} ';'
+
+.PHONY: distclean
+distclean: clean
+
+.PHONY: upload
+upload: ${DEB}
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw 
+       mkdir -p /pve/${RELEASE}/extra
+       rm -f /pve/${RELEASE}/extra/${PACKAGE}_*.deb
+       rm -f /pve/${RELEASE}/extra/Packages*
+       cp ${DEB} /pve/${RELEASE}/extra
+       cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
+       umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..649abf0
--- /dev/null
@@ -0,0 +1,6 @@
+pve-container (0.1-1) unstable; urgency=medium
+
+  * first try
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 16 Apr 2015 06:50:08 +0200
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..2e760fa
--- /dev/null
@@ -0,0 +1,14 @@
+Source: pve-container
+Section: perl
+Priority: extra
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Build-Depends: debhelper (>= 7.0.50~), libpve-common-perl, libpve-storage-perl, pve-cluster (>= 4.0-8) 
+Standards-Version: 3.8.4
+
+Package: pve-container
+Section: perl
+Priority: optional
+Architecture: all
+Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, libpve-common-perl, libpve-storage-perl, pve-cluster (>= 4.0-8)
+Description: Proxmox VE Container management tool
+ Tool to manage Linux Containers on Proxmox VE.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..3ef2e85
--- /dev/null
@@ -0,0 +1,16 @@
+Copyright (C) 2015 Proxmox Server Solutions GmbH
+
+This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..955dd78
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+       dh $@
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..1cd7c5a
--- /dev/null
@@ -0,0 +1,48 @@
+PACKAGE=pve-container
+
+PREFIX=${DESTDIR}/usr
+BINDIR=${PREFIX}/bin
+SBINDIR=${PREFIX}/sbin
+MANDIR=${PREFIX}/share/man
+DOCDIR=${PREFIX}/share/doc/${PACKAGE}
+LXCTMPLDIR=${PREFIX}/share/lxc/templates
+PODDIR=${DOCDIR}/pod
+MAN1DIR=${MANDIR}/man1/
+export PERLDIR=${PREFIX}/share/perl5
+
+all:
+
+%.1.gz: %.1.pod
+       rm -f $@
+       cat $<|pod2man -n $* -s 1 -r ${VERSION} -c "Proxmox Documentation"|gzip -c9 >$@
+
+pct.1.pod: pct
+       perl -I.. -T ./pct printmanpod >$@
+
+lxc-pve.1.pod: lxc-pve
+       perl -I.. -T ./lxc-pve printmanpod >$@
+
+.PHONY: install
+install: pct lxc-pve pct.1.pod pct.1.gz lxc-pve.1.pod lxc-pve.1.gz
+       perl -T -I. ./pct verifyapi
+       perl -T -I. ./lxc-pve verifyapi
+       install -d ${SBINDIR}
+       install -m 0755 pct ${SBINDIR}
+       install -d ${LXCTMPLDIR}
+       install -m 0755 lxc-pve ${LXCTMPLDIR}
+       make -C PVE install
+       install -d ${MAN1DIR}
+       install -d ${PODDIR}
+       install -m 0644 pct.1.gz ${MAN1DIR}
+       install -m 0644 lxc-pve.1.gz ${MAN1DIR}
+       install -m 0644 pct.1.pod ${PODDIR}
+       install -m 0644 lxc-pve.1.pod ${PODDIR}
+
+.PHONY: clean
+clean:         
+       rm -rf *.1.pod *.1.gz
+       find . -name '*~' -exec rm {} ';'
+
+.PHONY: distclean
+distclean: clean
+
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
new file mode 100644 (file)
index 0000000..0dc9c24
--- /dev/null
@@ -0,0 +1,406 @@
+package PVE::API2::LXC;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param run_command);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::INotify;
+use PVE::AccessControl;
+use PVE::Storage;
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::LXC;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::RESTHandler);
+
+use Data::Dumper; # fixme: remove
+
+my $get_container_storage = sub {
+    my ($stcfg, $vmid, $lxc_conf) = @_;
+
+    my $path = $lxc_conf->{'lxc.rootfs'};
+    my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path);
+    my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid;
+    return wantarray ? ($sid, $volname, $path) : $sid;
+};
+
+my $check_ct_modify_config_perm = sub {
+    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
+    
+    return 1 if $authuser ne 'root@pam';
+
+    foreach my $opt (@$key_list) {
+
+       if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
+       } elsif ($opt eq 'disk') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
+       } elsif ($opt eq 'memory' || $opt eq 'swap') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
+       } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' || 
+                $opt eq 'searchdomain' || $opt eq 'hostname') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
+       } else {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
+       }
+    }
+
+    return 1;
+};
+
+
+__PACKAGE__->register_method({
+    name => 'vmlist', 
+    path => '', 
+    method => 'GET',
+    description => "LXC container index (per node).",
+    permissions => {
+       description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
+       user => 'all',
+    },
+    proxyto => 'node',
+    protected => 1, # /proc files are only readable by root
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {},
+       },
+       links => [ { rel => 'child', href => "{vmid}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $vmstatus = PVE::LXC::vmstatus();
+
+       my $res = [];
+       foreach my $vmid (keys %$vmstatus) {
+           next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
+
+           my $data = $vmstatus->{$vmid};
+           $data->{vmid} = $vmid;
+           push @$res, $data;
+       }
+
+       return $res;
+  
+    }});
+
+my $vm_config_perm_list = [
+           'VM.Config.Disk', 
+           'VM.Config.CPU', 
+           'VM.Config.Memory', 
+           'VM.Config.Network', 
+           'VM.Config.Options',
+    ];
+
+__PACKAGE__->register_method({
+    name => 'update_vm', 
+    path => '{vmid}/config', 
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Set container options.",
+    permissions => {
+       check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::LXC::json_config_properties(
+           {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               digest => {
+                   type => 'string',
+                   description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+                   maxLength => 40,
+                   optional => 1,                  
+               }
+           }),
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $authuser = $rpcenv->get_user();
+
+       my $node = extract_param($param, 'node');
+
+       my $vmid = extract_param($param, 'vmid');
+
+       my $digest = extract_param($param, 'digest');
+
+       die "no options specified\n" if !scalar(keys %$param);
+
+       &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
+
+       my $code = sub {
+
+           my $conf = PVE::LXC::load_config($vmid);
+
+           PVE::Tools::assert_if_modified($digest, $conf->{digest});
+
+           die "implement me"
+       };
+
+       PVE::LXC::lock_container($vmid, undef, $code);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::CT",  
+    path => '{vmid}/firewall',
+});
+
+__PACKAGE__->register_method({
+    name => 'vmdiridx',
+    path => '{vmid}', 
+    method => 'GET',
+    proxyto => 'node',
+    description => "Directory index",
+    permissions => {
+       user => 'all',
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {
+               subdir => { type => 'string' },
+           },
+       },
+       links => [ { rel => 'child', href => "{subdir}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       # test if VM exists
+       my $conf = PVE::OpenVZ::load_config($param->{vmid});
+
+       my $res = [
+           { subdir => 'config' },
+#          { subdir => 'status' },
+#          { subdir => 'vncproxy' },
+#          { subdir => 'spiceproxy' },
+#          { subdir => 'migrate' },
+#          { subdir => 'initlog' },
+           { subdir => 'rrd' },
+           { subdir => 'rrddata' },
+           { subdir => 'firewall' },
+           ];
+       
+       return $res;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'rrd', 
+    path => '{vmid}/rrd', 
+    method => 'GET',
+    protected => 1, # fixme: can we avoid that?
+    permissions => {
+       check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+    },
+    description => "Read VM RRD statistics (returns PNG)",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           ds => {
+               description => "The list of datasources you want to display.",
+               type => 'string', format => 'pve-configid-list',
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "object",
+       properties => {
+           filename => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_graph(
+           "pve2-vm/$param->{vmid}", $param->{timeframe}, 
+           $param->{ds}, $param->{cf});
+                                             
+    }});
+
+__PACKAGE__->register_method({
+    name => 'rrddata', 
+    path => '{vmid}/rrddata', 
+    method => 'GET',
+    protected => 1, # fixme: can we avoid that?
+    permissions => {
+       check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+    },
+    description => "Read VM RRD statistics",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           timeframe => {
+               description => "Specify the time frame you are interested in.",
+               type => 'string',
+               enum => [ 'hour', 'day', 'week', 'month', 'year' ],
+           },
+           cf => {
+               description => "The RRD consolidation function",
+               type => 'string',
+               enum => [ 'AVERAGE', 'MAX' ],
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => "array",
+       items => {
+           type => "object",
+           properties => {},
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       return PVE::Cluster::create_rrd_data(
+           "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
+    }});
+
+
+__PACKAGE__->register_method({
+    name => 'vm_config', 
+    path => '{vmid}/config', 
+    method => 'GET',
+    proxyto => 'node',
+    description => "Get container configuration.",
+    permissions => {
+       check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { 
+       type => "object",
+       properties => {
+           digest => {
+               type => 'string',
+               description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
+           }
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $lxc_conf = PVE::LXC::load_config($param->{vmid});
+
+       # NOTE: we only return selected/converted values
+       
+       my $conf = { digest => $lxc_conf->{digest} };
+
+       my $stcfg = PVE::Cluster::cfs_read_file("storage.cfg");
+
+       my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $lxc_conf);
+       $conf->{storage} = $sid || $path;
+
+       my $properties = PVE::LXC::json_config_properties();
+
+       foreach my $k (keys %$properties) {
+
+           if ($k eq 'description') {
+               if (my $raw = $lxc_conf->{'pve.comment'}) {
+                   $conf->{$k} = PVE::Tools::decode_text($raw);
+               }
+           } elsif ($k eq 'hostname') {
+               $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
+           } elsif ($k =~ m/^net\d$/) {
+               my $net = $lxc_conf->{$k};
+               next if !$net;
+               $conf->{$k} = PVE::LXC::print_netif($net);
+           }
+       }
+
+       return $conf;
+    }});
+
+__PACKAGE__->register_method({
+    name => 'destroy_vm', 
+    path => '{vmid}', 
+    method => 'DELETE',
+    protected => 1,
+    proxyto => 'node',
+    description => "Destroy the container (also delete all uses files).",
+    permissions => {
+       check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+       },
+    },
+    returns => { 
+       type => 'string',
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $authuser = $rpcenv->get_user();
+
+       my $vmid = $param->{vmid};
+
+       # test if container exists
+       my $conf = PVE::LXC::load_config($param->{vmid});
+
+       my $realcmd = sub {
+           my $cmd = ['lxc-destroy', '-n', $vmid ];
+
+           run_command($cmd);
+
+           PVE::AccessControl::remove_vm_from_pool($vmid);
+       };
+
+       return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
+    }});
+
+1;
diff --git a/src/PVE/API2/Makefile b/src/PVE/API2/Makefile
new file mode 100644 (file)
index 0000000..5fe8e5b
--- /dev/null
@@ -0,0 +1,6 @@
+SOURCES=LXC.pm
+
+.PHONY: install
+install:
+       install -d -m 0755 ${PERLDIR}/PVE/API2
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${PERLDIR}/PVE/API2/$$i; done
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
new file mode 100644 (file)
index 0000000..ca1f4da
--- /dev/null
@@ -0,0 +1,365 @@
+package PVE::LXC;
+
+use strict;
+use warnings;
+
+use File::Path;
+use Fcntl ':flock';
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::SafeSyslog;
+use PVE::INotify;
+
+use Data::Dumper;
+
+cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
+
+my $nodename = PVE::INotify::nodename();
+
+my $valid_lxc_keys = {
+    'lxc.arch' => 1,
+    'lxc.include' => 1,
+    'lxc.rootfs' => 1,
+    'lxc.mount' => 1,
+    'lxc.utsname' => 1,
+
+    'lxc.cgroup.memory.limit_in_bytes' => 1,
+
+    # pve related keys
+    'pve.comment' => 1,
+};
+
+my $valid_network_keys = {
+    type => 1,
+    flags => 1,
+    link => 1,
+    mtu => 1,
+    name => 1, # ifname inside container
+    'veth.pair' => 1, # ifname at host (eth${vmid}.X)
+    hwaddr => 1,
+    ipv4 => 1,
+    'ipv4.gateway' => 1,
+    ipv6 => 1,
+    'ipv6.gateway' => 1,
+};
+
+my $lxc_array_configs = {
+    'lxc.network' => 1,
+    'lxc.mount' => 1,
+    'lxc.include' => 1,
+};
+
+sub write_lxc_config {
+    my ($filename, $data) = @_;
+
+    my $raw = "";
+
+    return $raw if !$data;
+
+    my $done_hash = { digest => 1};
+    
+    foreach my $k (sort keys %$data) {
+       next if $k !~ m/^lxc\./;
+       $done_hash->{$k} = 1;
+       $raw .= "$k = $data->{$k}\n";
+    }
+    
+    foreach my $k (sort keys %$data) {
+       next if $k !~ m/^net\d+$/;
+       $done_hash->{$k} = 1;
+       my $net = $data->{$k};
+       $raw .= "lxc.network.type = $net->{type}\n";
+       foreach my $subkey (sort keys %$net) {
+           next if $subkey eq 'type';
+           $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
+       }
+    }
+
+    foreach my $k (sort keys %$data) {
+       next if $done_hash->{$k};
+       die "found un-written value in config - implement this!";
+    }
+
+    print $raw;
+    
+    return $raw;
+}
+
+sub parse_lxc_config {
+    my ($filename, $raw) = @_;
+
+    return undef if !defined($raw);
+
+    my $data = {
+       digest => Digest::SHA::sha1_hex($raw),
+    };
+
+    $filename =~ m|/lxc/(\d+)/config$|
+       || die "got strange filename '$filename'";
+
+    my $vmid = $1;
+
+    my $network_counter = 0;
+    my $network_list = [];
+    my $host_ifnames = {};
+
+     my $find_next_hostif_name = sub {
+       for (my $i = 0; $i < 10; $i++) {
+           my $name = "veth${vmid}.$i";
+           if (!$host_ifnames->{$name}) {
+               $host_ifnames->{$name} = 1;
+               return $name;
+           }
+       }
+
+       die "unable to find free host_ifname"; # should not happen
+    };
+   
+    my $push_network = sub {
+       my ($netconf) = @_;
+       return if !$netconf;
+       push @{$network_list}, $netconf;
+       $network_counter++;
+       if (my $netname = $netconf->{'veth.pair'}) {
+           if ($netname =~ m/^veth(\d+).(\d)$/) {
+               die "wrong vmid for network interface pair\n" if $1 != $vmid;
+               my $host_ifnames->{$netname} = 1;
+           } else {
+               die "wrong network interface pair\n";
+           }
+       }
+    };
+
+    my $network;
+    
+    while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+       my $line = $1;
+
+       next if $line =~ m/^\#/;
+       next if $line =~ m/^\s*$/;
+
+       if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
+           my ($subkey, $value) = ($1, $2);
+           if ($subkey eq 'type') {
+               &$push_network($network);
+               $network = { type => $value };
+           } elsif ($valid_network_keys->{$subkey}) {
+               $network->{$subkey} = $value;
+           } else {
+               die "unable to parse config line: $line\n";
+           }
+           
+           next;
+       }
+       if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
+           my ($name, $value) = ($1, $2);
+           $data->{$name} = $value;
+           next;
+       }
+       if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S+)\s*$/) {
+           my ($name, $value) = ($1, $2);
+
+           die "inavlid key '$name'\n" if !$valid_lxc_keys->{$name};       
+
+           die "multiple definitions for $name\n" if defined($data->{$name});
+
+           $data->{$name} = $value;
+           next;
+       }
+
+       die "unable to parse config line: $line\n";
+    }
+
+    &$push_network($network);
+
+    foreach my $net (@{$network_list}) {
+       $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
+       $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
+       die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
+
+       if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
+           $data->{"net$1"} = $net;
+       }
+    } 
+    
+    return $data;   
+}
+
+sub config_list {
+    my $vmlist = PVE::Cluster::get_vmlist();
+    my $res = {};
+    return $res if !$vmlist || !$vmlist->{ids};
+    my $ids = $vmlist->{ids};
+
+    foreach my $vmid (keys %$ids) {
+       next if !$vmid; # skip CT0
+       my $d = $ids->{$vmid};
+       next if !$d->{node} || $d->{node} ne $nodename;
+       next if !$d->{type} || $d->{type} ne 'lxc';
+       $res->{$vmid}->{type} = 'lxc';
+    }
+    return $res;
+}
+
+sub cfs_config_path {
+    my ($vmid, $node) = @_;
+
+    $node = $nodename if !$node;
+    return "nodes/$node/lxc/$vmid/config";
+}
+
+sub load_config {
+    my ($vmid) = @_;
+
+    my $cfspath = cfs_config_path($vmid);
+
+    my $conf = PVE::Cluster::cfs_read_file($cfspath);
+    die "container $vmid does not exists\n" if !defined($conf);
+
+    return $conf;
+}
+
+sub write_config {
+    my ($vmid, $conf) = @_;
+
+    my $cfspath = cfs_config_path($vmid);
+
+    PVE::Cluster::cfs_write_file($cfspath, $conf);
+}
+
+sub lock_container {
+    my ($vmid, $timeout, $code, @param) = @_;
+
+    my $lockdir = "/run/lock/lxc";
+    my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
+
+    File::Path::make_path($lockdir);
+
+    my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
+
+    die $@ if $@;
+
+    return $res;
+}
+
+my $confdesc = {
+    onboot => {
+       optional => 1,
+       type => 'boolean',
+       description => "Specifies whether a VM will be started during system bootup.",
+       default => 0,
+    },
+    cpus => {
+       optional => 1,
+       type => 'integer',
+       description => "The number of CPUs for this container.",
+       minimum => 1,
+       default => 1,
+    },
+    cpuunits => {
+       optional => 1,
+       type => 'integer',
+       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+       minimum => 0,
+       maximum => 500000,
+       default => 1000,
+    },
+    memory => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of RAM for the VM in MB.",
+       minimum => 16,
+       default => 512,
+    },
+    swap => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of SWAP for the VM in MB.",
+       minimum => 0,
+       default => 512,
+    },
+    disk => {
+       optional => 1,
+       type => 'number',
+       description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
+       minimum => 0,
+       default => 2,
+    },
+    hostname => {
+       optional => 1,
+       description => "Set a host name for the container.",
+       type => 'string',
+       maxLength => 255,
+    },
+    description => {
+       optional => 1,
+       type => 'string',
+       description => "Container description. Only used on the configuration web interface.",
+    },
+    searchdomain => {
+       optional => 1,
+       type => 'string',
+       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+    },
+    nameserver => {
+       optional => 1,
+       type => 'string',
+       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+    },
+    net0 => {
+       optional => 1,
+       type => 'string', format => 'pve-lxc-network',
+       description => "Specifies network interfaces for the container.",
+    },
+};
+
+# add JSON properties for create and set function
+sub json_config_properties {
+    my $prop = shift;
+
+    foreach my $opt (keys %$confdesc) {
+       $prop->{$opt} = $confdesc->{$opt};
+    }
+
+    return $prop;
+}
+
+
+sub vmstatus {
+    my ($opt_vmid) = @_;
+
+    my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
+
+    foreach my $vmid (keys %$list) {
+       next if $opt_vmid && ($vmid ne $opt_vmid);
+
+       my $d = $list->{$vmid};
+       $d->{status} = 'stopped';
+
+       my $cfspath = cfs_config_path($vmid);
+       if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
+           print Dumper($conf);
+           $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
+           $d->{name} =~ s/[\s]//g;
+
+       }
+    }
+
+    return $list;
+}
+
+sub print_netif {
+    my $net = shift;
+
+    my $res = "pair=$net->{pair}";
+
+    foreach my $k (qw(link hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway)) {
+       next if !defined($net->{$k});
+       $res .= ",$k=$net->{$k}";
+    }
+    
+    return $res;
+}
+
+
+1;
diff --git a/src/PVE/Makefile b/src/PVE/Makefile
new file mode 100644 (file)
index 0000000..fac35d8
--- /dev/null
@@ -0,0 +1,8 @@
+SOURCES=LXC.pm
+
+.PHONY: install
+install:
+       install -d -m 0755 ${PERLDIR}/PVE
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${PERLDIR}/PVE/$$i; done
+       make -C API2 install
+
diff --git a/src/lxc-pve b/src/lxc-pve
new file mode 100755 (executable)
index 0000000..64714c9
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX;
+use File::Path;
+
+use PVE::SafeSyslog;
+use PVE::Tools;
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::RPCEnvironment;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::CLIHandler;
+use PVE::Storage;
+use PVE::LXC;
+use Data::Dumper;
+
+use base qw(PVE::CLIHandler);
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+initlog ('lxc-pve');
+
+die "please run as root\n" if $> != 0;
+
+
+PVE::INotify::inotify_init();
+
+my $rpcenv = PVE::RPCEnvironment->init('cli');
+$rpcenv->set_language($ENV{LANG});
+$rpcenv->set_user('root@pam');
+
+my $nodename = PVE::INotify::nodename();
+
+__PACKAGE__->register_method ({
+    name => 'lxc-pve',
+    path => 'lxc-pve',
+    method => 'GET',
+    description => "Create a new container root directory.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           name => get_standard_option('pve-vmid'),
+           path => {
+               description => "The path to the container configuration directory (LXC internal argument - do not pass manually!).",
+               type => 'string',
+           },
+           rootfs => {
+               description => "The path to the container's rootfs (LXC internal argument - do not pass manually!)",
+               type => 'string',
+           },
+           storage => get_standard_option('pve-storage-id'),
+           'mapped-uid' => {
+               description => " A uid map (user namespaces - LXC internal argument - do not pass manually!)",
+               type => 'string',
+               optional => 1,
+           },
+           'mapped-gid' => {
+               description => " A gid map (user namespaces - LXC internal argument - do not pass manually!)",
+               type => 'string',
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       # check that we have all variables we need
+       if (!(defined($ENV{LXC_NAME}) && defined($ENV{LXC_PATH}) && defined($ENV{LXC_ROOTFS}))) {
+           die "ERROR: Not running through LXC.\n"
+       }
+
+       # only orinial user can access /etc/pve
+       $< = $param->{'mapped-uid'} if defined($param->{'mapped-uid'});
+       $( = $param->{'mapped-gid'} if defined($param->{'mapped-gid'});
+
+       my $userns = defined($param->{'mapped-uid'}) ? 1 : 0;
+
+       my $private = $param->{rootfs};
+
+       die "got strange path '$param->{path}'\n"
+           if $param->{path} !~ m!^/etc/pve/lxc/$param->{name}/?$!;
+       
+       # print "TEST $< $(\n";
+
+       $rpcenv->init_request();
+
+       print "TEST2\n";
+       
+       my $conf = PVE::LXC::load_config($param->{name});
+       print Dumper($conf);
+
+       my $storage_cfg = PVE::Cluster::cfs_read_file("storage.cfg");
+
+       my $ostemplate = "local:vztmpl/debian-7.0-standard_7.0-2_i386.tar.gz";
+       print "Template: $ostemplate\n";
+       
+       my $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
+
+       #$< = $( = 0; # switch back to container root
+       #print "TEST $< $(\n";
+      
+       my $cmd = ['tar', 'xpf', $archive, '--numeric-owner', '--totals', '--sparse', '-C', $private];
+       push @$cmd, '--anchored';
+       push @$cmd, '--exclude' , '/dev/*';
+#      push @$cmd, '--exclude' , '/dev/loop*';
+#      push @$cmd, '--exclude' , '/dev/ram*';
+#      push @$cmd, '--exclude' , '/dev/pty*';
+#      push @$cmd, '--exclude' , '/dev/tty*';
+#      push @$cmd, '--exclude' , '/dev/pty*';
+#      push @$cmd, '--exclude' , '/dev/null';
+#      push @$cmd, '--exclude' , '/dev/random';
+#      push @$cmd, '--exclude' , '/dev/urandom';
+#      push @$cmd, '--exclude' , '/dev/console';
+#      push @$cmd, '--exclude' , '/dev/zero';
+#      push @$cmd, '--exclude' , '/dev/ptmx';
+#      push @$cmd, '--exclude' , '/dev/port';
+#      push @$cmd, '--exclude' , '/dev/mem';
+#      push @$cmd, '--exclude' , '/dev/kmem';
+#      push @$cmd, '--exclude' , '/dev/full';
+       
+       print "extracting archive '$archive' to '$private'\n";
+       PVE::Tools::run_command($cmd);
+
+       File::Path::make_path("$private/dev/pts");
+       
+       # template/OS specific configuration
+       $conf->{'lxc.arch'} = 'i386'; # || x86_64
+
+       $conf->{'lxc.include'} = "/usr/share/lxc/config/debian.common.conf";
+
+       if ($userns) {
+           die "also include me";
+           $conf->{'lxc.include'} = "/usr/share/lxc/config/debian.userns.conf";
+       }
+
+       PVE::LXC::write_config($param->{name}, $conf);
+
+       return undef;
+    }});
+
+my $cmddef = [ __PACKAGE__, 'lxc-pve' ];
+
+push @ARGV, 'help' if !scalar(@ARGV);
+
+PVE::CLIHandler::handle_simple_cmd($cmddef, \@ARGV, undef, $0);
+
+exit 0;
+
+__END__
+
+=head1 NAME
+
+lxc-pve - create containers from Proxmox VE templates
+
+=head1 SYNOPSIS
+
+=include synopsis
+
+=head1 DESCRIPTION
+
+This script creates containers from Proxmox VE templates. This is always 
+called from within lxc-create, so please do not try to use this directly. 
+Use 'lct' instead.
+
+=head1 SEE ALSO
+
+lct(1)
+
+=include pve_copyright
diff --git a/src/pct b/src/pct
new file mode 100755 (executable)
index 0000000..f4cd1e0
--- /dev/null
+++ b/src/pct
@@ -0,0 +1,105 @@
+#!/usr/bin/perl -T
+
+use strict;
+use warnings;
+use lib qw(. ..);
+
+use PVE::SafeSyslog;
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::RPCEnvironment;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::CLIHandler;
+use PVE::API2::LXC;
+
+use Data::Dumper;
+
+use base qw(PVE::CLIHandler);
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+initlog ('pct');
+
+die "please run as root\n" if $> != 0;
+
+PVE::INotify::inotify_init();
+
+my $rpcenv = PVE::RPCEnvironment->init('cli');
+$rpcenv->init_request();
+$rpcenv->set_language($ENV{LANG});
+$rpcenv->set_user('root@pam');
+
+my $nodename = PVE::INotify::nodename();
+
+my $upid_exit = sub {
+    my $upid = shift;
+    my $status = PVE::Tools::upid_read_status($upid);
+    exit($status eq 'OK' ? 0 : -1);
+};
+
+__PACKAGE__->register_method ({
+    name => 'test',
+    path => 'test',
+    method => 'GET',
+    description => "Test only",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+       },
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       print "TEST\n";
+       
+       return undef;
+    }});
+
+
+my $cmddef = {
+    test => [ __PACKAGE__, 'test', [], {}, sub {} ],
+    list=> [ 'PVE::API2::LXC', 'vmlist', [], { node => $nodename }, sub {
+       my $res = shift;
+       print Dumper($res);
+            }],
+    config => [ "PVE::API2::LXC", 'vm_config', ['vmid'], 
+               { node => $nodename }, sub {
+                   my $config = shift;
+                   foreach my $k (sort (keys %$config)) {
+                       next if $k eq 'digest';
+                       my $v = $config->{$k};
+                       if ($k eq 'description') {
+                           $v = PVE::Tools::encode_text($v);
+                       }
+                       print "$k: $v\n";
+                   }
+               }],
+
+    destroy => [ 'PVE::API2::LXC', 'destroy_vm', ['vmid'], 
+                { node => $nodename }, $upid_exit ],
+
+};
+                             
+my $cmd = shift;
+
+PVE::CLIHandler::handle_cmd($cmddef, "pct", $cmd, \@ARGV, undef, $0);
+
+exit 0;
+
+__END__
+
+=head1 NAME
+
+pct - Tool to manage Linux Containers on Proxmox VE
+
+=head1 SYNOPSIS
+
+=include synopsis
+
+=head1 DESCRIPTION
+
+Tool to manage linux containers.
+
+=include pve_copyright