~openstack-charmers/charms/precise/ceilometer-agent/old-1410

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2013-10-14 16:10:30 UTC
  • Revision ID: james.page@canonical.com-20131014161030-x89xxf581s07ayzy
Rebase on charmhelpers for havana

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 os
 
9
import pwd
 
10
import grp
 
11
import random
 
12
import string
 
13
import subprocess
 
14
import hashlib
 
15
 
 
16
from collections import OrderedDict
 
17
 
 
18
from hookenv import log
 
19
 
 
20
 
 
21
def service_start(service_name):
 
22
    return service('start', service_name)
 
23
 
 
24
 
 
25
def service_stop(service_name):
 
26
    return service('stop', service_name)
 
27
 
 
28
 
 
29
def service_restart(service_name):
 
30
    return service('restart', service_name)
 
31
 
 
32
 
 
33
def service_reload(service_name, restart_on_failure=False):
 
34
    service_result = service('reload', service_name)
 
35
    if not service_result and restart_on_failure:
 
36
        service_result = service('restart', service_name)
 
37
    return service_result
 
38
 
 
39
 
 
40
def service(action, service_name):
 
41
    cmd = ['service', service_name, action]
 
42
    return subprocess.call(cmd) == 0
 
43
 
 
44
 
 
45
def service_running(service):
 
46
    try:
 
47
        output = subprocess.check_output(['service', service, 'status'])
 
48
    except subprocess.CalledProcessError:
 
49
        return False
 
50
    else:
 
51
        if ("start/running" in output or "is running" in output):
 
52
            return True
 
53
        else:
 
54
            return False
 
55
 
 
56
 
 
57
def adduser(username, password=None, shell='/bin/bash', system_user=False):
 
58
    """Add a user"""
 
59
    try:
 
60
        user_info = pwd.getpwnam(username)
 
61
        log('user {0} already exists!'.format(username))
 
62
    except KeyError:
 
63
        log('creating user {0}'.format(username))
 
64
        cmd = ['useradd']
 
65
        if system_user or password is None:
 
66
            cmd.append('--system')
 
67
        else:
 
68
            cmd.extend([
 
69
                '--create-home',
 
70
                '--shell', shell,
 
71
                '--password', password,
 
72
            ])
 
73
        cmd.append(username)
 
74
        subprocess.check_call(cmd)
 
75
        user_info = pwd.getpwnam(username)
 
76
    return user_info
 
77
 
 
78
 
 
79
def add_user_to_group(username, group):
 
80
    """Add a user to a group"""
 
81
    cmd = [
 
82
        'gpasswd', '-a',
 
83
        username,
 
84
        group
 
85
    ]
 
86
    log("Adding user {} to group {}".format(username, group))
 
87
    subprocess.check_call(cmd)
 
88
 
 
89
 
 
90
def rsync(from_path, to_path, flags='-r', options=None):
 
91
    """Replicate the contents of a path"""
 
92
    options = options or ['--delete', '--executability']
 
93
    cmd = ['/usr/bin/rsync', flags]
 
94
    cmd.extend(options)
 
95
    cmd.append(from_path)
 
96
    cmd.append(to_path)
 
97
    log(" ".join(cmd))
 
98
    return subprocess.check_output(cmd).strip()
 
99
 
 
100
 
 
101
def symlink(source, destination):
 
102
    """Create a symbolic link"""
 
103
    log("Symlinking {} as {}".format(source, destination))
 
104
    cmd = [
 
105
        'ln',
 
106
        '-sf',
 
107
        source,
 
108
        destination,
 
109
    ]
 
110
    subprocess.check_call(cmd)
 
111
 
 
112
 
 
113
def mkdir(path, owner='root', group='root', perms=0555, force=False):
 
114
    """Create a directory"""
 
115
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
 
116
                                          perms))
 
117
    uid = pwd.getpwnam(owner).pw_uid
 
118
    gid = grp.getgrnam(group).gr_gid
 
119
    realpath = os.path.abspath(path)
 
120
    if os.path.exists(realpath):
 
121
        if force and not os.path.isdir(realpath):
 
122
            log("Removing non-directory file {} prior to mkdir()".format(path))
 
123
            os.unlink(realpath)
 
124
    else:
 
125
        os.makedirs(realpath, perms)
 
126
    os.chown(realpath, uid, gid)
 
127
 
 
128
 
 
129
def write_file(path, content, owner='root', group='root', perms=0444):
 
130
    """Create or overwrite a file with the contents of a string"""
 
131
    log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
 
132
    uid = pwd.getpwnam(owner).pw_uid
 
133
    gid = grp.getgrnam(group).gr_gid
 
134
    with open(path, 'w') as target:
 
135
        os.fchown(target.fileno(), uid, gid)
 
136
        os.fchmod(target.fileno(), perms)
 
137
        target.write(content)
 
138
 
 
139
 
 
140
def mount(device, mountpoint, options=None, persist=False):
 
141
    '''Mount a filesystem'''
 
142
    cmd_args = ['mount']
 
143
    if options is not None:
 
144
        cmd_args.extend(['-o', options])
 
145
    cmd_args.extend([device, mountpoint])
 
146
    try:
 
147
        subprocess.check_output(cmd_args)
 
148
    except subprocess.CalledProcessError, e:
 
149
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
 
150
        return False
 
151
    if persist:
 
152
        # TODO: update fstab
 
153
        pass
 
154
    return True
 
155
 
 
156
 
 
157
def umount(mountpoint, persist=False):
 
158
    '''Unmount a filesystem'''
 
159
    cmd_args = ['umount', mountpoint]
 
160
    try:
 
161
        subprocess.check_output(cmd_args)
 
162
    except subprocess.CalledProcessError, e:
 
163
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
164
        return False
 
165
    if persist:
 
166
        # TODO: update fstab
 
167
        pass
 
168
    return True
 
169
 
 
170
 
 
171
def mounts():
 
172
    '''List of all mounted volumes as [[mountpoint,device],[...]]'''
 
173
    with open('/proc/mounts') as f:
 
174
        # [['/mount/point','/dev/path'],[...]]
 
175
        system_mounts = [m[1::-1] for m in [l.strip().split()
 
176
                                            for l in f.readlines()]]
 
177
    return system_mounts
 
178
 
 
179
 
 
180
def file_hash(path):
 
181
    ''' Generate a md5 hash of the contents of 'path' or None if not found '''
 
182
    if os.path.exists(path):
 
183
        h = hashlib.md5()
 
184
        with open(path, 'r') as source:
 
185
            h.update(source.read())  # IGNORE:E1101 - it does have update
 
186
        return h.hexdigest()
 
187
    else:
 
188
        return None
 
189
 
 
190
 
 
191
def restart_on_change(restart_map):
 
192
    ''' Restart services based on configuration files changing
 
193
 
 
194
    This function is used a decorator, for example
 
195
 
 
196
        @restart_on_change({
 
197
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
198
            })
 
199
        def ceph_client_changed():
 
200
            ...
 
201
 
 
202
    In this example, the cinder-api and cinder-volume services
 
203
    would be restarted if /etc/ceph/ceph.conf is changed by the
 
204
    ceph_client_changed function.
 
205
    '''
 
206
    def wrap(f):
 
207
        def wrapped_f(*args):
 
208
            checksums = {}
 
209
            for path in restart_map:
 
210
                checksums[path] = file_hash(path)
 
211
            f(*args)
 
212
            restarts = []
 
213
            for path in restart_map:
 
214
                if checksums[path] != file_hash(path):
 
215
                    restarts += restart_map[path]
 
216
            for service_name in list(OrderedDict.fromkeys(restarts)):
 
217
                service('restart', service_name)
 
218
        return wrapped_f
 
219
    return wrap
 
220
 
 
221
 
 
222
def lsb_release():
 
223
    '''Return /etc/lsb-release in a dict'''
 
224
    d = {}
 
225
    with open('/etc/lsb-release', 'r') as lsb:
 
226
        for l in lsb:
 
227
            k, v = l.split('=')
 
228
            d[k.strip()] = v.strip()
 
229
    return d
 
230
 
 
231
 
 
232
def pwgen(length=None):
 
233
    '''Generate a random pasword.'''
 
234
    if length is None:
 
235
        length = random.choice(range(35, 45))
 
236
    alphanumeric_chars = [
 
237
        l for l in (string.letters + string.digits)
 
238
        if l not in 'l0QD1vAEIOUaeiou']
 
239
    random_chars = [
 
240
        random.choice(alphanumeric_chars) for _ in range(length)]
 
241
    return(''.join(random_chars))