1
0
mirror of https://github.com/lxc/python2-lxc.git synced 2026-02-05 09:48:07 +01:00

Initial release of basic python2 binding

This is the first release of a basic out of tree python2 binding. It's
not entirely in sync with the python3 (lacks arch_to_personality) but
otherwise should behave identically.

This binding is not officially supported by LXC upstream as part of
1.0.x nor do we have any plan to include it for any upcoming release. It
is just released as a convenience for those who unfortunately can't yet
use python3.

Thanks to Dwight Engen for the original patchset!

Signed-off-by: Stéphane Graber <stgraber@ubuntu.com>
This commit is contained in:
Stéphane Graber
2014-05-05 22:38:22 -05:00
commit 9541b38038
4 changed files with 2339 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build/*
*.pyc

1801
lxc.c Normal file

File diff suppressed because it is too large Load Diff

503
lxc/__init__.py Normal file
View File

@@ -0,0 +1,503 @@
#
# -*- coding: utf-8 -*-
# python-lxc: Python bindings for LXC
#
# (C) Copyright Canonical Ltd. 2012
#
# Authors:
# Stéphane Graber <stgraber@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
#
import _lxc
import os
import subprocess
import time
default_config_path = _lxc.get_global_config_item("lxc.lxcpath")
get_global_config_item = _lxc.get_global_config_item
version = _lxc.get_version()
class ContainerNetwork(object):
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, config_path=None):
"""
Creates a new Container instance.
"""
if config_path:
_lxc.Container.__init__(self, name, config_path)
else:
_lxc.Container.__init__(self, name)
self.network = ContainerNetworkList(self)
def add_device_net(self, name, destname=None):
"""
Add network device to running container.
"""
if not self.running:
return False
if os.path.exists("/sys/class/net/%s/phy80211/name" % name):
with open("/sys/class/net/%s/phy80211/name" % name) as fd:
phy = fd.read().strip()
if subprocess.call(['iw', 'phy', phy, 'set', 'netns',
str(self.init_pid)]) != 0:
return False
if destname:
def rename_interface(args):
old, new = args
return subprocess.call(['ip', 'link', 'set',
'dev', old, 'name', new])
return self.attach_wait(rename_interface, (name, destname),
namespaces=(CLONE_NEWNET)) == 0
return True
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 create(self, template, flags=0, args=()):
"""
Create a new rootfs for the container.
"template" must be a valid template name.
"flags" (optional) is an integer representing the optional
create flags to be passed.
"args" (optional) is a tuple of arguments to pass to the
template. It can also be provided as a dict.
"""
if isinstance(args, dict):
template_args = []
for item in args.items():
template_args.append("--%s" % item[0])
template_args.append("%s" % item[1])
else:
template_args = args
return _lxc.Container.create(self, template=template,
flags=flags, args=tuple(template_args))
def clone(self, newname, config_path=None, flags=0, bdevtype=None,
bdevdata=None, newsize=0, hookargs=()):
"""
Clone the current container.
"""
args = {}
args['newname'] = newname
args['flags'] = flags
args['newsize'] = newsize
args['hookargs'] = hookargs
if config_path:
args['config_path'] = config_path
if bdevtype:
args['bdevtype'] = bdevtype
if bdevdata:
args['bdevdata'] = bdevdata
if _lxc.Container.clone(self, **args):
return Container(newname, config_path=config_path)
else:
return False
def console(self, ttynum=-1, stdinfd=0, stdoutfd=1, stderrfd=2, escape=1):
"""
Attach to console of running container.
"""
if not self.running:
return False
return _lxc.Container.console(self, ttynum, stdinfd, stdoutfd,
stderrfd, escape)
def console_getfd(self, ttynum=-1):
"""
Attach to console of running container.
"""
if not self.running:
return False
return _lxc.Container.console_getfd(self, ttynum)
def get_cgroup_item(self, key):
"""
Returns the value for a given cgroup entry.
A list is returned when multiple values are set.
"""
value = _lxc.Container.get_cgroup_item(self, key)
if value is False:
return False
else:
return value.rstrip("\n")
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_keys(self, key=None):
"""
Returns a list of valid sub-keys.
"""
if key:
value = _lxc.Container.get_keys(self, key)
else:
value = _lxc.Container.get_keys(self)
if value is False:
return False
elif value.endswith("\n"):
return value.rstrip("\n").split("\n")
else:
return value
def get_interfaces(self):
"""
Get a tuple of interfaces for the container.
"""
return _lxc.Container.get_interfaces(self)
def get_ips(self, interface=None, family=None, scope=None, timeout=0):
"""
Get a tuple of IPs for the container.
"""
kwargs = {}
if interface:
kwargs['interface'] = interface
if family:
kwargs['family'] = family
if scope:
kwargs['scope'] = scope
ips = None
timeout = int(os.environ.get('LXC_GETIP_TIMEOUT', timeout))
while not ips:
ips = _lxc.Container.get_ips(self, **kwargs)
if timeout == 0:
break
timeout -= 1
time.sleep(1)
return ips
def rename(self, new_name):
"""
Rename the container.
On success, returns the new Container object.
On failure, returns False.
"""
if _lxc.Container.rename(self, new_name):
return Container(new_name)
return False
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.
"""
try:
old_value = self.get_config_item(key)
except KeyError:
old_value = None
# 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)
# loglevel is special and won't match the string we set
if key == "lxc.loglevel":
new_value = value
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(active=True, defined=True,
as_object=False, config_path=None):
"""
List the containers on the system.
"""
if config_path:
if not os.path.exists(config_path):
return tuple()
try:
entries = _lxc.list_containers(active=active, defined=defined,
config_path=config_path)
except ValueError:
return tuple()
else:
try:
entries = _lxc.list_containers(active=active, defined=defined)
except ValueError:
return tuple()
if as_object:
return tuple([Container(name, config_path) for name in entries])
else:
return entries
def attach_run_command(cmd):
"""
Run a command when attaching
Please do not call directly, this will execvp the command.
This is to be used in conjunction with the attach method
of a container.
"""
if isinstance(cmd, tuple):
return _lxc.attach_run_command(cmd)
elif isinstance(cmd, list):
return _lxc.attach_run_command((cmd[0], cmd))
else:
return _lxc.attach_run_command((cmd, [cmd]))
def attach_run_shell():
"""
Run a shell when attaching
Please do not call directly, this will execvp the shell.
This is to be used in conjunction with the attach method
of a container.
"""
return _lxc.attach_run_shell(None)
def arch_to_personality(arch):
"""
Determine the process personality corresponding to the architecture
"""
if isinstance(arch, bytes):
arch = str(arch, 'utf-8')
return _lxc.arch_to_personality(arch)
# namespace flags (no other python lib exports this)
CLONE_NEWIPC = _lxc.CLONE_NEWIPC
CLONE_NEWNET = _lxc.CLONE_NEWNET
CLONE_NEWNS = _lxc.CLONE_NEWNS
CLONE_NEWPID = _lxc.CLONE_NEWPID
CLONE_NEWUSER = _lxc.CLONE_NEWUSER
CLONE_NEWUTS = _lxc.CLONE_NEWUTS
# attach: environment variable handling
LXC_ATTACH_CLEAR_ENV = _lxc.LXC_ATTACH_CLEAR_ENV
LXC_ATTACH_KEEP_ENV = _lxc.LXC_ATTACH_KEEP_ENV
# attach: attach options
LXC_ATTACH_DEFAULT = _lxc.LXC_ATTACH_DEFAULT
LXC_ATTACH_DROP_CAPABILITIES = _lxc.LXC_ATTACH_DROP_CAPABILITIES
LXC_ATTACH_LSM_EXEC = _lxc.LXC_ATTACH_LSM_EXEC
LXC_ATTACH_LSM_NOW = _lxc.LXC_ATTACH_LSM_NOW
LXC_ATTACH_MOVE_TO_CGROUP = _lxc.LXC_ATTACH_MOVE_TO_CGROUP
LXC_ATTACH_REMOUNT_PROC_SYS = _lxc.LXC_ATTACH_REMOUNT_PROC_SYS
LXC_ATTACH_SET_PERSONALITY = _lxc.LXC_ATTACH_SET_PERSONALITY
# clone: clone flags
LXC_CLONE_KEEPBDEVTYPE = _lxc.LXC_CLONE_KEEPBDEVTYPE
LXC_CLONE_KEEPMACADDR = _lxc.LXC_CLONE_KEEPMACADDR
LXC_CLONE_KEEPNAME = _lxc.LXC_CLONE_KEEPNAME
LXC_CLONE_MAYBE_SNAPSHOT = _lxc.LXC_CLONE_MAYBE_SNAPSHOT
LXC_CLONE_SNAPSHOT = _lxc.LXC_CLONE_SNAPSHOT
# create: create flags
LXC_CREATE_QUIET = _lxc.LXC_CREATE_QUIET

33
setup.py Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/python
#
# python-lxc: Python bindings for LXC
#
# (C) Copyright Canonical Ltd. 2012
#
# Authors:
# Stephane Graber <stgraber@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from distutils.core import setup, Extension
module = Extension('_lxc', sources=['lxc.c'], libraries=['lxc'])
setup(name='_lxc',
version='0.1',
description='LXC',
packages=['lxc'],
package_dir={'lxc': 'lxc'},
ext_modules=[module])