def Ubuntu(host, force=False): unix.isvalid(host) root = host.__dict__.get('root', None) instances = unix.instances(host) if len(instances) >= 1: host = Linux(getattr(unix, instances[0]).clone(host)) if root: host = Chroot(host, root) host = Debian(host) if host.distrib[0] != 'Ubuntu' and not force: raise LinuxError('invalid distrib') class UbuntuHost(host.__class__): def __init__(self): # kwargs = {'root': root} if root else {} host.__class__.__init__(self) self.__dict__.update(host.__dict__) @property def services(self): version = float(self.distrib[1]) if version <= 9.04: service_handler = Initd elif 9.10 <= version <= 14.10: service_handler = Upstart elif version >= 15.04: servivce_handler = Systemd return service_handler(weakref.ref(self)()) return UbuntuHost()
def Arch(host, force=False): unix.isvalid(host) root = host.__dict__.get('root', None) instances = unix.instances(host) if len(instances) >= 1: host = Linux(getattr(unix, instances[0]).clone(host)) if root: host = Chroot(host, root) if host.distrib[0] != 'Arch' and not force: raise LinuxError('invalid distrib') class ArchHost(host.__class__): def __init__(self): kwargs = {'root': root} if root else {} host.__class__.__init__(self, **kwargs) self.__dict__.update(host.__dict__) @property def hostname(self): with self.open(_HOSTNAMEFILE) as fhandler: return fhandler.read().decode().strip() @hostname.setter def hostname(self, value): with self.open(_HOSTNAMEFILE, 'w') as fhandler: fhandler.write(value) @property def services(self): return Systemd(weakref.ref(self)()) return ArchHost()
def Debian(host, force=False): unix.isvalid(host) root = host.__dict__.get('root', None) instances = unix.instances(host) if len(instances) >= 1: host = Linux(getattr(unix, instances[0]).clone(host)) if root: host = Chroot(host, root) if host.distrib[0] not in DISTRIBS and not force: raise LinuxError('invalid distrib') class DebianHost(host.__class__): def __init__(self): kwargs = {'root': root} if root else {} host.__class__.__init__(self, **kwargs) self.__dict__.update(host.__dict__) def list_packages(self): return self.execute('dpkg -l') @property def hostname(self): with self.open(_HOSTNAMEFILE) as fhandler: return fhandler.read().decode().strip() @hostname.setter def hostname(self, value): with self.open(_HOSTNAMEFILE, 'w') as fhandler: fhandler.write(value) @property def network(self): return _Network(weakref.ref(self)()) @property def apt(self): return _APT(weakref.ref(self)()) @property def services(self): major_version = int(self.distrib[1][0]) if major_version <= 5: service_handler = Initd elif 6 <= major_version <= 7: service_handler = Upstart elif major_version >= 8: service_handler = Systemd return service_handler(weakref.ref(self)()) return DebianHost()
def RedHat(host, force=False): unix.isvalid(host) root = host.__dict__.get('root', None) instances = unix.instances(host) if len(instances) >= 1: host = Linux(getattr(unix, instances[0]).clone(host)) if root: host = Chroot(host, root) if host.distrib[0] not in DISTRIBS and not force: raise LinuxError('invalid distrib') class RedHatHost(host.__class__): def __init__(self): kwargs = {'root': root} if root else {} host.__class__.__init__(self, **kwargs) self.__dict__.update(host.__dict__) def list_packages(self): return self.execute('dpkg -l') @property def hostname(self): with self.open(_NETFILE) as fhandler: for line in fhandler.read().splitlines(): attr, value = line.split('=') if attr == 'HOSTNAME': return value @hostname.setter def hostname(self, value): contnet = '' with self.open(_NETFILE) as fhandler: content = re.sub('HOSTNAME=[^\n]*', 'HOSTNAME=%s\n' % value, fhandler.read()) with self.open(_NETFILE, 'w') as fhandler: fhandler.write(content) @property def services(self): major_version = int(self.distrib[1][0]) if major_version <= 5: service_handler = Initd elif major_version == 6: service_handler = Upstart elif major_version >= 7: service_handler = Systemd return service_handler(weakref.ref(self)()) return RedHatHost()
def Linux(host): unix.isvalid(host) host.is_connected() instances = unix.instances(host) if len(instances) > 1: host = getattr(unix, instances[0]).clone(host) host_type = host.type if host_type != 'linux': raise LinuxError('this is not a Linux host (%s)' % host_type) class LinuxHost(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) @property def distrib(self): return distribution(self) @property def chrooted(self): return False @property def conf(self): return _Conf(weakref.ref(self)()) @property def memory(self): return _Memory(weakref.ref(self)()) def stat(self, filepath): return _Stat(weakref.ref(self)(), filepath) @property def modules(self): return _Modules(weakref.ref(self)()) @property def sysctl(self): return _Sysctl(weakref.ref(self)()) @property def fstab(self): return _Fstab(weakref.ref(self)()) return LinuxHost()
def CentOS(host, root='', force=False): unix.isvalid(host) root = host.__dict__.get('root', None) instances = unix.instances(host) if len(instances) >= 1: host = Linux(getattr(unix, instances[0]).clone(host)) if root: host = Chroot(host, root) host = RedHat(host) if host.distrib[0] != 'CentOS' and not force: raise LinuxError('invalid distrib') class CentOSHost(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) return CentOSHost()
def Hypervisor(host): unix.isvalid(host) try: host.which('virsh') except unix.UnixError: raise KvmError("unable to find 'virsh' command, is this a KVM host?") class Hypervisor(host.__class__): """This object represent an Hypervisor. **host** must be an object of type ``unix.Local`` or ``unix.Remote`` (or an object inheriting from them). """ def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) for control, value in _CONTROLS.items(): setattr(self, '_%s' % control, value) def virsh(self, command, *args, **kwargs): """Wrap the execution of the virsh command. It set a control for putting options after the virsh **command**. If **parse** control is activated, the value of ``stdout`` is returned or **KvmError** exception is raised. """ if self._ignore_opts: for opt in self._ignore_opts: kwargs.update({opt: False}) with self.set_controls(options_place='after', decode='utf-8'): status, stdout, stderr = self.execute('virsh', command, *args, **kwargs) # Clean stdout and stderr. if stdout: stdout = stdout.rstrip('\n') if stderr: stderr = stderr.rstrip('\n') if not self._parse: return status, stdout, stderr elif not status: raise KvmError(stderr) else: stdout = stdout.splitlines() return stdout[:-1] if not stdout[-1] else stdout def list_domains(self, **kwargs): """List domains. **kwargs** can contains any option supported by the virsh command. It can also contains a **state** argument which is a list of states for filtering (*all* option is automatically set). For compatibility the options ``--table``, ``--name`` and ``--uuid`` have been disabled. Virsh options are (some option may not work according your version): * *all*: list all domains * *inactive*: list only inactive domains * *persistent*: include persistent domains * *transient*: include transient domains * *autostart*: list autostarting domains * *no_autostart*: list not autostarting domains * *with_snapshot*: list domains having snapshots * *without_snapshort*: list domains not having snapshots * *managed_save*: domains that have managed save state (only possible if they are in the shut off state, so you need to specify *inactive* or *all* to actually list them) will instead show as saved * *with_managed_save*: list domains having a managed save image * *without_managed_save*: list domains not having a managed save image """ # Remove incompatible options between virsh versions. kwargs.pop('name', None) kwargs.pop('uuid', None) # Get states argument (which is not an option of the virsh command). states = kwargs.pop('states', []) if states: kwargs['all'] = True # Add virsh options for kwargs. virsh_opts = {arg: value for arg, value in kwargs.items() if value} # Get domains (filtered on state). domains = {} with self.set_controls(parse=True): stdout = self.virsh('list', **virsh_opts) for line in stdout[2:]: line = line.split() (domid, name, state), params = line[:3], line[3:] # Manage state in two words. if state == 'shut': state += ' %s' % params.pop(0) domain = {'id': int(domid) if domid != '-' else -1, 'state': state} if 'title' in kwargs: domain['title'] = ' '.join(params) if params else '' domains[name] = domain return domains def list_networks(self, **kwargs): with self.set_controls(parse=True): stdout = self.virsh('net-list', **kwargs) networks = {} for line in stdout[2:]: line = line.split() name, state, autostart = line[:3] net = dict(state=state, autostart=_convert(autostart)) if len(line) == 4: net.update(persistent=_convert(line[3])) networks.setdefault(name, net) return networks def list_interfaces(self, **kwargs): with self.set_controls(parse=True): stdout = self.virsh('iface-list', **kwargs) return {name: {'state': state, 'mac': mac} for line in self.virsh('iface-list', **kwargs)[2:] for name, state, mac in [line.split()]} @property def image(self): return _Image(weakref.ref(self)()) for property_name, property_methods in _MAPPING.items(): property_obj = type('_%s' % property_name.capitalize(), (object,), dict(__init__=__init)) for method_name, method_conf in property_methods.items(): __add_method(property_obj, method_name, method_conf) # getattr(Hypervisor, method['name']).__doc__ = '\n'.join(method['doc']) for method_name in dir(_SELF): if method_name.startswith('__%s' % property_name): method = method_name.replace('__%s_' % property_name, '') setattr(property_obj, method, getattr(_SELF, method_name)) setattr(Hypervisor, property_name, property(property_obj)) return Hypervisor()
def Deb(host, root=''): unix.isvalid(host) if unix.ishost(host, 'DebHost'): if unix.ishost(host, 'Local'): new_host = unix.Local() else: new_host = unix.Remote() new_host.__dict__.update(host.__dict__) return Deb(new_host, root) host = unix.linux.Linux(host, root) class DebHost(host.__class__): def __init__(self, root=''): host.__class__.__init__(self, root) self.__dict__.update(host.__dict__) # Check this is a Debian-like system. @property def distribution(self): return self.execute('lsb_release -i')[1].split(':')[1].strip() @property def release(self): return self.execute('lsb_release -r')[1].split(':')[1].strip() @property def codename(self): return self.execute('lsb_release -c')[1].split(':')[1].strip() def set_hostname(self, hostname): try: self.write('/etc/hostname', hostname) return [True, '', ''] except IOError as ioerr: return [False, '', ioerr] def set_network(self, interfaces): main_conf = ['auto lo', 'iface lo inet loopback'] # For each interface, creating a configuration file interfaces_conf = [] for index, interface in enumerate(interfaces): interface_name = 'eth%s' % index interface_conf = [ 'auto %s' % interface_name, 'iface %s inet static' % interface_name, ' address %s' % interface['address'], ' netmask %s' % interface['netmask'], ] if 'gateway' in interface: interface_conf.insert( -2, ' gateway %s' % interface['gateway'] ) interfaces_conf.append(interface_conf) if self.distribution == 'Ubuntu' and float(self.release) < 11.04: for interface_conf in interfaces_conf: main_conf.append('') main_conf.extend(interface_conf) else: # Add a line main_conf.extend(['', 'source %s*' % NETCONF_DIR]) # Creating the directory where configuration files of each # interfaces are stored. output = self.mkdir(NETCONF_DIR) if not output[0]: return [False, '', output[2]] for index, interface_conf in enumerate(interfaces_conf): try: self.write( path.join(NETCONF_DIR, 'eth%s' % index), '\n'.join(interface_conf) ) except IOError as ioerr: return [False, '', ioerr] # Creating main configuration file. try: self.write(NETCONF_FILE, '\n'.join(main_conf)) except IOError as ioerr: return [False, '', ioerr] return [True, '', ''] def check_pkg(self, package): status, stdout = self.execute('dpkg -l')[:2] for line in stdout.split('\n'): if status and line.find(package) != -1 and line[0] != 'r': return True return False def add_key(self, filepath): remote_filepath = path.join('/tmp', path.basename(filepath)) self.get(filepath, remote_filepath) return self.execute('apt-key add %s' % remote_filepath) def add_repository(self, filepath): return self.get(filepath, path.join( '/etc/apt/sources.list.d', path.basename(filepath) )) def apt_update(self): return self.execute('aptitude update') def apt_install(self, packages, interactive=True): return self.execute( '%s aptitude install -y %s' % (NO_DEBCONF, ' '.join(packages)), interactive ) def apt_search(self, package, interactive=True): status, stdout, stderr = self.execute( "aptitude search %s" % package, interactive ) if status: for line in stdout.split("\n"): if line.find(package) != -1: return True return False def apt_remove(self, packages, purge=False): apt_command = 'purge -y' if purge else 'remove -y' return self.execute( '%s aptitude %s %s' % (NO_DEBCONF, apt_command, ' '.join(packages)) ) def deb_install(self, filepath, force=False): command = '-i --force-depends' if force else '-i' return self.execute('%s dpkg %s %s' % (NO_DEBCONF, command, filepath)) return DebHost(root)
def LXC(host): unix.isvalid(host) if not unix.ishost(host, 'Linux'): raise LXCError('this is not a Linux host') class Hypervisor(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) def list_containers(self, **kwargs): def format_value(key, val): return (([v.strip() for v in val.split(',')] if val != '-' else []) if 'ipv' in key else ({ 'YES': True, 'NO': False }.get(val) if val in ['YES', 'NO'] else val)) kwargs.update({ 'fancy': True, 'fancy_format': ','.join(_FIELDS), '1': True }) status, stdout, stderr = self.execute('lxc-ls', **kwargs) if not status: raise LXCError(stderr) return { values[0]: { key: format_value(key, val) for key, val in zip(_FIELDS[1:], values[1:]) } for container in stdout.splitlines()[2:] for values in [re.split('\s{2,}', container)] } @property def container(self): return Container(weakref.ref(self)()) @property def device(self): return Device(weakref.ref(self)()) class Container(object): def __init__(self, host): self._host = host def exists(self, name): return True if name in self._host.list_containers() else False def info(self, name, **kwargs): kwargs.update(name=name) status, stdout, stderr = self._host.execute('lxc-info', **kwargs) if not status: raise LXCError(stderr) return { param.lower().strip().replace(' use', ''): value.strip() for line in stdout.splitlines() for param, value in [line.split(':')] } def state(self, name): return self.info(name, state=True)['state'] def console(self, name, **kwargs): self._host.execute('lxc-console', name=name, TTY=True, INTERACTIVE=True, **kwargs) def create(self, name, tmpl_opts={}, **kwargs): def format_opt(opt, value): opt = '%s%s' % ('-' if len(opt) == 1 else '--', opt) return '%s %s' % (opt, value if isinstance(value, str) else '') tmpl_args = ' '.join( format_opt(opt, value) for opt, value in tmpl_opts.items()) with self._host.set_controls(escape_args=False): return self._host.execute('lxc-create', '--', tmpl_args, name=name, **kwargs) def destroy(self, name, **kwargs): return self._host.execute('lxc-destroy', name=name, **kwargs) def start(self, name, **kwargs): return self._host.execute('lxc-start', name=name, **kwargs) class Device: def __init__(self, host): self._host = host def add(self, name, device): return self._host.execute('lxc-device', 'add', device, n=name) return Hypervisor()
def Linux(host, root=''): # Check it a valid host (ie: *Local* or *Remote*) unix.isvalid(host) if unix.ishost(host, 'LinuxHost'): if unix.ishost(host, 'Local'): new_host = unix.Local() else: new_host = unix.Remote() new_host.__dict__.update(host.__dict__) return Linux(new_host, root) class LinuxHost(host.__class__): """Inherit class from *host* and deepcopy object.""" def __init__(self, root=''): host.__class__.__init__(self) self.__dict__.update(host.__dict__) # If *root*, check that the directory contain # a valid Linux environment. self.root = root def __chroot(self): """Mount specials filesystems for having a "valid" chrooted environment. This may be needed when install a package in a chrooted environment for example.""" # Use parent (ie: Local or Remote) *execute* function. super(LinuxHost, self).execute( 'mount -t proc proc %s/proc/' % self.root ) super(LinuxHost, self).execute( 'mount -t sysfs sys %s/sys/' % self.root ) super(LinuxHost, self).execute( 'mount -o bind /dev %s/dev/' % self.root ) def __unchroot(self): """Umount specials filesystems on the chrooted environment.""" # Use parent (ie: Local or Remote) *execute* function. super(LinuxHost, self).execute('umount %s/proc/' % self.root) super(LinuxHost, self).execute('umount %s/sys/' % self.root) super(LinuxHost, self).execute('umount %s/dev/' % self.root) def execute(self, command, interactive=False, chroot=False): """Refine the *execute* wrapper function, taking into account if it must be executed in a chrooted environment. if *chroot* is given, specials filesystems (*proc*, *sys* and *dev*) are mounted before execution of the command and unmounted after. """ if chroot and self.root: self.__chroot() command = 'chroot %s %s' % (self.root, command) \ if self.root \ else command result = super(LinuxHost, self).execute(command, interactive) if chroot and self.root: self.__unchroot() return result def read(self, path, **kwargs): """Refine the *read* function, taking into account if it must be executed in a chroot environment.""" if self.root: if not os.path.isabs(path): raise IOError("'%s' is not an absolute path") path = os.path.join(self.root, path[1:]) return super(LinuxHost, self).read(path, **kwargs) def write(self, path, content, **kwargs): """Refine the *write* function, taking into account if it must be executed in a chroot environment.""" if self.root: if not os.path.isabs(path): raise IOError("'%s' is not an absolute path") path = os.path.join(self.root, path[1:]) super(LinuxHost, self).write(path, content, **kwargs) def isloaded(self, module): status, stdout, stderr = self.execute('lsmod') if not status: return False for line in stdout.split('\n')[1:-1]: if line.split()[0] == module: return True return False def load(self, module, options=()): return self.execute('modprobe %s %s' % (module, ' '.join(options))) def unload(self, module): return self.execute('modprobe -r %s' % module) def service(self, name, action, type='upstart'): return self.execute( { 'upstart': lambda: "service %s %s" % (name, action), 'init': lambda: "/etc/init.d/%s %s" % (name, action) }.get(type)() ) def set_password(self, username, password): shadow_file = '/etc/shadow' hashed_password = crypt.crypt(password, '$6$%s$' % ''.join( [random.choice(string.letters + string.digits) for i in xrange(0,8)] )) shadow_line = '%s:%s:%s:0:99999:7:::' % ( username, hashed_password, (datetime.today() - datetime(1970, 1, 1)).days ) new_content = [] in_file = False for line in self.readlines(shadow_file): if line.find(username) != -1: new_content.append(shadow_line) in_file = True else: new_content.append(line) if not in_file: new_content.append(shadow_line) try: self.write(shadow_file, '\n'.join(new_content)) return [True, '', ''] except IOError: return [False, '', ioerr] def set_hosts(self, ip, hostname, domain): try: self.write( '/etc/hosts', HOSTS_CONTENT \ .replace("$(IP)", ip) \ .replace("$(HOSTNAME)", hostname) \ .replace("$(DOMAIN)", domain) ) return [True, '', ''] except IOError as ioerr: return [False, '', ioerr] def set_sshkeys(self, algos=['rsa', 'dsa']): sshd_dir = '/etc/ssh' keys = [ os.path.join(sshd_dir, filename) \ for filename in self.listdir(sshd_dir) if 'ssh_host_' in filename ] for key in keys: output = self.execute("rm %s" % key) if not output[0]: output[2] = "Unable to remove old keys: %s" % output[2] return output for algo in algos: output = self.execute( 'ssh-keygen -N "" -t %s -f %s/ssh_host_%s_key' % ( algo, sshd_dir, algo ) ) if not output[0]: output[2] = "Unable to generate %s keys: %s" % ( algo.upper(), output[2] ) return output return [True, '', ''] return LinuxHost(root)
def KVM(host): unix.isvalid(host) class KVM(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) def virsh(self, command, args): virsh_cmd = ' '.join(( 'LANGUAGE=%s' % LANGUAGE, 'virsh', command, args, )) status, stdout, stderr = self.execute(virsh_cmd) if not status \ and (UNKNOWN_CMD_REGEXP.match(stderr) or BAD_OPTION_REGEXP.match(stderr)): raise KVMError('%s: %s' % (virsh_cmd, stderr)) return (status, stdout, stderr) @property def vms(self): return [ vm.split()[1] \ for vm in self.virsh('list', '--all')[1].split('\n')[2:-1] ] def isdefined(self, vm): return True if vm in self.vms else False def state(self, vm): if not self.isdefined(vm): return (False, '', 'VM not exists') return self.virsh('domstate', vm)[1].split('\n')[0] def start(self, vm): return self.virsh('start', vm) def reboot(self, vm): return self.virsh('reboot', vm) def stop(self, vm, timeout=30, force=False): output = self.virsh('shutdown', vm) if not output[0]: return output def timeout_handler(signum, frame): raise TimeoutException() old_handler = signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout) try: while self.state(vm) != SHUTOFF: time.sleep(1) except TimeoutException: if force: status, stdout, stderr = self.destroy(vm) if status: stderr = 'VM has been destroy after %ss' % timeout return (status, stdout, stderr) else: return (False, '', 'VM not stopping after %ss' % timeout) finally: signal.signal(signal.SIGALRM, old_handler) signal.alarm(0) return output def destroy(self, vm): return self.virsh('destroy', vm) def restore(self, vm, src): return self.virsh('restore', src) def save(self, vm, dst): return self.virsh('save', ''.join("%s %s" % (vm, dst))) def define(self, conf_file): return self.virsh('define', conf_file) def undefine(self, vm, del_img=False): return self.virsh('undefine', vm) def migrate(self, vm, dst): return self.virsh( 'migrate', ' '.join("--connect", "qemu:///system", "--live", "--persistent", "--copy-storage-all", "%s qemu+ssh://%s/system" % (vm, dst))) def img_size(self, img_path): if not self.exists(img_path): raise OSError("file '%s' not exists" % img_path) stdout = self.execute('qemu-img info %s' % img_path)[1] return int(SIZE_REGEXP.search(stdout).group(1)) / 1024 def img_create(self, img_path, format, size): return self.execute('qemu-img create -f %s %s %sG' % (format, img_path, size)) def img_convert(self, format, src_path, dst_path, delete=False): output = self.execute("qemu-img convert -O %s %s %s" % (format, src_path, dst_path)) if not delete or not output: return output return self.rm(src_path) def img_resize(self, path, new_size): return self.execute("qemu-img resize %s %sG" % (path, new_size)) def img_load(self, path, nbd='/dev/nbd0'): if not self.isloaded('nbd'): output = self.load('nbd') if not output[0]: return output if not self.exists(path): return [False, '', "'%' not exists" % path] output = self.execute("qemu-nbd -c %s %s" % (nbd, path)) time.sleep(2) return output def img_unload(self, nbd='/dev/nbd0'): return self.execute("qemu-nbd -d %s" % nbd) def __xml_value(self, elt, tag): return elt.getElementsByTagName(tag)[0].childNodes[0].data def __xml_attr(self, elt, tag, attr): try: return elt.getElementsByTagName(tag)[0].getAttribute(attr) except IndexError: return '' def conf(self, vm): if not self.isdefined(vm): raise KVMError("VM '%s' is not defined" % vm) xml_conf = self.virsh('dumpxml', vm)[1] dom = parseString(xml_conf) disks = [ { 'path': self.__xml_attr(disk_node, 'source', 'file'), 'format': self.__xml_attr(disk_node, 'driver', 'type'), 'driver': self.__xml_attr(disk_node, 'target', 'bus'), 'device': self.__xml_attr(disk_node, 'target', 'dev') } for disk_node in dom.getElementsByTagName('disk') \ if disk_node.getAttribute('device') == 'disk' ] for index, disk in enumerate(disks): try: disks[index]['size'] = self.img_size(disk['path']) except OSError: disks[index]['size'] = 0 interfaces = [{ 'mac': self.__xml_attr(int_node, 'mac', 'address'), 'vlan': self.__xml_attr(int_node, 'source', 'bridge'), 'interface': self.__xml_attr(int_node, 'target', 'dev'), 'driver': self.__xml_attr(int_node, 'model', 'type') } for int_node in dom.getElementsByTagName('interface')] return { 'pc': self.__xml_attr( dom.getElementsByTagName('os')[0], 'type', 'machine'), 'name': self.__xml_value(dom, 'name'), 'uuid': self.__xml_value(dom, 'uuid'), 'memory': int(self.__xml_value(dom, 'currentMemory')), 'memory_max': int(self.__xml_value(dom, 'memory')), 'cores': int(self.__xml_value(dom, 'vcpu')), 'disks': disks, 'interfaces': interfaces } def __node(self, name, attrs={}, text='', childs=[]): node = self.xml.createElement(name) for attr_name, attr_value in attrs.iteritems(): node.setAttribute(attr_name, attr_value) if text: node.appendChild(self.xml.createTextNode(str(text))) if childs: for child_node in childs: node.appendChild(child_node) return node def _gen_devices_config(self, disks, interfaces): devices_nodes = [self.__node('emulator', text='/usr/bin/kvm')] # Add disks. devices_nodes.extend( (self.__node('disk', { 'type': 'file', 'device': 'disk' }, childs=( self.__node('driver', { 'name': 'qemu', 'type': disk['format'] }), self.__node('source', {'file': disk['path']}), self.__node('target', { 'dev': disk['device'], 'bus': disk['driver'] })))) for disk in disks) # Add interfaces. devices_nodes.extend((self.__node( 'interface', {'type': 'bridge'}, childs=( self.__node('mac', {'address': interface['mac']}), self.__node('source', {'bridge': 'br%s' % interface['vlan']}), self.__node('model', {'type': interface['driver']}), )) for interface in interfaces)) # Add other devices. devices_nodes.extend( (self.__node('serial', {'type': 'pty'}, childs=(self.__node('target', {'port': '0'}), )), self.__node('console', {'type': 'pty'}, childs=(self.__node('target', {'port': '0'}), )), self.__node('input', { 'type': 'mouse', 'bus': 'ps2' }), self.__node( 'graphics', { 'type': 'vnc', 'port': '-1', 'autoport': 'yes', 'keymap': 'fr' }), self.__node('sound', {'model': 'es1370'}), self.__node('video', childs=(self.__node( 'model', { 'type': 'cirrus', 'vram': '9216', 'heads': '1' }), )))) return devices_nodes def gen_conf(self, conf_file, params): # Hack for not printing xml version Document.writexml = writexml_document # Hack for XML output: text node on one line Element.writexml = writexml_element self.xml = Document() # memory = int(float(params['memory']) * 1024 * 1024) config = self.__node( 'domain', {'type': 'kvm'}, childs=( self.__node('name', text=params['name']), self.__node('uuid', text=params['uuid']), self.__node('memory', text=params['memory']), self.__node('currentMemory', text=params['memory']), self.__node('vcpu', text=params['cores']), self.__node( 'os', childs=( self.__node( 'type', { 'arch': 'x86_64', # 'machine': 'pc-0.11' }, 'hvm'), self.__node('boot', {'dev': 'hd'}))), self.__node('features', childs=(self.__node('acpi'), self.__node('apic'), self.__node('pae'))), self.__node('clock', {'offset': 'utc'}), self.__node('on_poweroff', text='destroy'), self.__node('on_reboot', text='restart'), self.__node('on_crash', text='restart'), self.__node('devices', childs=self._gen_devices_config( params['disks'], params['interfaces'])))) try: self.write( conf_file, '\n'.join(config.toprettyxml(indent=' ').split('\n')[1:])) return [True, '', ''] except IOError as ioerr: return [False, '', ioerr] def vms_conf(self): vms_conf = {} for vm in self.vms: vms_conf.setdefault(vm, self.conf(vm)) return vms_conf def mount(self, vgroot, lvroot, path): # Load root Volume Group. output = self.execute("vgchange -ay %s" % vgroot) if not output[0]: output[2] = "Unable to load root Volume Group: %s" % output[2] return output # Create mount point. if not self.exists(path): output = self.mkdir(path, True) if not output[0]: output[2] = "Unable to create mount point: %s" % output[2] return output # Mount root partition output = self.execute("mount /dev/%s/%s %s" % (vgroot, lvroot, path)) if not output[0]: output[2] = "Unable to mount root partition: %s" % output[2] return output self.mounted = [path] # Read fstab try: lines = self.readlines(os.path.join(path, 'etc', 'fstab')) except OSError, os_err: return (False, "", "Unable to read fstab: %s" % output[2]) for line in lines: if \ line.find('/dev/mapper') == -1 or \ line.find('/dev/mapper/%s-%s' % (vgroot, lvroot)) != -1 or \ line.find('swap') != -1: continue dev, partition = line.split()[:2] mount_point = os.path.join(path, partition[1:]) output = self.execute("mount %s %s" % (dev, mount_point)) if not output[0]: output[2] = "Unable to mount '%s' partition: %s" % ( partition, output[2]) return output self.mounted.append(mount_point) return (True, "", "") def umount(self, vgroot): if not self.mounted: return (True, "", "Nothing was mounted") mounted = list(self.mounted) for mount in reversed(mounted): output = self.execute("umount %s" % mount) if not output[0]: output[2] = "Unable to umount '%s': %s" % (mount, output[2]) return output self.mounted.remove(mount) output = self.execute("vgchange -an %s" % vgroot) if not output[0]: output[ 2] = "Unable to unload root Volume Group: %s" % output[2] return output
def Rpm(host, root=''): unix.isvalid(host) if unix.ishost(host, 'RpmHost'): if unix.ishost(host, 'Local'): new_host = unix.Local() else: new_host = unix.Remote() new_host.__dict__.update(host.__dict__) return Rpm(new_host, root) host = unix.linux.Linux(host, root) class RpmHost(host.__class__): def __init__(self, root=''): host.__class__.__init__(self, root) self.__dict__.update(host.__dict__) def set_hostname(self, hostname): try: self.write( '/etc/sysconfig/network', '\n'.join(( 'NETWORKING=yes', 'NETWORKING_IPV6=no', 'HOSTNAME=%s' % hostname )) ) return [True, '', ''] except IOError as ioerr: return [False, '', ioerr] def set_network(self, interfaces): network_root = '/etc/sysconfig/network-scripts' self.rm(path.join(network_root, 'ifcfg-ext')) for index, interface in enumerate(interfaces): interface_conf = [ 'DEVICE=eth%s' % index, 'BOOTPROTO=none', 'ONBOOT=yes', 'NETMASK=%s' % interface['netmask'], 'IPADDR=%s' % interface['address'], 'TYPE=Ethernet', 'USERCTL=no', 'IPV6INIT=no', 'PEERDNS=yes' ] if 'gateway' in interface: interface_conf.insert(5, 'GATEWAY=%s' % interface['gateway']) try: self.write( path.join(network_root, 'ifcfg-eth%s' % index), '\n'.join(interface_conf) ) except IOError as ioerr: return [False, '', ioerr] return [True, '', ''] def check_pkg(self, package): status, stdout = self.execute('rpm -qa')[:2] if status and stdout.find(package) != -1: return True return False def add_repository(self, filepath): return self.get(filepath, os.path.join( '/etc/yum.repos.d', os.path.basename(filepath)) ) def yum_install(self, packages, interative=True,repository=''): yum_cmd = '-y' if not repository else '-y --enablerepo=%s' % repository return self.execute( 'yum install %s %s' % (yum_cmd, ' '.join(packages)), interactive ) def yum_remove(self, packages): return self.execute('yum erase -y %s' % ' '.join(packages)) def rpm_install(self, filepath): return self.execute('rpm -U %s' % filepath) return RpmHost(root)
def LXC(host): unix.isvalid(host) if not unix.ishost(host, 'Linux'): raise LXCError('this is not a Linux host') class Hypervisor(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) def list_containers(self, **kwargs): def format_value(key, val): return (([v.strip() for v in val.split(',')] if val != '-' else []) if 'ipv' in key else ({'YES': True, 'NO': False}.get(val) if val in ['YES', 'NO'] else val)) kwargs.update({'fancy': True, 'fancy_format': ','.join(_FIELDS), '1': True}) status, stdout, stderr = self.execute('lxc-ls', **kwargs) if not status: raise LXCError(stderr) return {values[0]: {key: format_value(key, val) for key, val in zip(_FIELDS[1:], values[1:])} for container in stdout.splitlines()[2:] for values in [re.split('\s{2,}', container)]} @property def container(self): return Container(weakref.ref(self)()) @property def device(self): return Device(weakref.ref(self)()) class Container(object): def __init__(self, host): self._host = host def exists(self, name): return True if name in self._host.list_containers() else False def info(self, name, **kwargs): kwargs.update(name=name) status, stdout, stderr = self._host.execute('lxc-info', **kwargs) if not status: raise LXCError(stderr) return {param.lower().strip().replace(' use', ''): value.strip() for line in stdout.splitlines() for param, value in [line.split(':')]} def state(self, name): return self.info(name, state=True)['state'] def console(self, name, **kwargs): self._host.execute('lxc-console', name=name, TTY=True, INTERACTIVE=True, **kwargs) def create(self, name, tmpl_opts={}, **kwargs): def format_opt(opt, value): opt = '%s%s' % ('-' if len(opt) == 1 else '--', opt) return '%s %s' % (opt, value if isinstance(value, str) else '') tmpl_args = ' '.join(format_opt(opt, value) for opt, value in tmpl_opts.items()) with self._host.set_controls(escape_args=False): return self._host.execute('lxc-create', '--', tmpl_args, name=name, **kwargs) def destroy(self, name, **kwargs): return self._host.execute('lxc-destroy', name=name, **kwargs) def start(self, name, **kwargs): return self._host.execute('lxc-start', name=name, **kwargs) class Device: def __init__(self, host): self._host = host def add(self, name, device): return self._host.execute('lxc-device', 'add', device, n=name) return Hypervisor()
def Chroot(host, root): unix.isvalid(host) host.is_connected() if root and host.username != 'root': raise ChrootError('you need to be root for chroot') instances = unix.instances(host) if len(instances) > 1: host = getattr(unix, instances[0]).clone(host) host = Linux(host) class ChrootHost(host.__class__): def __init__(self, root): host.__class__.__init__(self) self.__dict__.update(host.__dict__) self.root = root @property def chrooted(self): return True def execute(self, cmd, *args, **kwargs): if self.root: cmd = 'chroot %s %s' % (self.root, cmd) result = host.execute(cmd, *args, **kwargs) # Set return code of the parent. If not set some functions (like # Path) does not work correctly on chrooted objects. self.return_code = host.return_code return result def open(self, filepath, mode='r'): if self.root: filepath = filepath[1:] if filepath.startswith('/') else filepath filepath = os.path.join(self.root, filepath) return host.open(filepath, mode) @contextmanager def set_controls(self, **controls): cur_controls = dict(host.controls) try: for control, value in controls.items(): host.set_control(control, value) yield None finally: for control, value in cur_controls.items(): host.set_control(control, value) def chroot(self): for (fs, opts) in _FILESYSTEMS: mount_point = os.path.join(root, fs[1:] if fs.startswith('/') else fs) status, _, stderr = host.mount(fs, mount_point, **opts) if not status: raise ChrootError("unable to mount '%s': %s" % (fs, stderr)) def unchroot(self): for (fs, _) in _FILESYSTEMS: mount_point = os.path.join(root, fs[1:] if fs.startswith('/') else fs) status, _, stderr = host.umount(mount_point) if not status: raise ChrootError("unable to umount '%s': %s" % (fs, stderr)) return ChrootHost(root)
def KVM(host): unix.isvalid(host) class KVM(host.__class__): def __init__(self): host.__class__.__init__(self) self.__dict__.update(host.__dict__) def virsh(self, command, args): virsh_cmd = ' '.join(( 'LANGUAGE=%s' % LANGUAGE, 'virsh', command, args, )) status, stdout, stderr = self.execute(virsh_cmd) if not status \ and (UNKNOWN_CMD_REGEXP.match(stderr) or BAD_OPTION_REGEXP.match(stderr)): raise KVMError('%s: %s' % (virsh_cmd, stderr)) return (status, stdout, stderr) @property def vms(self): return [ vm.split()[1] \ for vm in self.virsh('list', '--all')[1].split('\n')[2:-1] ] def isdefined(self, vm): return True if vm in self.vms else False def state(self, vm): if not self.isdefined(vm): return (False, '', 'VM not exists') return self.virsh('domstate', vm)[1].split('\n')[0] def start(self, vm): return self.virsh('start', vm) def reboot(self, vm): return self.virsh('reboot', vm) def stop(self, vm, timeout=30, force=False): output = self.virsh('shutdown', vm) if not output[0]: return output def timeout_handler(signum, frame): raise TimeoutException() old_handler = signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout) try: while self.state(vm) != SHUTOFF: time.sleep(1) except TimeoutException: if force: status, stdout,stderr = self.destroy(vm) if status: stderr = 'VM has been destroy after %ss' % timeout return (status, stdout, stderr) else: return (False, '', 'VM not stopping after %ss' % timeout) finally: signal.signal(signal.SIGALRM, old_handler) signal.alarm(0) return output def destroy(self, vm): return self.virsh('destroy', vm) def restore(self, vm, src): return self.virsh('restore', src) def save(self, vm, dst): return self.virsh('save', ''.join("%s %s" %(vm, dst))) def define(self, conf_file): return self.virsh('define', conf_file) def undefine(self, vm, del_img=False): return self.virsh('undefine', vm) def migrate(self, vm, dst): return self.virsh('migrate', ' '.join( "--connect", "qemu:///system", "--live", "--persistent", "--copy-storage-all", "%s qemu+ssh://%s/system" % (vm, dst) )) def img_size(self, img_path): if not self.exists(img_path): raise OSError("file '%s' not exists" % img_path) stdout = self.execute('qemu-img info %s' % img_path)[1] return int(SIZE_REGEXP.search(stdout).group(1)) / 1024 def img_create(self, img_path, format, size): return self.execute('qemu-img create -f %s %s %sG' % ( format, img_path, size )) def img_convert(self, format, src_path, dst_path, delete=False): output = self.execute("qemu-img convert -O %s %s %s" % ( format, src_path, dst_path )) if not delete or not output: return output return self.rm(src_path) def img_resize(self, path, new_size): return self.execute( "qemu-img resize %s %sG" % (path, new_size) ) def img_load(self, path, nbd='/dev/nbd0'): if not self.isloaded('nbd'): output = self.load('nbd') if not output[0]: return output if not self.exists(path): return [False, '', "'%' not exists" % path] output = self.execute("qemu-nbd -c %s %s" % (nbd, path)) time.sleep(2) return output def img_unload(self, nbd='/dev/nbd0'): return self.execute("qemu-nbd -d %s" % nbd) def __xml_value(self, elt, tag): return elt.getElementsByTagName(tag)[0].childNodes[0].data def __xml_attr(self, elt, tag, attr): try: return elt.getElementsByTagName(tag)[0].getAttribute(attr) except IndexError: return '' def conf(self, vm): if not self.isdefined(vm): raise KVMError("VM '%s' is not defined" % vm) xml_conf = self.virsh('dumpxml', vm)[1] dom = parseString(xml_conf) disks = [ { 'path': self.__xml_attr(disk_node, 'source', 'file'), 'format': self.__xml_attr(disk_node, 'driver', 'type'), 'driver': self.__xml_attr(disk_node, 'target', 'bus'), 'device': self.__xml_attr(disk_node, 'target', 'dev') } for disk_node in dom.getElementsByTagName('disk') \ if disk_node.getAttribute('device') == 'disk' ] for index, disk in enumerate(disks): try: disks[index]['size'] = self.img_size(disk['path']) except OSError: disks[index]['size'] = 0 interfaces = [ { 'mac': self.__xml_attr(int_node, 'mac', 'address'), 'vlan': self.__xml_attr(int_node, 'source', 'bridge'), 'interface': self.__xml_attr(int_node, 'target', 'dev'), 'driver': self.__xml_attr(int_node, 'model', 'type') } for int_node in dom.getElementsByTagName('interface') ] return { 'pc': self.__xml_attr(dom.getElementsByTagName('os')[0], 'type', 'machine'), 'name': self.__xml_value(dom, 'name'), 'uuid': self.__xml_value(dom, 'uuid'), 'memory': int(self.__xml_value(dom, 'currentMemory')), 'memory_max': int(self.__xml_value(dom, 'memory')), 'cores': int(self.__xml_value(dom, 'vcpu')), 'disks': disks, 'interfaces': interfaces } def __node(self, name, attrs={}, text='', childs=[]): node = self.xml.createElement(name) for attr_name, attr_value in attrs.iteritems(): node.setAttribute(attr_name, attr_value) if text: node.appendChild(self.xml.createTextNode(str(text))) if childs: for child_node in childs: node.appendChild(child_node) return node def _gen_devices_config(self, disks, interfaces): devices_nodes = [self.__node('emulator', text='/usr/bin/kvm')] # Add disks. devices_nodes.extend( ( self.__node('disk', {'type': 'file', 'device': 'disk'}, childs=( self.__node('driver', {'name': 'qemu', 'type': disk['format']}), self.__node('source', {'file': disk['path']}), self.__node('target', {'dev': disk['device'], 'bus': disk['driver']}) )) ) for disk in disks ) # Add interfaces. devices_nodes.extend( ( self.__node('interface', {'type': 'bridge'}, childs=( self.__node('mac', {'address': interface['mac']}), self.__node('source', {'bridge': 'br%s' % interface['vlan']}), self.__node('model', {'type': interface['driver']}), )) for interface in interfaces ) ) # Add other devices. devices_nodes.extend(( self.__node('serial', {'type': 'pty'}, childs=( self.__node('target', {'port': '0'}), )), self.__node('console', {'type': 'pty'}, childs=( self.__node('target', {'port': '0'}), )), self.__node('input', {'type': 'mouse', 'bus': 'ps2'}), self.__node('graphics', { 'type': 'vnc', 'port': '-1', 'autoport': 'yes', 'keymap': 'fr'} ), self.__node('sound', {'model': 'es1370'}), self.__node('video', childs=( self.__node('model', {'type': 'cirrus', 'vram': '9216', 'heads': '1'}), ) ))) return devices_nodes def gen_conf(self, conf_file, params): # Hack for not printing xml version Document.writexml = writexml_document # Hack for XML output: text node on one line Element.writexml = writexml_element self.xml = Document() # memory = int(float(params['memory']) * 1024 * 1024) config = self.__node('domain', {'type': 'kvm'}, childs=( self.__node('name', text=params['name']), self.__node('uuid', text=params['uuid']), self.__node('memory', text=params['memory']), self.__node('currentMemory', text=params['memory']), self.__node('vcpu', text=params['cores']), self.__node('os', childs=( self.__node('type', { 'arch': 'x86_64', # 'machine': 'pc-0.11' }, 'hvm'), self.__node('boot', {'dev': 'hd'}) )), self.__node('features', childs=( self.__node('acpi'), self.__node('apic'), self.__node('pae') )), self.__node('clock', {'offset': 'utc'}), self.__node('on_poweroff', text='destroy'), self.__node('on_reboot', text='restart'), self.__node('on_crash', text='restart'), self.__node('devices', childs=self._gen_devices_config( params['disks'], params['interfaces'] )) )) try: self.write( conf_file, '\n'.join(config.toprettyxml(indent=' ').split('\n')[1:]) ) return [True, '', ''] except IOError as ioerr: return[False, '', ioerr] def vms_conf(self): vms_conf = {} for vm in self.vms: vms_conf.setdefault(vm, self.conf(vm)) return vms_conf def mount(self, vgroot, lvroot, path): # Load root Volume Group. output = self.execute("vgchange -ay %s" % vgroot) if not output[0]: output[2] = "Unable to load root Volume Group: %s" % output[2] return output # Create mount point. if not self.exists(path): output = self.mkdir(path, True) if not output[0]: output[2] = "Unable to create mount point: %s" % output[2] return output # Mount root partition output = self.execute( "mount /dev/%s/%s %s" % (vgroot, lvroot, path) ) if not output[0]: output[2] = "Unable to mount root partition: %s" % output[2] return output self.mounted = [path] # Read fstab try: lines = self.readlines(os.path.join(path, 'etc', 'fstab')) except OSError, os_err: return (False, "", "Unable to read fstab: %s" % output[2]) for line in lines: if \ line.find('/dev/mapper') == -1 or \ line.find('/dev/mapper/%s-%s' % (vgroot, lvroot)) != -1 or \ line.find('swap') != -1: continue dev, partition = line.split()[:2] mount_point = os.path.join(path, partition[1:]) output = self.execute("mount %s %s" % (dev, mount_point)) if not output[0]: output[2] = "Unable to mount '%s' partition: %s" % ( partition, output[2] ) return output self.mounted.append(mount_point) return (True, "", "") def umount(self, vgroot): if not self.mounted: return (True, "", "Nothing was mounted") mounted = list(self.mounted) for mount in reversed(mounted): output = self.execute("umount %s" % mount) if not output[0]: output[2] = "Unable to umount '%s': %s" % (mount, output[2]) return output self.mounted.remove(mount) output = self.execute("vgchange -an %s" % vgroot) if not output[0]: output[2] = "Unable to unload root Volume Group: %s" % output[2] return output