~jorge/charms/precise/mysql/fix-metadata

« back to all changes in this revision

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

  • Committer: Edward Hope-Morley
  • Date: 2014-02-19 14:49:31 UTC
  • mto: This revision was merged to the branch mainline in revision 121.
  • Revision ID: edward.hope-morley@canonical.com-20140219144931-ujfwlf11fx2y55h4
[dosaboy] added support for 'source' and 'key' config options so that
          an alternative archive can be added to get more recent
          packages e.g. ceph packages from the coud archive.

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
    """Start a system service"""
 
23
    return service('start', service_name)
 
24
 
 
25
 
 
26
def service_stop(service_name):
 
27
    """Stop a system service"""
 
28
    return service('stop', service_name)
 
29
 
 
30
 
 
31
def service_restart(service_name):
 
32
    """Restart a system service"""
 
33
    return service('restart', service_name)
 
34
 
 
35
 
 
36
def service_reload(service_name, restart_on_failure=False):
 
37
    """Reload a system service, optionally falling back to restart if reload fails"""
 
38
    service_result = service('reload', service_name)
 
39
    if not service_result and restart_on_failure:
 
40
        service_result = service('restart', service_name)
 
41
    return service_result
 
42
 
 
43
 
 
44
def service(action, service_name):
 
45
    """Control a system service"""
 
46
    cmd = ['service', service_name, action]
 
47
    return subprocess.call(cmd) == 0
 
48
 
 
49
 
 
50
def service_running(service):
 
51
    """Determine whether a system service is running"""
 
52
    try:
 
53
        output = subprocess.check_output(['service', service, 'status'])
 
54
    except subprocess.CalledProcessError:
 
55
        return False
 
56
    else:
 
57
        if ("start/running" in output or "is running" in output):
 
58
            return True
 
59
        else:
 
60
            return False
 
61
 
 
62
 
 
63
def adduser(username, password=None, shell='/bin/bash', system_user=False):
 
64
    """Add a user to the system"""
 
65
    try:
 
66
        user_info = pwd.getpwnam(username)
 
67
        log('user {0} already exists!'.format(username))
 
68
    except KeyError:
 
69
        log('creating user {0}'.format(username))
 
70
        cmd = ['useradd']
 
71
        if system_user or password is None:
 
72
            cmd.append('--system')
 
73
        else:
 
74
            cmd.extend([
 
75
                '--create-home',
 
76
                '--shell', shell,
 
77
                '--password', password,
 
78
            ])
 
79
        cmd.append(username)
 
80
        subprocess.check_call(cmd)
 
81
        user_info = pwd.getpwnam(username)
 
82
    return user_info
 
83
 
 
84
 
 
85
def add_user_to_group(username, group):
 
86
    """Add a user to a group"""
 
87
    cmd = [
 
88
        'gpasswd', '-a',
 
89
        username,
 
90
        group
 
91
    ]
 
92
    log("Adding user {} to group {}".format(username, group))
 
93
    subprocess.check_call(cmd)
 
94
 
 
95
 
 
96
def rsync(from_path, to_path, flags='-r', options=None):
 
97
    """Replicate the contents of a path"""
 
98
    options = options or ['--delete', '--executability']
 
99
    cmd = ['/usr/bin/rsync', flags]
 
100
    cmd.extend(options)
 
101
    cmd.append(from_path)
 
102
    cmd.append(to_path)
 
103
    log(" ".join(cmd))
 
104
    return subprocess.check_output(cmd).strip()
 
105
 
 
106
 
 
107
def symlink(source, destination):
 
108
    """Create a symbolic link"""
 
109
    log("Symlinking {} as {}".format(source, destination))
 
110
    cmd = [
 
111
        'ln',
 
112
        '-sf',
 
113
        source,
 
114
        destination,
 
115
    ]
 
116
    subprocess.check_call(cmd)
 
117
 
 
118
 
 
119
def mkdir(path, owner='root', group='root', perms=0555, force=False):
 
120
    """Create a directory"""
 
121
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
 
122
                                          perms))
 
123
    uid = pwd.getpwnam(owner).pw_uid
 
124
    gid = grp.getgrnam(group).gr_gid
 
125
    realpath = os.path.abspath(path)
 
126
    if os.path.exists(realpath):
 
127
        if force and not os.path.isdir(realpath):
 
128
            log("Removing non-directory file {} prior to mkdir()".format(path))
 
129
            os.unlink(realpath)
 
130
    else:
 
131
        os.makedirs(realpath, perms)
 
132
    os.chown(realpath, uid, gid)
 
133
 
 
134
 
 
135
def write_file(path, content, owner='root', group='root', perms=0444):
 
136
    """Create or overwrite a file with the contents of a string"""
 
137
    log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
 
138
    uid = pwd.getpwnam(owner).pw_uid
 
139
    gid = grp.getgrnam(group).gr_gid
 
140
    with open(path, 'w') as target:
 
141
        os.fchown(target.fileno(), uid, gid)
 
142
        os.fchmod(target.fileno(), perms)
 
143
        target.write(content)
 
144
 
 
145
 
 
146
def mount(device, mountpoint, options=None, persist=False):
 
147
    """Mount a filesystem at a particular mountpoint"""
 
148
    cmd_args = ['mount']
 
149
    if options is not None:
 
150
        cmd_args.extend(['-o', options])
 
151
    cmd_args.extend([device, mountpoint])
 
152
    try:
 
153
        subprocess.check_output(cmd_args)
 
154
    except subprocess.CalledProcessError, e:
 
155
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
 
156
        return False
 
157
    if persist:
 
158
        # TODO: update fstab
 
159
        pass
 
160
    return True
 
161
 
 
162
 
 
163
def umount(mountpoint, persist=False):
 
164
    """Unmount a filesystem"""
 
165
    cmd_args = ['umount', mountpoint]
 
166
    try:
 
167
        subprocess.check_output(cmd_args)
 
168
    except subprocess.CalledProcessError, e:
 
169
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
170
        return False
 
171
    if persist:
 
172
        # TODO: update fstab
 
173
        pass
 
174
    return True
 
175
 
 
176
 
 
177
def mounts():
 
178
    """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
 
179
    with open('/proc/mounts') as f:
 
180
        # [['/mount/point','/dev/path'],[...]]
 
181
        system_mounts = [m[1::-1] for m in [l.strip().split()
 
182
                                            for l in f.readlines()]]
 
183
    return system_mounts
 
184
 
 
185
 
 
186
def file_hash(path):
 
187
    """Generate a md5 hash of the contents of 'path' or None if not found """
 
188
    if os.path.exists(path):
 
189
        h = hashlib.md5()
 
190
        with open(path, 'r') as source:
 
191
            h.update(source.read())  # IGNORE:E1101 - it does have update
 
192
        return h.hexdigest()
 
193
    else:
 
194
        return None
 
195
 
 
196
 
 
197
def restart_on_change(restart_map, stopstart=False):
 
198
    """Restart services based on configuration files changing
 
199
 
 
200
    This function is used a decorator, for example
 
201
 
 
202
        @restart_on_change({
 
203
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
204
            })
 
205
        def ceph_client_changed():
 
206
            ...
 
207
 
 
208
    In this example, the cinder-api and cinder-volume services
 
209
    would be restarted if /etc/ceph/ceph.conf is changed by the
 
210
    ceph_client_changed function.
 
211
    """
 
212
    def wrap(f):
 
213
        def wrapped_f(*args):
 
214
            checksums = {}
 
215
            for path in restart_map:
 
216
                checksums[path] = file_hash(path)
 
217
            f(*args)
 
218
            restarts = []
 
219
            for path in restart_map:
 
220
                if checksums[path] != file_hash(path):
 
221
                    restarts += restart_map[path]
 
222
            services_list = list(OrderedDict.fromkeys(restarts))
 
223
            if not stopstart:
 
224
                for service_name in services_list:
 
225
                    service('restart', service_name)
 
226
            else:
 
227
                for action in ['stop', 'start']:
 
228
                    for service_name in services_list:
 
229
                        service(action, service_name)
 
230
        return wrapped_f
 
231
    return wrap
 
232
 
 
233
 
 
234
def lsb_release():
 
235
    """Return /etc/lsb-release in a dict"""
 
236
    d = {}
 
237
    with open('/etc/lsb-release', 'r') as lsb:
 
238
        for l in lsb:
 
239
            k, v = l.split('=')
 
240
            d[k.strip()] = v.strip()
 
241
    return d
 
242
 
 
243
 
 
244
def pwgen(length=None):
 
245
    """Generate a random pasword."""
 
246
    if length is None:
 
247
        length = random.choice(range(35, 45))
 
248
    alphanumeric_chars = [
 
249
        l for l in (string.letters + string.digits)
 
250
        if l not in 'l0QD1vAEIOUaeiou']
 
251
    random_chars = [
 
252
        random.choice(alphanumeric_chars) for _ in range(length)]
 
253
    return(''.join(random_chars))
 
254
 
 
255
 
 
256
def list_nics(nic_type):
 
257
    '''Return a list of nics of given type(s)'''
 
258
    if isinstance(nic_type, basestring):
 
259
        int_types = [nic_type]
 
260
    else:
 
261
        int_types = nic_type
 
262
    interfaces = []
 
263
    for int_type in int_types:
 
264
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
265
        ip_output = subprocess.check_output(cmd).split('\n')
 
266
        ip_output = (line for line in ip_output if line)
 
267
        for line in ip_output:
 
268
            if line.split()[1].startswith(int_type):
 
269
                interfaces.append(line.split()[1].replace(":", ""))
 
270
    return interfaces
 
271
 
 
272
 
 
273
def set_nic_mtu(nic, mtu):
 
274
    '''Set MTU on a network interface'''
 
275
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
 
276
    subprocess.check_call(cmd)
 
277
 
 
278
 
 
279
def get_nic_mtu(nic):
 
280
    cmd = ['ip', 'addr', 'show', nic]
 
281
    ip_output = subprocess.check_output(cmd).split('\n')
 
282
    mtu = ""
 
283
    for line in ip_output:
 
284
        words = line.split()
 
285
        if 'mtu' in words:
 
286
            mtu = words[words.index("mtu") + 1]
 
287
    return mtu
 
288
 
 
289
 
 
290
def get_nic_hwaddr(nic):
 
291
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
 
292
    ip_output = subprocess.check_output(cmd)
 
293
    hwaddr = ""
 
294
    words = ip_output.split()
 
295
    if 'link/ether' in words:
 
296
        hwaddr = words[words.index('link/ether') + 1]
 
297
    return hwaddr