~1chb1n/charms/trusty/nova-cloud-controller/15.10-stable-flip-tests-helper-syncs

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/core/host.py

Check in start of py redux.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Tools for working with the host system"""
 
2
# Copyright 2012 Canonical Ltd.
 
3
#
 
4
# Authors:
 
5
#  Nick Moffitt <nick.moffitt@canonical.com>
 
6
#  Matthew Wedgwood <matthew.wedgwood@canonical.com>
 
7
 
 
8
import apt_pkg
 
9
import os
 
10
import pwd
 
11
import grp
 
12
import random
 
13
import string
 
14
import subprocess
 
15
import hashlib
 
16
 
 
17
from collections import OrderedDict
 
18
 
 
19
from hookenv import log
 
20
 
 
21
 
 
22
def service_start(service_name):
 
23
    service('start', service_name)
 
24
 
 
25
 
 
26
def service_stop(service_name):
 
27
    service('stop', service_name)
 
28
 
 
29
 
 
30
def service_restart(service_name):
 
31
    service('restart', service_name)
 
32
 
 
33
 
 
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)
 
37
 
 
38
 
 
39
def service(action, service_name):
 
40
    cmd = ['service', service_name, action]
 
41
    return subprocess.call(cmd) == 0
 
42
 
 
43
 
 
44
def service_running(service):
 
45
    try:
 
46
        output = subprocess.check_output(['service', service, 'status'])
 
47
    except subprocess.CalledProcessError:
 
48
        return False
 
49
    else:
 
50
        if ("start/running" in output or "is running" in output):
 
51
            return True
 
52
        else:
 
53
            return False
 
54
 
 
55
 
 
56
def adduser(username, password=None, shell='/bin/bash', system_user=False):
 
57
    """Add a user"""
 
58
    try:
 
59
        user_info = pwd.getpwnam(username)
 
60
        log('user {0} already exists!'.format(username))
 
61
    except KeyError:
 
62
        log('creating user {0}'.format(username))
 
63
        cmd = ['useradd']
 
64
        if system_user or password is None:
 
65
            cmd.append('--system')
 
66
        else:
 
67
            cmd.extend([
 
68
                '--create-home',
 
69
                '--shell', shell,
 
70
                '--password', password,
 
71
            ])
 
72
        cmd.append(username)
 
73
        subprocess.check_call(cmd)
 
74
        user_info = pwd.getpwnam(username)
 
75
    return user_info
 
76
 
 
77
 
 
78
def add_user_to_group(username, group):
 
79
    """Add a user to a group"""
 
80
    cmd = [
 
81
        'gpasswd', '-a',
 
82
        username,
 
83
        group
 
84
    ]
 
85
    log("Adding user {} to group {}".format(username, group))
 
86
    subprocess.check_call(cmd)
 
87
 
 
88
 
 
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]
 
93
    cmd.extend(options)
 
94
    cmd.append(from_path)
 
95
    cmd.append(to_path)
 
96
    log(" ".join(cmd))
 
97
    return subprocess.check_output(cmd).strip()
 
98
 
 
99
 
 
100
def symlink(source, destination):
 
101
    """Create a symbolic link"""
 
102
    log("Symlinking {} as {}".format(source, destination))
 
103
    cmd = [
 
104
        'ln',
 
105
        '-sf',
 
106
        source,
 
107
        destination,
 
108
    ]
 
109
    subprocess.check_call(cmd)
 
110
 
 
111
 
 
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,
 
115
                                          perms))
 
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))
 
122
            os.unlink(realpath)
 
123
    else:
 
124
        os.makedirs(realpath, perms)
 
125
    os.chown(realpath, uid, gid)
 
126
 
 
127
 
 
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)
 
137
 
 
138
 
 
139
def filter_installed_packages(packages):
 
140
    """Returns a list of packages that require installation"""
 
141
    apt_pkg.init()
 
142
    cache = apt_pkg.Cache()
 
143
    _pkgs = []
 
144
    for package in packages:
 
145
        try:
 
146
            p = cache[package]
 
147
            p.current_ver or _pkgs.append(package)
 
148
        except KeyError:
 
149
            log('Package {} has no installation candidate.'.format(package),
 
150
                level='WARNING')
 
151
            _pkgs.append(package)
 
152
    return _pkgs
 
153
 
 
154
 
 
155
def apt_install(packages, options=None, fatal=False):
 
156
    """Install one or more packages"""
 
157
    options = options or []
 
158
    cmd = ['apt-get', '-y']
 
159
    cmd.extend(options)
 
160
    cmd.append('install')
 
161
    if isinstance(packages, basestring):
 
162
        cmd.append(packages)
 
163
    else:
 
164
        cmd.extend(packages)
 
165
    log("Installing {} with options: {}".format(packages,
 
166
                                                options))
 
167
    if fatal:
 
168
        subprocess.check_call(cmd)
 
169
    else:
 
170
        subprocess.call(cmd)
 
171
 
 
172
 
 
173
def apt_update(fatal=False):
 
174
    """Update local apt cache"""
 
175
    cmd = ['apt-get', 'update']
 
176
    if fatal:
 
177
        subprocess.check_call(cmd)
 
178
    else:
 
179
        subprocess.call(cmd)
 
180
 
 
181
 
 
182
def mount(device, mountpoint, options=None, persist=False):
 
183
    '''Mount a filesystem'''
 
184
    cmd_args = ['mount']
 
185
    if options is not None:
 
186
        cmd_args.extend(['-o', options])
 
187
    cmd_args.extend([device, mountpoint])
 
188
    try:
 
189
        subprocess.check_output(cmd_args)
 
190
    except subprocess.CalledProcessError, e:
 
191
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
 
192
        return False
 
193
    if persist:
 
194
        # TODO: update fstab
 
195
        pass
 
196
    return True
 
197
 
 
198
 
 
199
def umount(mountpoint, persist=False):
 
200
    '''Unmount a filesystem'''
 
201
    cmd_args = ['umount', mountpoint]
 
202
    try:
 
203
        subprocess.check_output(cmd_args)
 
204
    except subprocess.CalledProcessError, e:
 
205
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
206
        return False
 
207
    if persist:
 
208
        # TODO: update fstab
 
209
        pass
 
210
    return True
 
211
 
 
212
 
 
213
def mounts():
 
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()]]
 
219
    return system_mounts
 
220
 
 
221
 
 
222
def file_hash(path):
 
223
    ''' Generate a md5 hash of the contents of 'path' or None if not found '''
 
224
    if os.path.exists(path):
 
225
        h = hashlib.md5()
 
226
        with open(path, 'r') as source:
 
227
            h.update(source.read())  # IGNORE:E1101 - it does have update
 
228
        return h.hexdigest()
 
229
    else:
 
230
        return None
 
231
 
 
232
 
 
233
def restart_on_change(restart_map):
 
234
    ''' Restart services based on configuration files changing
 
235
 
 
236
    This function is used a decorator, for example
 
237
 
 
238
        @restart_on_change({
 
239
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
240
            })
 
241
        def ceph_client_changed():
 
242
            ...
 
243
 
 
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.
 
247
    '''
 
248
    def wrap(f):
 
249
        def wrapped_f(*args):
 
250
            checksums = {}
 
251
            for path in restart_map:
 
252
                checksums[path] = file_hash(path)
 
253
            f(*args)
 
254
            restarts = []
 
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)
 
260
        return wrapped_f
 
261
    return wrap
 
262
 
 
263
 
 
264
def lsb_release():
 
265
    '''Return /etc/lsb-release in a dict'''
 
266
    d = {}
 
267
    with open('/etc/lsb-release', 'r') as lsb:
 
268
        for l in lsb:
 
269
            k, v = l.split('=')
 
270
            d[k.strip()] = v.strip()
 
271
    return d
 
272
 
 
273
 
 
274
def pwgen(length=None):
 
275
    '''Generate a random pasword.'''
 
276
    if length is None:
 
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']
 
281
    random_chars = [
 
282
        random.choice(alphanumeric_chars) for _ in range(length)]
 
283
    return(''.join(random_chars))