# coding: utf-8 import subprocess import logging import threading import select import pty import os import signal class ContainerAlreadyExists(Exception): pass class ContainerAlreadyRunning(Exception): pass class ContainerNotExists(Exception): pass _logger = logging.getLogger("pylxc") _monitor = None class _LXCMonitor(threading.Thread): def __init__(self): threading.Thread.__init__(self) self._process = None self._monitors = {} def run(self): master, slave = pty.openpty() cmd = ['/usr/bin/lxc-monitor', '-n', '.*'] self._process = subprocess.Popen(cmd, stdout=slave, bufsize=1) stdout = os.fdopen(master) while self._process.poll() is None: ready, _, _ = select.select([stdout], [], [], 0.1) if ready: logging.debug("Waiting for state change") state = stdout.readline() inf = state.strip().split() container = inf[0].strip("'") state = inf[-1].strip('[]') if container in self._monitors: logging.debug("State of container '%s' changed to '%s'", container, state) self._monitors[container](state) _logger.info("LXC Monitor stopped!") def add_monitor(self, name, callback): self._monitors[name] = callback def rm_monitor(self, name): self._monitors.pop(name) def is_monitored(self, name): return name in self._monitors def kill(self): try: self._process.terminate() self._process.wait() except: pass self.join() class lxc(): def __init__(self): logging.debug("") def list(self, status=None): """ :return: ['container_first', 'container_second'] """ if status in ['active', 'frozen', 'running', 'stopped', 'nesting']: path = "--%s" % status else: path = "" cmd = ['/usr/bin/lxc-ls', path] out = subprocess.check_output(cmd).splitlines() # print out return out def exists(self, name): """ checks if a given container is defined or not """ if name in self.list(): return True return False def start(self, name, config_file=None): """ starts a container in daemon mode """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) if name in self.list("running"): raise ContainerAlreadyRunning('The container %s is already started!' % name) cmd = ['lxc-start', '-n', name, '-d'] if config_file: cmd += ['-f', config_file] return subprocess.check_call(cmd) def stop(self, name): """ stops a container """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) cmd = ['/usr/bin/lxc-stop', '-n', name] try: result = subprocess.check_call(cmd) return True except Exception as e: return False def destroy(self, name): """ removes a container [stops a container if it's running and] raises ContainerNotExists exception if the specified name is not created """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) # todo: check status. If status not STOPPED - run method self.stop(name) # todo: add condition self.stop(name) cmd = ['/usr/bin/lxc-destroy', '-f', '-n', name] return subprocess.check_call(cmd) def info(self, name): """ returns info dict about the specified container """ # if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) # cmd = ['/usr/bin/lxc-info', '-n', name, "-H"] out = subprocess.check_output(cmd).splitlines() clean = [] info = {} # for line in out: if line not in clean: clean.append(line) # for line in clean: key, value = line.split(":") # strip key = key.lstrip() value = value.lstrip() # key = key.replace(" ", "_") info[key.lower()] = value # get container size info['size'] = self.__get_container_size(name) return info def __get_container_size(self, name): cmd = ['/usr/bin/du', '--total', '-s', '/var/lib/lxc/%s' % name] out = subprocess.check_output(cmd).splitlines() size = 0 for l in out: key, value = l.split('\t') if value == 'total': size = key return int(key) def freeze(self, name): """ freezes the container """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) cmd = ['/usr/bin/lxc-freeze', '-n', name] subprocess.check_call(cmd) def unfreeze(self, name): """ unfreezes the container """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) cmd = ['lxc-unfreeze', '-n', name] subprocess.check_call(cmd) def notify(self, name, states, callback): """ executes the callback function with no parameters when the container reaches the specified state or states states can be or-ed or and-ed notify('test', 'STOPPED', letmeknow) notify('test', 'STOPPED|RUNNING', letmeknow) """ if not self.exists(name): raise ContainerNotExists("The container (%s) does not exist!" % name) cmd = ['lxc-wait', '-n', name, '-s', states] def th(): subprocess.check_call(cmd) callback() _logger.info("Waiting on states %s for container %s", states, name) threading.Thread(target=th).start() def checkconfig(self): """ returns the output of lxc-checkconfig """ cmd = ['lxc-checkconfig'] return subprocess.check_output(cmd).replace('[1;32m', '').replace('[1;33m', '').replace('[0;39m', '').replace('[1;32m', '').replace(' ', '').split('\n') def create(self, name, config_file=None, template=None, backing_store=None, template_options=None): """ Create a new container raises ContainerAlreadyExists exception if the container name is reserved already. :param template_options: Options passed to the specified template :type template_options: list or None """ if self.exists(name): raise ContainerAlreadyExists("The Container %s is already created!" % name) command = [] command.append("lxc-create -n %s" % name) if config_file: command.append(' -f %s' % config_file) if template: command.append(' -t %s' % template) if backing_store: command.append(' -B %s' % backing_store) if template_options: command.append(' -- %s' % template_options) print " ".join(command) print # create = subprocess.check_call(command, shell=True) create = subprocess.check_call(" ".join(command), shell=True) print print create print # if create == 0: # if not self.exists(name): # _logger.critical("The Container %s doesn't seem to be created! (options: %s)", name, command[3:]) # raise ContainerNotExists("The container (%s) does not exist!" % name) # # _logger.info("Container %s has been created with options %s", name, command[3:]) # return False return True def reset_password(self, container_name, username, password): call = [ 'echo', '"%s:${PASSWORD:-%s}"' % (username, password), "|", "chroot", "/var/lib/lxc/%s/rootfs/ chpasswd" % container_name ] subprocess.check_call(call, shell=True) # subprocess.call("echo \"ubuntu:${PASSWORD:-%(password)s}\" | chroot /var/lib/lxc/%(hostname)s/rootfs/ chpasswd" % task['parameters'], shell=True) return True # def running(): # ''' # returns a list of the currently running containers # ''' # return all_as_dict()['Running'] # def stopped(): # ''' # returns a list of the stopped containers # ''' # return all_as_dict()['Stopped'] # def all_as_dict(): # ''' # returns a dict {'Running': ['cont1', 'cont2'], # 'Stopped': ['cont3', 'cont4'] # } # # ''' # cmd = ['lxc-ls'] # out = subprocess.check_output(cmd).splitlines() # print out # stopped = [] # running = [] # frozen = [] # current = None # for c in out: # c = c.strip() # if c == 'RUNNING': # current = running # continue # if c == 'STOPPED': # current = stopped # continue # if c == 'FROZEN': # current = frozen # continue # if not len(c): # continue # current.append(c) # return {'Running': running, # 'Stopped': stopped, # 'Frozen': frozen} # def all_as_list(): # ''' # returns a list of all defined containers # ''' # as_dict = all_as_dict() # containers = as_dict['Running'] + as_dict['Frozen'] + as_dict['Stopped'] # containers_list = [] # for i in containers: # i = i.replace(' (auto)', '') # containers_list.append(i) # return containers_list # def kill(name, signal): # ''' # sends a kill signal to process 1 of ths container # :param signal: numeric signal # ''' # if not exists(name): # raise ContainerNotExists("The container (%s) does not exist!" % name) # cmd = ['lxc-kill', '--name=%s' % name, signal] # subprocess.check_call(cmd) # def shutdown(name, wait=False, reboot=False): # ''' # graceful shutdown sent to the container # :param wait: should we wait for the shutdown to complete? # :param reboot: reboot a container, ignores wait # ''' # if not exists(name): # raise ContainerNotExists("The container (%s) does not exist!" % name) # cmd = ['lxc-shutdown', '-n', name] # if wait: # cmd += ['-w'] # if reboot: # cmd += ['-r'] # # subprocess.check_call(cmd) # def monitor(name, callback): # ''' # monitors actions on the specified container, # callback is a function to be called on # ''' # global _monitor # if not exists(name): # raise ContainerNotExists("The container (%s) does not exist!" % name) # if _monitor: # if _monitor.is_monitored(name): # raise Exception("You are already monitoring this container (%s)" % name) # else: # _monitor = _LXCMonitor() # logging.info("Starting LXC Monitor") # _monitor.start() # def kill_handler(sg, fr): # stop_monitor() # signal.signal(signal.SIGTERM, kill_handler) # signal.signal(signal.SIGINT, kill_handler) # _monitor.add_monitor(name, callback) # def unmonitor(name): # if not exists(name): # raise ContainerNotExists("The container (%s) does not exist!" % name) # if not _monitor: # raise Exception("LXC Monitor is not started!") # if not _monitor.is_monitored(name): # raise Exception("This container (%s) is not monitored!" % name) # _monitor.rm_monitor(name) # def stop_monitor(): # global _monitor # if _monitor: # logging.info("Killing LXC Monitor") # _monitor.kill() # _monitor = None # signal.signal(signal.SIGTERM, signal.SIG_DFL) # signal.signal(signal.SIGINT, signal.SIG_DFL)