1
"""Tools for working with the host system"""
2
# Copyright 2012 Canonical Ltd.
5
# Nick Moffitt <nick.moffitt@canonical.com>
6
# Matthew Wedgwood <matthew.wedgwood@canonical.com>
17
from collections import OrderedDict
19
from hookenv import log
22
def service_start(service_name):
23
service('start', service_name)
26
def service_stop(service_name):
27
service('stop', service_name)
30
def service_restart(service_name):
31
service('restart', service_name)
34
def service_reload(service_name, restart_on_failure=False):
35
if not service('reload', service_name) and restart_on_failure:
36
service('restart', service_name)
39
def service(action, service_name):
40
cmd = ['service', service_name, action]
41
return subprocess.call(cmd) == 0
44
def service_running(service):
46
output = subprocess.check_output(['service', service, 'status'])
47
except subprocess.CalledProcessError:
50
if ("start/running" in output or "is running" in output):
56
def adduser(username, password=None, shell='/bin/bash', system_user=False):
59
user_info = pwd.getpwnam(username)
60
log('user {0} already exists!'.format(username))
62
log('creating user {0}'.format(username))
64
if system_user or password is None:
65
cmd.append('--system')
70
'--password', password,
73
subprocess.check_call(cmd)
74
user_info = pwd.getpwnam(username)
78
def add_user_to_group(username, group):
79
"""Add a user to a group"""
85
log("Adding user {} to group {}".format(username, group))
86
subprocess.check_call(cmd)
89
def rsync(from_path, to_path, flags='-r', options=None):
90
"""Replicate the contents of a path"""
91
options = options or ['--delete', '--executability']
92
cmd = ['/usr/bin/rsync', flags]
97
return subprocess.check_output(cmd).strip()
100
def symlink(source, destination):
101
"""Create a symbolic link"""
102
log("Symlinking {} as {}".format(source, destination))
109
subprocess.check_call(cmd)
112
def mkdir(path, owner='root', group='root', perms=0555, force=False):
113
"""Create a directory"""
114
log("Making dir {} {}:{} {:o}".format(path, owner, group,
116
uid = pwd.getpwnam(owner).pw_uid
117
gid = grp.getgrnam(group).gr_gid
118
realpath = os.path.abspath(path)
119
if os.path.exists(realpath):
120
if force and not os.path.isdir(realpath):
121
log("Removing non-directory file {} prior to mkdir()".format(path))
124
os.makedirs(realpath, perms)
125
os.chown(realpath, uid, gid)
128
def write_file(path, content, owner='root', group='root', perms=0444):
129
"""Create or overwrite a file with the contents of a string"""
130
log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
131
uid = pwd.getpwnam(owner).pw_uid
132
gid = grp.getgrnam(group).gr_gid
133
with open(path, 'w') as target:
134
os.fchown(target.fileno(), uid, gid)
135
os.fchmod(target.fileno(), perms)
136
target.write(content)
139
def filter_installed_packages(packages):
140
"""Returns a list of packages that require installation"""
142
cache = apt_pkg.Cache()
144
for package in packages:
147
p.current_ver or _pkgs.append(package)
149
log('Package {} has no installation candidate.'.format(package),
151
_pkgs.append(package)
155
def apt_install(packages, options=None, fatal=False):
156
"""Install one or more packages"""
157
options = options or []
158
cmd = ['apt-get', '-y']
160
cmd.append('install')
161
if isinstance(packages, basestring):
165
log("Installing {} with options: {}".format(packages,
168
subprocess.check_call(cmd)
173
def apt_update(fatal=False):
174
"""Update local apt cache"""
175
cmd = ['apt-get', 'update']
177
subprocess.check_call(cmd)
182
def mount(device, mountpoint, options=None, persist=False):
183
'''Mount a filesystem'''
185
if options is not None:
186
cmd_args.extend(['-o', options])
187
cmd_args.extend([device, mountpoint])
189
subprocess.check_output(cmd_args)
190
except subprocess.CalledProcessError, e:
191
log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
199
def umount(mountpoint, persist=False):
200
'''Unmount a filesystem'''
201
cmd_args = ['umount', mountpoint]
203
subprocess.check_output(cmd_args)
204
except subprocess.CalledProcessError, e:
205
log('Error unmounting {}\n{}'.format(mountpoint, e.output))
214
'''List of all mounted volumes as [[mountpoint,device],[...]]'''
215
with open('/proc/mounts') as f:
216
# [['/mount/point','/dev/path'],[...]]
217
system_mounts = [m[1::-1] for m in [l.strip().split()
218
for l in f.readlines()]]
223
''' Generate a md5 hash of the contents of 'path' or None if not found '''
224
if os.path.exists(path):
226
with open(path, 'r') as source:
227
h.update(source.read()) # IGNORE:E1101 - it does have update
233
def restart_on_change(restart_map):
234
''' Restart services based on configuration files changing
236
This function is used a decorator, for example
239
'/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
241
def ceph_client_changed():
244
In this example, the cinder-api and cinder-volume services
245
would be restarted if /etc/ceph/ceph.conf is changed by the
246
ceph_client_changed function.
249
def wrapped_f(*args):
251
for path in restart_map:
252
checksums[path] = file_hash(path)
255
for path in restart_map:
256
if checksums[path] != file_hash(path):
257
restarts += restart_map[path]
258
for service_name in list(OrderedDict.fromkeys(restarts)):
259
service('restart', service_name)
265
'''Return /etc/lsb-release in a dict'''
267
with open('/etc/lsb-release', 'r') as lsb:
270
d[k.strip()] = v.strip()
274
def pwgen(length=None):
275
'''Generate a random pasword.'''
277
length = random.choice(range(35, 45))
278
alphanumeric_chars = [
279
l for l in (string.letters + string.digits)
280
if l not in 'l0QD1vAEIOUaeiou']
282
random.choice(alphanumeric_chars) for _ in range(length)]
283
return(''.join(random_chars))