"""Tools for working with the host system""" # Copyright 2012 Canonical Ltd. # # Authors: # Nick Moffitt # Matthew Wedgwood import os import pwd import grp import random import string import subprocess import hashlib from collections import OrderedDict from hookenv import log def service_start(service_name): return service('start', service_name) def service_stop(service_name): return service('stop', service_name) def service_restart(service_name): return service('restart', service_name) def service_reload(service_name, restart_on_failure=False): service_result = service('reload', service_name) if not service_result and restart_on_failure: service_result = service('restart', service_name) return service_result def service(action, service_name): cmd = ['service', service_name, action] return subprocess.call(cmd) == 0 def service_running(service): try: output = subprocess.check_output(['service', service, 'status']) except subprocess.CalledProcessError: return False else: if ("start/running" in output or "is running" in output): return True else: return False def adduser(username, password=None, shell='/bin/bash', system_user=False): """Add a user""" try: user_info = pwd.getpwnam(username) log('user {0} already exists!'.format(username)) except KeyError: log('creating user {0}'.format(username)) cmd = ['useradd'] if system_user or password is None: cmd.append('--system') else: cmd.extend([ '--create-home', '--shell', shell, '--password', password, ]) cmd.append(username) subprocess.check_call(cmd) user_info = pwd.getpwnam(username) return user_info def add_user_to_group(username, group): """Add a user to a group""" cmd = [ 'gpasswd', '-a', username, group ] log("Adding user {} to group {}".format(username, group)) subprocess.check_call(cmd) def rsync(from_path, to_path, flags='-r', options=None): """Replicate the contents of a path""" options = options or ['--delete', '--executability'] cmd = ['/usr/bin/rsync', flags] cmd.extend(options) cmd.append(from_path) cmd.append(to_path) log(" ".join(cmd)) return subprocess.check_output(cmd).strip() def symlink(source, destination): """Create a symbolic link""" log("Symlinking {} as {}".format(source, destination)) cmd = [ 'ln', '-sf', source, destination, ] subprocess.check_call(cmd) def mkdir(path, owner='root', group='root', perms=0555, force=False): """Create a directory""" log("Making dir {} {}:{} {:o}".format(path, owner, group, perms)) uid = pwd.getpwnam(owner).pw_uid gid = grp.getgrnam(group).gr_gid realpath = os.path.abspath(path) if os.path.exists(realpath): if force and not os.path.isdir(realpath): log("Removing non-directory file {} prior to mkdir()".format(path)) os.unlink(realpath) else: os.makedirs(realpath, perms) os.chown(realpath, uid, gid) def write_file(path, content, owner='root', group='root', perms=0444): """Create or overwrite a file with the contents of a string""" log("Writing file {} {}:{} {:o}".format(path, owner, group, perms)) uid = pwd.getpwnam(owner).pw_uid gid = grp.getgrnam(group).gr_gid with open(path, 'w') as target: os.fchown(target.fileno(), uid, gid) os.fchmod(target.fileno(), perms) target.write(content) def mount(device, mountpoint, options=None, persist=False): '''Mount a filesystem''' cmd_args = ['mount'] if options is not None: cmd_args.extend(['-o', options]) cmd_args.extend([device, mountpoint]) try: subprocess.check_output(cmd_args) except subprocess.CalledProcessError, e: log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output)) return False if persist: # TODO: update fstab pass return True def umount(mountpoint, persist=False): '''Unmount a filesystem''' cmd_args = ['umount', mountpoint] try: subprocess.check_output(cmd_args) except subprocess.CalledProcessError, e: log('Error unmounting {}\n{}'.format(mountpoint, e.output)) return False if persist: # TODO: update fstab pass return True def mounts(): '''List of all mounted volumes as [[mountpoint,device],[...]]''' with open('/proc/mounts') as f: # [['/mount/point','/dev/path'],[...]] system_mounts = [m[1::-1] for m in [l.strip().split() for l in f.readlines()]] return system_mounts def file_hash(path): ''' Generate a md5 hash of the contents of 'path' or None if not found ''' if os.path.exists(path): h = hashlib.md5() with open(path, 'r') as source: h.update(source.read()) # IGNORE:E1101 - it does have update return h.hexdigest() else: return None def restart_on_change(restart_map): ''' Restart services based on configuration files changing This function is used a decorator, for example @restart_on_change({ '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ] }) def ceph_client_changed(): ... In this example, the cinder-api and cinder-volume services would be restarted if /etc/ceph/ceph.conf is changed by the ceph_client_changed function. ''' def wrap(f): def wrapped_f(*args): checksums = {} for path in restart_map: checksums[path] = file_hash(path) f(*args) restarts = [] for path in restart_map: if checksums[path] != file_hash(path): restarts += restart_map[path] for service_name in list(OrderedDict.fromkeys(restarts)): service('restart', service_name) return wrapped_f return wrap def lsb_release(): '''Return /etc/lsb-release in a dict''' d = {} with open('/etc/lsb-release', 'r') as lsb: for l in lsb: k, v = l.split('=') d[k.strip()] = v.strip() return d def pwgen(length=None): '''Generate a random pasword.''' if length is None: length = random.choice(range(35, 45)) alphanumeric_chars = [ l for l in (string.letters + string.digits) if l not in 'l0QD1vAEIOUaeiou'] random_chars = [ random.choice(alphanumeric_chars) for _ in range(length)] return(''.join(random_chars))