[lxc-devel] [PATCH 4/4] python: Remove hardcoded LXCPATH
Stéphane Graber
stgraber at ubuntu.com
Wed Dec 5 23:51:10 UTC 2012
Switch the python scripts to using @LXCPATH at .
According to grep, this was the last occurence of a /var/*/lxc
path in the code.
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
configure.ac | 2 +
src/python-lxc/examples/api_test.py | 151 -----------
src/python-lxc/examples/api_test.py.in | 151 +++++++++++
src/python-lxc/lxc/__init__.py | 473 ---------------------------------
src/python-lxc/lxc/__init__.py.in | 473 +++++++++++++++++++++++++++++++++
5 files changed, 626 insertions(+), 624 deletions(-)
delete mode 100644 src/python-lxc/examples/api_test.py
create mode 100644 src/python-lxc/examples/api_test.py.in
delete mode 100644 src/python-lxc/lxc/__init__.py
create mode 100644 src/python-lxc/lxc/__init__.py.in
diff --git a/configure.ac b/configure.ac
index 893e179..ef321ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -271,6 +271,8 @@ AC_CONFIG_FILES([
src/lxc/legacy/lxc-ls
src/python-lxc/Makefile
+ src/python-lxc/lxc/__init__.py
+ src/python-lxc/examples/api_test.py
src/tests/Makefile
diff --git a/src/python-lxc/examples/api_test.py b/src/python-lxc/examples/api_test.py
deleted file mode 100644
index 3ca0267..0000000
--- a/src/python-lxc/examples/api_test.py
+++ /dev/null
@@ -1,151 +0,0 @@
-#!/usr/bin/python3
-#
-# api_test.py: Test/demo of the python3-lxc API
-#
-# (C) Copyright Canonical Ltd. 2012
-#
-# Authors:
-# Stéphane Graber <stgraber at ubuntu.com>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-
-import warnings
-warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable")
-
-import lxc
-import uuid
-import sys
-
-# Some constants
-LXC_PATH_LIB = "/var/lib/lxc"
-LXC_TEMPLATE = "ubuntu"
-
-# Let's pick a random name, avoiding clashes
-CONTAINER_NAME = str(uuid.uuid1())
-CLONE_NAME = str(uuid.uuid1())
-
-## Instantiate the container instance
-print("Getting instance for '%s'" % CONTAINER_NAME)
-container = lxc.Container(CONTAINER_NAME)
-
-# A few basic checks of the current state
-assert(container.config_file_name == "%s/%s/config" %
- (LXC_PATH_LIB, CONTAINER_NAME))
-assert(not container.defined)
-assert(container.init_pid == -1)
-assert(container.name == CONTAINER_NAME)
-assert(not container.running)
-assert(container.state == "STOPPED")
-
-## Create a rootfs
-print("Creating rootfs using '%s'" % LXC_TEMPLATE)
-container.create(LXC_TEMPLATE)
-
-assert(container.defined)
-assert(container.name == CONTAINER_NAME
- == container.get_config_item("lxc.utsname"))
-assert(container.name in lxc.list_containers())
-
-## Test the config
-print("Testing the configuration")
-capdrop = container.get_config_item("lxc.cap.drop")
-container.clear_config_item("lxc.cap.drop")
-container.set_config_item("lxc.cap.drop", capdrop[:-1])
-container.append_config_item("lxc.cap.drop", capdrop[-1])
-container.save_config()
-
-# A few basic checks of the current state
-assert(isinstance(capdrop, list))
-assert(capdrop == container.get_config_item("lxc.cap.drop"))
-
-## Test the networking
-print("Testing the networking")
-
-# A few basic checks of the current state
-assert("name" in container.get_keys("lxc.network.0"))
-assert(len(container.network) == 1)
-assert(container.network[0].hwaddr.startswith("00:16:3e"))
-
-## Starting the container
-print("Starting the container")
-container.start()
-container.wait("RUNNING", 3)
-
-# A few basic checks of the current state
-assert(container.init_pid > 1)
-assert(container.running)
-assert(container.state == "RUNNING")
-
-## Checking IP address
-print("Getting the IP addresses")
-ips = container.get_ips(timeout=10)
-container.attach("NETWORK|UTSNAME", "/sbin/ifconfig", "eth0")
-
-# A few basic checks of the current state
-assert(len(ips) > 0)
-
-## Freezing the container
-print("Freezing the container")
-container.freeze()
-container.wait("FROZEN", 3)
-
-# A few basic checks of the current state
-assert(container.init_pid > 1)
-assert(container.running)
-assert(container.state == "FROZEN")
-
-## Unfreezing the container
-print("Unfreezing the container")
-container.unfreeze()
-container.wait("RUNNING", 3)
-
-# A few basic checks of the current state
-assert(container.init_pid > 1)
-assert(container.running)
-assert(container.state == "RUNNING")
-
-if len(sys.argv) > 1 and sys.argv[1] == "--with-console":
- ## Attaching to tty1
- print("Attaching to tty1")
- container.console(tty=1)
-
-## Shutting down the container
-print("Shutting down the container")
-container.shutdown(3)
-
-if container.running:
- print("Stopping the container")
- container.stop()
- container.wait("STOPPED", 3)
-
-# A few basic checks of the current state
-assert(container.init_pid == -1)
-assert(not container.running)
-assert(container.state == "STOPPED")
-
-## Cloning the container
-print("Cloning the container")
-clone = lxc.Container(CLONE_NAME)
-clone.clone(container)
-clone.start()
-clone.stop()
-clone.destroy()
-
-## Destroy the container
-print("Destroying the container")
-container.destroy()
-
-assert(not container.defined)
diff --git a/src/python-lxc/examples/api_test.py.in b/src/python-lxc/examples/api_test.py.in
new file mode 100644
index 0000000..0b17bd6
--- /dev/null
+++ b/src/python-lxc/examples/api_test.py.in
@@ -0,0 +1,151 @@
+#!/usr/bin/python3
+#
+# api_test.py: Test/demo of the python3-lxc API
+#
+# (C) Copyright Canonical Ltd. 2012
+#
+# Authors:
+# Stéphane Graber <stgraber at ubuntu.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import warnings
+warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable")
+
+import lxc
+import uuid
+import sys
+
+# Some constants
+LXC_PATH_LIB = "@LXCPATH@"
+LXC_TEMPLATE = "ubuntu"
+
+# Let's pick a random name, avoiding clashes
+CONTAINER_NAME = str(uuid.uuid1())
+CLONE_NAME = str(uuid.uuid1())
+
+## Instantiate the container instance
+print("Getting instance for '%s'" % CONTAINER_NAME)
+container = lxc.Container(CONTAINER_NAME)
+
+# A few basic checks of the current state
+assert(container.config_file_name == "%s/%s/config" %
+ (LXC_PATH_LIB, CONTAINER_NAME))
+assert(not container.defined)
+assert(container.init_pid == -1)
+assert(container.name == CONTAINER_NAME)
+assert(not container.running)
+assert(container.state == "STOPPED")
+
+## Create a rootfs
+print("Creating rootfs using '%s'" % LXC_TEMPLATE)
+container.create(LXC_TEMPLATE)
+
+assert(container.defined)
+assert(container.name == CONTAINER_NAME
+ == container.get_config_item("lxc.utsname"))
+assert(container.name in lxc.list_containers())
+
+## Test the config
+print("Testing the configuration")
+capdrop = container.get_config_item("lxc.cap.drop")
+container.clear_config_item("lxc.cap.drop")
+container.set_config_item("lxc.cap.drop", capdrop[:-1])
+container.append_config_item("lxc.cap.drop", capdrop[-1])
+container.save_config()
+
+# A few basic checks of the current state
+assert(isinstance(capdrop, list))
+assert(capdrop == container.get_config_item("lxc.cap.drop"))
+
+## Test the networking
+print("Testing the networking")
+
+# A few basic checks of the current state
+assert("name" in container.get_keys("lxc.network.0"))
+assert(len(container.network) == 1)
+assert(container.network[0].hwaddr.startswith("00:16:3e"))
+
+## Starting the container
+print("Starting the container")
+container.start()
+container.wait("RUNNING", 3)
+
+# A few basic checks of the current state
+assert(container.init_pid > 1)
+assert(container.running)
+assert(container.state == "RUNNING")
+
+## Checking IP address
+print("Getting the IP addresses")
+ips = container.get_ips(timeout=10)
+container.attach("NETWORK|UTSNAME", "/sbin/ifconfig", "eth0")
+
+# A few basic checks of the current state
+assert(len(ips) > 0)
+
+## Freezing the container
+print("Freezing the container")
+container.freeze()
+container.wait("FROZEN", 3)
+
+# A few basic checks of the current state
+assert(container.init_pid > 1)
+assert(container.running)
+assert(container.state == "FROZEN")
+
+## Unfreezing the container
+print("Unfreezing the container")
+container.unfreeze()
+container.wait("RUNNING", 3)
+
+# A few basic checks of the current state
+assert(container.init_pid > 1)
+assert(container.running)
+assert(container.state == "RUNNING")
+
+if len(sys.argv) > 1 and sys.argv[1] == "--with-console":
+ ## Attaching to tty1
+ print("Attaching to tty1")
+ container.console(tty=1)
+
+## Shutting down the container
+print("Shutting down the container")
+container.shutdown(3)
+
+if container.running:
+ print("Stopping the container")
+ container.stop()
+ container.wait("STOPPED", 3)
+
+# A few basic checks of the current state
+assert(container.init_pid == -1)
+assert(not container.running)
+assert(container.state == "STOPPED")
+
+## Cloning the container
+print("Cloning the container")
+clone = lxc.Container(CLONE_NAME)
+clone.clone(container)
+clone.start()
+clone.stop()
+clone.destroy()
+
+## Destroy the container
+print("Destroying the container")
+container.destroy()
+
+assert(not container.defined)
diff --git a/src/python-lxc/lxc/__init__.py b/src/python-lxc/lxc/__init__.py
deleted file mode 100644
index cde4fd1..0000000
--- a/src/python-lxc/lxc/__init__.py
+++ /dev/null
@@ -1,473 +0,0 @@
-#
-# python-lxc: Python bindings for LXC
-#
-# (C) Copyright Canonical Ltd. 2012
-#
-# Authors:
-# Stéphane Graber <stgraber at ubuntu.com>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-
-import _lxc
-import glob
-import os
-import subprocess
-import stat
-import tempfile
-import time
-import warnings
-
-warnings.warn("The python-lxc API isn't yet stable "
- "and may change at any point in the future.", Warning, 2)
-
-
-class ContainerNetwork():
- props = {}
-
- def __init__(self, container, index):
- self.container = container
- self.index = index
-
- for key in self.container.get_keys("lxc.network.%s" % self.index):
- if "." in key:
- self.props[key.replace(".", "_")] = key
- else:
- self.props[key] = key
-
- if not self.props:
- return False
-
- def __delattr__(self, key):
- if key in ["container", "index", "props"]:
- return object.__delattr__(self, key)
-
- if key not in self.props:
- raise AttributeError("'%s' network has no attribute '%s'" % (
- self.__get_network_item("type"), key))
-
- return self.__clear_network_item(self.props[key])
-
- def __dir__(self):
- return sorted(self.props.keys())
-
- def __getattr__(self, key):
- if key in ["container", "index", "props"]:
- return object.__getattribute__(self, key)
-
- if key not in self.props:
- raise AttributeError("'%s' network has no attribute '%s'" % (
- self.__get_network_item("type"), key))
-
- return self.__get_network_item(self.props[key])
-
- def __hasattr__(self, key):
- if key in ["container", "index", "props"]:
- return object.__hasattr__(self, key)
-
- if key not in self.props:
- raise AttributeError("'%s' network has no attribute '%s'" % (
- self.__get_network_item("type"), key))
-
- return True
-
- def __repr__(self):
- return "'%s' network at index '%s'" % (
- self.__get_network_item("type"), self.index)
-
- def __setattr__(self, key, value):
- if key in ["container", "index", "props"]:
- return object.__setattr__(self, key, value)
-
- if key not in self.props:
- raise AttributeError("'%s' network has no attribute '%s'" % (
- self.__get_network_item("type"), key))
-
- return self.__set_network_item(self.props[key], value)
-
- def __clear_network_item(self, key):
- return self.container.clear_config_item("lxc.network.%s.%s" % (
- self.index, key))
-
- def __get_network_item(self, key):
- return self.container.get_config_item("lxc.network.%s.%s" % (
- self.index, key))
-
- def __set_network_item(self, key, value):
- return self.container.set_config_item("lxc.network.%s.%s" % (
- self.index, key), value)
-
-
-class ContainerNetworkList():
- def __init__(self, container):
- self.container = container
-
- def __getitem__(self, index):
- if index >= len(self):
- raise IndexError("list index out of range")
-
- return ContainerNetwork(self.container, index)
-
- def __len__(self):
- values = self.container.get_config_item("lxc.network")
-
- if values:
- return len(values)
- else:
- return 0
-
- def add(self, network_type):
- index = len(self)
-
- return self.container.set_config_item("lxc.network.%s.type" % index,
- network_type)
-
- def remove(self, index):
- count = len(self)
- if index >= count:
- raise IndexError("list index out of range")
-
- return self.container.clear_config_item("lxc.network.%s" % index)
-
-
-class Container(_lxc.Container):
- def __init__(self, name):
- """
- Creates a new Container instance.
- """
-
- if os.geteuid() != 0:
- raise Exception("Running as non-root.")
-
- _lxc.Container.__init__(self, name)
- self.network = ContainerNetworkList(self)
-
- def add_device_node(self, path, destpath=None):
- """
- Add block/char device to running container.
- """
-
- if not self.running:
- return False
-
- if not destpath:
- destpath = path
-
- if not os.path.exists(path):
- return False
-
- # Lookup the source
- path_stat = os.stat(path)
- mode = stat.S_IMODE(path_stat.st_mode)
-
- # Lookup the cgroup
- cgroup_path = None
- with open("/proc/%s/cgroup" % self.init_pid, "r") as fd:
- for line in fd:
- if ":devices:" in line:
- cgroup_path = line.split(":")[-1].strip()
- break
- else:
- return False
-
- # Lookup the cgroup mount point
- cgroup = None
- with open("/proc/mounts", "r") as fd:
- for line in fd:
- mount = line.split()
- if (mount[2] == "cgroup" and "devices" in mount[3]
- and os.path.exists("%s/%s" % (mount[1], cgroup_path))):
- cgroup = "%s/%s" % (mount[1], cgroup_path)
- break
-
- if not os.path.exists(cgroup):
- return False
-
- # Allow the target
- with open("%s/devices.allow" % cgroup, "a") as fd:
- if stat.S_ISBLK(path_stat.st_mode):
- fd.write("b %s:%s rwm" % (int(path_stat.st_rdev / 256),
- int(path_stat.st_rdev % 256)))
- elif stat.S_ISCHR(path_stat.st_mode):
- fd.write("c %s:%s rwm" % (int(path_stat.st_rdev / 256),
- int(path_stat.st_rdev % 256)))
-
- # Create the target
- rootfs = "/proc/%s/root/" % self.init_pid
- container_path = "%s/%s" % (rootfs, destpath)
-
- if os.path.exists(container_path):
- os.remove(container_path)
-
- os.mknod(container_path, path_stat.st_mode, path_stat.st_rdev)
- os.chmod(container_path, mode)
- os.chown(container_path, 0, 0)
-
- return True
-
- def add_device_net(self, name, destname=None):
- """
- Add network device to running container.
- """
-
- if not self.running:
- return False
-
- if not destname:
- destname = name
-
- if not os.path.exists("/sys/class/net/%s/" % name):
- return False
-
- return subprocess.call(['ip', 'link', 'set',
- 'dev', name,
- 'netns', str(self.init_pid),
- 'name', destname]) == 0
-
- def append_config_item(self, key, value):
- """
- Append 'value' to 'key', assuming 'key' is a list.
- If 'key' isn't a list, 'value' will be set as the value of 'key'.
- """
-
- return _lxc.Container.set_config_item(self, key, value)
-
- def attach(self, namespace="ALL", *cmd):
- """
- Attach to a running container.
- """
-
- if not self.running:
- return False
-
- attach = ["lxc-attach", "-n", self.name]
- if namespace != "ALL":
- attach += ["-s", namespace]
-
- if cmd:
- attach += ["--"] + list(cmd)
-
- if subprocess.call(
- attach,
- universal_newlines=True) != 0:
- return False
- return True
-
- def create(self, template, args={}):
- """
- Create a new rootfs for the container.
-
- "template" must be a valid template name.
-
- "args" (optional) is a dictionary of parameters and values to pass
- to the template.
- """
-
- template_args = []
- for item in args.items():
- template_args.append("--%s" % item[0])
- template_args.append("%s" % item[1])
-
- return _lxc.Container.create(self, template, tuple(template_args))
-
- def clone(self, container):
- """
- Clone an existing container into a new one.
- """
-
- if self.defined:
- return False
-
- if isinstance(container, Container):
- source = container
- else:
- source = Container(container)
-
- if not source.defined:
- return False
-
- if subprocess.call(["lxc-clone", "-o", source.name, "-n", self.name],
- universal_newlines=True) != 0:
- return False
-
- self.load_config()
- return True
-
- def console(self, tty="1"):
- """
- Access the console of a container.
- """
-
- if not self.running:
- return False
-
- if subprocess.call(["lxc-console", "-n", self.name, "-t", "%s" % tty],
- universal_newlines=True) != 0:
- return False
- return True
-
- def get_config_item(self, key):
- """
- Returns the value for a given config key.
- A list is returned when multiple values are set.
- """
- value = _lxc.Container.get_config_item(self, key)
-
- if value is False:
- return False
- elif value.endswith("\n"):
- return value.rstrip("\n").split("\n")
- else:
- return value
-
- def get_ips(self, timeout=60, interface=None, protocol=None):
- """
- Returns the list of IP addresses for the container.
- """
-
- if not self.defined or not self.running:
- return False
-
- try:
- os.makedirs("/run/netns")
- except:
- pass
-
- path = tempfile.mktemp(dir="/run/netns")
-
- os.symlink("/proc/%s/ns/net" % self.init_pid, path)
-
- ips = []
-
- count = 0
- while count < timeout:
- if count != 0:
- time.sleep(1)
-
- base_cmd = ["ip", "netns", "exec", path.split("/")[-1], "ip"]
-
- # Get IPv6
- if protocol in ("ipv6", None):
- ip6_cmd = base_cmd + ["-6", "addr", "show", "scope", "global"]
- if interface:
- ip = subprocess.Popen(ip6_cmd + ["dev", interface],
- stdout=subprocess.PIPE,
- universal_newlines=True)
- else:
- ip = subprocess.Popen(ip6_cmd, stdout=subprocess.PIPE,
- universal_newlines=True)
-
- ip.wait()
- for line in ip.stdout.read().split("\n"):
- fields = line.split()
- if len(fields) > 2 and fields[0] == "inet6":
- ips.append(fields[1].split('/')[0])
-
- # Get IPv4
- if protocol in ("ipv4", None):
- ip4_cmd = base_cmd + ["-4", "addr", "show", "scope", "global"]
- if interface:
- ip = subprocess.Popen(ip4_cmd + ["dev", interface],
- stdout=subprocess.PIPE,
- universal_newlines=True)
- else:
- ip = subprocess.Popen(ip4_cmd, stdout=subprocess.PIPE,
- universal_newlines=True)
-
- ip.wait()
- for line in ip.stdout.read().split("\n"):
- fields = line.split()
- if len(fields) > 2 and fields[0] == "inet":
- ips.append(fields[1].split('/')[0])
-
- if ips:
- break
-
- count += 1
-
- os.remove(path)
- return ips
-
- def get_keys(self, key):
- """
- Returns a list of valid sub-keys.
- """
- value = _lxc.Container.get_keys(self, key)
-
- if value is False:
- return False
- elif value.endswith("\n"):
- return value.rstrip("\n").split("\n")
- else:
- return value
-
- def set_config_item(self, key, value):
- """
- Set a config key to a provided value.
- The value can be a list for the keys supporting multiple values.
- """
- old_value = self.get_config_item(key)
-
- # Check if it's a list
- def set_key(key, value):
- self.clear_config_item(key)
- if isinstance(value, list):
- for entry in value:
- if not _lxc.Container.set_config_item(self, key, entry):
- return False
- else:
- _lxc.Container.set_config_item(self, key, value)
-
- set_key(key, value)
- new_value = self.get_config_item(key)
-
- if (isinstance(value, str) and isinstance(new_value, str) and
- value == new_value):
- return True
- elif (isinstance(value, list) and isinstance(new_value, list) and
- set(value) == set(new_value)):
- return True
- elif (isinstance(value, str) and isinstance(new_value, list) and
- set([value]) == set(new_value)):
- return True
- elif old_value:
- set_key(key, old_value)
- return False
- else:
- self.clear_config_item(key)
- return False
-
- def wait(self, state, timeout=-1):
- """
- Wait for the container to reach a given state or timeout.
- """
-
- if isinstance(state, str):
- state = state.upper()
-
- return _lxc.Container.wait(self, state, timeout)
-
-
-def list_containers(as_object=False):
- """
- List the containers on the system.
- """
- containers = []
- for entry in glob.glob("/var/lib/lxc/*/config"):
- if as_object:
- containers.append(Container(entry.split("/")[-2]))
- else:
- containers.append(entry.split("/")[-2])
- return containers
diff --git a/src/python-lxc/lxc/__init__.py.in b/src/python-lxc/lxc/__init__.py.in
new file mode 100644
index 0000000..f42b944
--- /dev/null
+++ b/src/python-lxc/lxc/__init__.py.in
@@ -0,0 +1,473 @@
+#
+# python-lxc: Python bindings for LXC
+#
+# (C) Copyright Canonical Ltd. 2012
+#
+# Authors:
+# Stéphane Graber <stgraber at ubuntu.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import _lxc
+import glob
+import os
+import subprocess
+import stat
+import tempfile
+import time
+import warnings
+
+warnings.warn("The python-lxc API isn't yet stable "
+ "and may change at any point in the future.", Warning, 2)
+
+
+class ContainerNetwork():
+ props = {}
+
+ def __init__(self, container, index):
+ self.container = container
+ self.index = index
+
+ for key in self.container.get_keys("lxc.network.%s" % self.index):
+ if "." in key:
+ self.props[key.replace(".", "_")] = key
+ else:
+ self.props[key] = key
+
+ if not self.props:
+ return False
+
+ def __delattr__(self, key):
+ if key in ["container", "index", "props"]:
+ return object.__delattr__(self, key)
+
+ if key not in self.props:
+ raise AttributeError("'%s' network has no attribute '%s'" % (
+ self.__get_network_item("type"), key))
+
+ return self.__clear_network_item(self.props[key])
+
+ def __dir__(self):
+ return sorted(self.props.keys())
+
+ def __getattr__(self, key):
+ if key in ["container", "index", "props"]:
+ return object.__getattribute__(self, key)
+
+ if key not in self.props:
+ raise AttributeError("'%s' network has no attribute '%s'" % (
+ self.__get_network_item("type"), key))
+
+ return self.__get_network_item(self.props[key])
+
+ def __hasattr__(self, key):
+ if key in ["container", "index", "props"]:
+ return object.__hasattr__(self, key)
+
+ if key not in self.props:
+ raise AttributeError("'%s' network has no attribute '%s'" % (
+ self.__get_network_item("type"), key))
+
+ return True
+
+ def __repr__(self):
+ return "'%s' network at index '%s'" % (
+ self.__get_network_item("type"), self.index)
+
+ def __setattr__(self, key, value):
+ if key in ["container", "index", "props"]:
+ return object.__setattr__(self, key, value)
+
+ if key not in self.props:
+ raise AttributeError("'%s' network has no attribute '%s'" % (
+ self.__get_network_item("type"), key))
+
+ return self.__set_network_item(self.props[key], value)
+
+ def __clear_network_item(self, key):
+ return self.container.clear_config_item("lxc.network.%s.%s" % (
+ self.index, key))
+
+ def __get_network_item(self, key):
+ return self.container.get_config_item("lxc.network.%s.%s" % (
+ self.index, key))
+
+ def __set_network_item(self, key, value):
+ return self.container.set_config_item("lxc.network.%s.%s" % (
+ self.index, key), value)
+
+
+class ContainerNetworkList():
+ def __init__(self, container):
+ self.container = container
+
+ def __getitem__(self, index):
+ if index >= len(self):
+ raise IndexError("list index out of range")
+
+ return ContainerNetwork(self.container, index)
+
+ def __len__(self):
+ values = self.container.get_config_item("lxc.network")
+
+ if values:
+ return len(values)
+ else:
+ return 0
+
+ def add(self, network_type):
+ index = len(self)
+
+ return self.container.set_config_item("lxc.network.%s.type" % index,
+ network_type)
+
+ def remove(self, index):
+ count = len(self)
+ if index >= count:
+ raise IndexError("list index out of range")
+
+ return self.container.clear_config_item("lxc.network.%s" % index)
+
+
+class Container(_lxc.Container):
+ def __init__(self, name):
+ """
+ Creates a new Container instance.
+ """
+
+ if os.geteuid() != 0:
+ raise Exception("Running as non-root.")
+
+ _lxc.Container.__init__(self, name)
+ self.network = ContainerNetworkList(self)
+
+ def add_device_node(self, path, destpath=None):
+ """
+ Add block/char device to running container.
+ """
+
+ if not self.running:
+ return False
+
+ if not destpath:
+ destpath = path
+
+ if not os.path.exists(path):
+ return False
+
+ # Lookup the source
+ path_stat = os.stat(path)
+ mode = stat.S_IMODE(path_stat.st_mode)
+
+ # Lookup the cgroup
+ cgroup_path = None
+ with open("/proc/%s/cgroup" % self.init_pid, "r") as fd:
+ for line in fd:
+ if ":devices:" in line:
+ cgroup_path = line.split(":")[-1].strip()
+ break
+ else:
+ return False
+
+ # Lookup the cgroup mount point
+ cgroup = None
+ with open("/proc/mounts", "r") as fd:
+ for line in fd:
+ mount = line.split()
+ if (mount[2] == "cgroup" and "devices" in mount[3]
+ and os.path.exists("%s/%s" % (mount[1], cgroup_path))):
+ cgroup = "%s/%s" % (mount[1], cgroup_path)
+ break
+
+ if not os.path.exists(cgroup):
+ return False
+
+ # Allow the target
+ with open("%s/devices.allow" % cgroup, "a") as fd:
+ if stat.S_ISBLK(path_stat.st_mode):
+ fd.write("b %s:%s rwm" % (int(path_stat.st_rdev / 256),
+ int(path_stat.st_rdev % 256)))
+ elif stat.S_ISCHR(path_stat.st_mode):
+ fd.write("c %s:%s rwm" % (int(path_stat.st_rdev / 256),
+ int(path_stat.st_rdev % 256)))
+
+ # Create the target
+ rootfs = "/proc/%s/root/" % self.init_pid
+ container_path = "%s/%s" % (rootfs, destpath)
+
+ if os.path.exists(container_path):
+ os.remove(container_path)
+
+ os.mknod(container_path, path_stat.st_mode, path_stat.st_rdev)
+ os.chmod(container_path, mode)
+ os.chown(container_path, 0, 0)
+
+ return True
+
+ def add_device_net(self, name, destname=None):
+ """
+ Add network device to running container.
+ """
+
+ if not self.running:
+ return False
+
+ if not destname:
+ destname = name
+
+ if not os.path.exists("/sys/class/net/%s/" % name):
+ return False
+
+ return subprocess.call(['ip', 'link', 'set',
+ 'dev', name,
+ 'netns', str(self.init_pid),
+ 'name', destname]) == 0
+
+ def append_config_item(self, key, value):
+ """
+ Append 'value' to 'key', assuming 'key' is a list.
+ If 'key' isn't a list, 'value' will be set as the value of 'key'.
+ """
+
+ return _lxc.Container.set_config_item(self, key, value)
+
+ def attach(self, namespace="ALL", *cmd):
+ """
+ Attach to a running container.
+ """
+
+ if not self.running:
+ return False
+
+ attach = ["lxc-attach", "-n", self.name]
+ if namespace != "ALL":
+ attach += ["-s", namespace]
+
+ if cmd:
+ attach += ["--"] + list(cmd)
+
+ if subprocess.call(
+ attach,
+ universal_newlines=True) != 0:
+ return False
+ return True
+
+ def create(self, template, args={}):
+ """
+ Create a new rootfs for the container.
+
+ "template" must be a valid template name.
+
+ "args" (optional) is a dictionary of parameters and values to pass
+ to the template.
+ """
+
+ template_args = []
+ for item in args.items():
+ template_args.append("--%s" % item[0])
+ template_args.append("%s" % item[1])
+
+ return _lxc.Container.create(self, template, tuple(template_args))
+
+ def clone(self, container):
+ """
+ Clone an existing container into a new one.
+ """
+
+ if self.defined:
+ return False
+
+ if isinstance(container, Container):
+ source = container
+ else:
+ source = Container(container)
+
+ if not source.defined:
+ return False
+
+ if subprocess.call(["lxc-clone", "-o", source.name, "-n", self.name],
+ universal_newlines=True) != 0:
+ return False
+
+ self.load_config()
+ return True
+
+ def console(self, tty="1"):
+ """
+ Access the console of a container.
+ """
+
+ if not self.running:
+ return False
+
+ if subprocess.call(["lxc-console", "-n", self.name, "-t", "%s" % tty],
+ universal_newlines=True) != 0:
+ return False
+ return True
+
+ def get_config_item(self, key):
+ """
+ Returns the value for a given config key.
+ A list is returned when multiple values are set.
+ """
+ value = _lxc.Container.get_config_item(self, key)
+
+ if value is False:
+ return False
+ elif value.endswith("\n"):
+ return value.rstrip("\n").split("\n")
+ else:
+ return value
+
+ def get_ips(self, timeout=60, interface=None, protocol=None):
+ """
+ Returns the list of IP addresses for the container.
+ """
+
+ if not self.defined or not self.running:
+ return False
+
+ try:
+ os.makedirs("/run/netns")
+ except:
+ pass
+
+ path = tempfile.mktemp(dir="/run/netns")
+
+ os.symlink("/proc/%s/ns/net" % self.init_pid, path)
+
+ ips = []
+
+ count = 0
+ while count < timeout:
+ if count != 0:
+ time.sleep(1)
+
+ base_cmd = ["ip", "netns", "exec", path.split("/")[-1], "ip"]
+
+ # Get IPv6
+ if protocol in ("ipv6", None):
+ ip6_cmd = base_cmd + ["-6", "addr", "show", "scope", "global"]
+ if interface:
+ ip = subprocess.Popen(ip6_cmd + ["dev", interface],
+ stdout=subprocess.PIPE,
+ universal_newlines=True)
+ else:
+ ip = subprocess.Popen(ip6_cmd, stdout=subprocess.PIPE,
+ universal_newlines=True)
+
+ ip.wait()
+ for line in ip.stdout.read().split("\n"):
+ fields = line.split()
+ if len(fields) > 2 and fields[0] == "inet6":
+ ips.append(fields[1].split('/')[0])
+
+ # Get IPv4
+ if protocol in ("ipv4", None):
+ ip4_cmd = base_cmd + ["-4", "addr", "show", "scope", "global"]
+ if interface:
+ ip = subprocess.Popen(ip4_cmd + ["dev", interface],
+ stdout=subprocess.PIPE,
+ universal_newlines=True)
+ else:
+ ip = subprocess.Popen(ip4_cmd, stdout=subprocess.PIPE,
+ universal_newlines=True)
+
+ ip.wait()
+ for line in ip.stdout.read().split("\n"):
+ fields = line.split()
+ if len(fields) > 2 and fields[0] == "inet":
+ ips.append(fields[1].split('/')[0])
+
+ if ips:
+ break
+
+ count += 1
+
+ os.remove(path)
+ return ips
+
+ def get_keys(self, key):
+ """
+ Returns a list of valid sub-keys.
+ """
+ value = _lxc.Container.get_keys(self, key)
+
+ if value is False:
+ return False
+ elif value.endswith("\n"):
+ return value.rstrip("\n").split("\n")
+ else:
+ return value
+
+ def set_config_item(self, key, value):
+ """
+ Set a config key to a provided value.
+ The value can be a list for the keys supporting multiple values.
+ """
+ old_value = self.get_config_item(key)
+
+ # Check if it's a list
+ def set_key(key, value):
+ self.clear_config_item(key)
+ if isinstance(value, list):
+ for entry in value:
+ if not _lxc.Container.set_config_item(self, key, entry):
+ return False
+ else:
+ _lxc.Container.set_config_item(self, key, value)
+
+ set_key(key, value)
+ new_value = self.get_config_item(key)
+
+ if (isinstance(value, str) and isinstance(new_value, str) and
+ value == new_value):
+ return True
+ elif (isinstance(value, list) and isinstance(new_value, list) and
+ set(value) == set(new_value)):
+ return True
+ elif (isinstance(value, str) and isinstance(new_value, list) and
+ set([value]) == set(new_value)):
+ return True
+ elif old_value:
+ set_key(key, old_value)
+ return False
+ else:
+ self.clear_config_item(key)
+ return False
+
+ def wait(self, state, timeout=-1):
+ """
+ Wait for the container to reach a given state or timeout.
+ """
+
+ if isinstance(state, str):
+ state = state.upper()
+
+ return _lxc.Container.wait(self, state, timeout)
+
+
+def list_containers(as_object=False):
+ """
+ List the containers on the system.
+ """
+ containers = []
+ for entry in glob.glob("@LXCPATH@/*/config"):
+ if as_object:
+ containers.append(Container(entry.split("/")[-2]))
+ else:
+ containers.append(entry.split("/")[-2])
+ return containers
--
1.8.0
More information about the lxc-devel
mailing list