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
20
from fstab import Fstab
23
def service_start(service_name):
24
"""Start a system service"""
25
return service('start', service_name)
28
def service_stop(service_name):
29
"""Stop a system service"""
30
return service('stop', service_name)
33
def service_restart(service_name):
34
"""Restart a system service"""
35
return service('restart', service_name)
38
def service_reload(service_name, restart_on_failure=False):
39
"""Reload a system service, optionally falling back to restart if
41
service_result = service('reload', service_name)
42
if not service_result and restart_on_failure:
43
service_result = service('restart', service_name)
47
def service(action, service_name):
48
"""Control a system service"""
49
cmd = ['service', service_name, action]
50
return subprocess.call(cmd) == 0
53
def service_running(service):
54
"""Determine whether a system service is running"""
56
output = subprocess.check_output(['service', service, 'status'])
57
except subprocess.CalledProcessError:
60
if ("start/running" in output or "is running" in output):
66
def adduser(username, password=None, shell='/bin/bash', system_user=False):
67
"""Add a user to the system"""
69
user_info = pwd.getpwnam(username)
70
log('user {0} already exists!'.format(username))
72
log('creating user {0}'.format(username))
74
if system_user or password is None:
75
cmd.append('--system')
80
'--password', password,
83
subprocess.check_call(cmd)
84
user_info = pwd.getpwnam(username)
88
def add_user_to_group(username, group):
89
"""Add a user to a group"""
95
log("Adding user {} to group {}".format(username, group))
96
subprocess.check_call(cmd)
99
def rsync(from_path, to_path, flags='-r', options=None):
100
"""Replicate the contents of a path"""
101
options = options or ['--delete', '--executability']
102
cmd = ['/usr/bin/rsync', flags]
104
cmd.append(from_path)
107
return subprocess.check_output(cmd).strip()
110
def symlink(source, destination):
111
"""Create a symbolic link"""
112
log("Symlinking {} as {}".format(source, destination))
119
subprocess.check_call(cmd)
122
def mkdir(path, owner='root', group='root', perms=0555, force=False):
123
"""Create a directory"""
124
log("Making dir {} {}:{} {:o}".format(path, owner, group,
126
uid = pwd.getpwnam(owner).pw_uid
127
gid = grp.getgrnam(group).gr_gid
128
realpath = os.path.abspath(path)
129
if os.path.exists(realpath):
130
if force and not os.path.isdir(realpath):
131
log("Removing non-directory file {} prior to mkdir()".format(path))
134
os.makedirs(realpath, perms)
135
os.chown(realpath, uid, gid)
138
def write_file(path, content, owner='root', group='root', perms=0444):
139
"""Create or overwrite a file with the contents of a string"""
140
log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
141
uid = pwd.getpwnam(owner).pw_uid
142
gid = grp.getgrnam(group).gr_gid
143
with open(path, 'w') as target:
144
os.fchown(target.fileno(), uid, gid)
145
os.fchmod(target.fileno(), perms)
146
target.write(content)
149
def fstab_remove(mp):
150
"""Remove the given mountpoint entry from /etc/fstab
152
return Fstab.remove_by_mountpoint(mp)
155
def fstab_add(dev, mp, fs, options=None):
156
"""Adds the given device entry to the /etc/fstab file
158
return Fstab.add(dev, mp, fs, options=options)
161
def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
162
"""Mount a filesystem at a particular mountpoint"""
164
if options is not None:
165
cmd_args.extend(['-o', options])
166
cmd_args.extend([device, mountpoint])
168
subprocess.check_output(cmd_args)
169
except subprocess.CalledProcessError, e:
170
log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
174
return fstab_add(device, mountpoint, filesystem, options=options)
178
def umount(mountpoint, persist=False):
179
"""Unmount a filesystem"""
180
cmd_args = ['umount', mountpoint]
182
subprocess.check_output(cmd_args)
183
except subprocess.CalledProcessError, e:
184
log('Error unmounting {}\n{}'.format(mountpoint, e.output))
188
return fstab_remove(mountpoint)
193
"""Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
194
with open('/proc/mounts') as f:
195
# [['/mount/point','/dev/path'],[...]]
196
system_mounts = [m[1::-1] for m in [l.strip().split()
197
for l in f.readlines()]]
202
"""Generate a md5 hash of the contents of 'path' or None if not found """
203
if os.path.exists(path):
205
with open(path, 'r') as source:
206
h.update(source.read()) # IGNORE:E1101 - it does have update
212
def restart_on_change(restart_map, stopstart=False):
213
"""Restart services based on configuration files changing
215
This function is used a decorator, for example
218
'/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
220
def ceph_client_changed():
223
In this example, the cinder-api and cinder-volume services
224
would be restarted if /etc/ceph/ceph.conf is changed by the
225
ceph_client_changed function.
228
def wrapped_f(*args):
230
for path in restart_map:
231
checksums[path] = file_hash(path)
234
for path in restart_map:
235
if checksums[path] != file_hash(path):
236
restarts += restart_map[path]
237
services_list = list(OrderedDict.fromkeys(restarts))
239
for service_name in services_list:
240
service('restart', service_name)
242
for action in ['stop', 'start']:
243
for service_name in services_list:
244
service(action, service_name)
250
"""Return /etc/lsb-release in a dict"""
252
with open('/etc/lsb-release', 'r') as lsb:
255
d[k.strip()] = v.strip()
259
def pwgen(length=None):
260
"""Generate a random pasword."""
262
length = random.choice(range(35, 45))
263
alphanumeric_chars = [
264
l for l in (string.letters + string.digits)
265
if l not in 'l0QD1vAEIOUaeiou']
267
random.choice(alphanumeric_chars) for _ in range(length)]
268
return(''.join(random_chars))
271
def list_nics(nic_type):
272
'''Return a list of nics of given type(s)'''
273
if isinstance(nic_type, basestring):
274
int_types = [nic_type]
278
for int_type in int_types:
279
cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
280
ip_output = subprocess.check_output(cmd).split('\n')
281
ip_output = (line for line in ip_output if line)
282
for line in ip_output:
283
if line.split()[1].startswith(int_type):
284
interfaces.append(line.split()[1].replace(":", ""))
288
def set_nic_mtu(nic, mtu):
289
'''Set MTU on a network interface'''
290
cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
291
subprocess.check_call(cmd)
294
def get_nic_mtu(nic):
295
cmd = ['ip', 'addr', 'show', nic]
296
ip_output = subprocess.check_output(cmd).split('\n')
298
for line in ip_output:
301
mtu = words[words.index("mtu") + 1]
305
def get_nic_hwaddr(nic):
306
cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
307
ip_output = subprocess.check_output(cmd)
309
words = ip_output.split()
310
if 'link/ether' in words:
311
hwaddr = words[words.index('link/ether') + 1]
315
def cmp_pkgrevno(package, revno, pkgcache=None):
316
'''Compare supplied revno with the revno of the installed package
317
1 => Installed revno is greater than supplied arg
318
0 => Installed revno is the same as supplied arg
319
-1 => Installed revno is less than supplied arg
323
pkgcache = apt_pkg.Cache()
324
pkg = pkgcache[package]
325
return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)