~junaidali/charms/trusty/plumgrid-director/pg-restart

« back to all changes in this revision

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

  • Committer: bbaqar at plumgrid
  • Date: 2015-07-29 18:07:31 UTC
  • Revision ID: bbaqar@plumgrid.com-20150729180731-ioynar8x3u5pxytc
Addressed reviews by Charmers

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2014-2015 Canonical Limited.
2
 
#
3
 
# This file is part of charm-helpers.
4
 
#
5
 
# charm-helpers is free software: you can redistribute it and/or modify
6
 
# it under the terms of the GNU Lesser General Public License version 3 as
7
 
# published by the Free Software Foundation.
8
 
#
9
 
# charm-helpers is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU Lesser General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU Lesser General Public License
15
 
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
16
 
 
17
 
"""Tools for working with the host system"""
18
 
# Copyright 2012 Canonical Ltd.
19
 
#
20
 
# Authors:
21
 
#  Nick Moffitt <nick.moffitt@canonical.com>
22
 
#  Matthew Wedgwood <matthew.wedgwood@canonical.com>
23
 
 
24
 
import os
25
 
import re
26
 
import pwd
27
 
import grp
28
 
import random
29
 
import string
30
 
import subprocess
31
 
import hashlib
32
 
from contextlib import contextmanager
33
 
from collections import OrderedDict
34
 
 
35
 
import six
36
 
 
37
 
from .hookenv import log
38
 
from .fstab import Fstab
39
 
 
40
 
 
41
 
def service_start(service_name):
42
 
    """Start a system service"""
43
 
    return service('start', service_name)
44
 
 
45
 
 
46
 
def service_stop(service_name):
47
 
    """Stop a system service"""
48
 
    return service('stop', service_name)
49
 
 
50
 
 
51
 
def service_restart(service_name):
52
 
    """Restart a system service"""
53
 
    return service('restart', service_name)
54
 
 
55
 
 
56
 
def service_reload(service_name, restart_on_failure=False):
57
 
    """Reload a system service, optionally falling back to restart if
58
 
    reload fails"""
59
 
    service_result = service('reload', service_name)
60
 
    if not service_result and restart_on_failure:
61
 
        service_result = service('restart', service_name)
62
 
    return service_result
63
 
 
64
 
 
65
 
def service(action, service_name):
66
 
    """Control a system service"""
67
 
    cmd = ['service', service_name, action]
68
 
    return subprocess.call(cmd) == 0
69
 
 
70
 
 
71
 
def service_running(service):
72
 
    """Determine whether a system service is running"""
73
 
    try:
74
 
        output = subprocess.check_output(
75
 
            ['service', service, 'status'],
76
 
            stderr=subprocess.STDOUT).decode('UTF-8')
77
 
    except subprocess.CalledProcessError:
78
 
        return False
79
 
    else:
80
 
        if ("start/running" in output or "is running" in output):
81
 
            return True
82
 
        else:
83
 
            return False
84
 
 
85
 
 
86
 
def service_available(service_name):
87
 
    """Determine whether a system service is available"""
88
 
    try:
89
 
        subprocess.check_output(
90
 
            ['service', service_name, 'status'],
91
 
            stderr=subprocess.STDOUT).decode('UTF-8')
92
 
    except subprocess.CalledProcessError as e:
93
 
        return b'unrecognized service' not in e.output
94
 
    else:
95
 
        return True
96
 
 
97
 
 
98
 
def adduser(username, password=None, shell='/bin/bash', system_user=False):
99
 
    """Add a user to the system"""
100
 
    try:
101
 
        user_info = pwd.getpwnam(username)
102
 
        log('user {0} already exists!'.format(username))
103
 
    except KeyError:
104
 
        log('creating user {0}'.format(username))
105
 
        cmd = ['useradd']
106
 
        if system_user or password is None:
107
 
            cmd.append('--system')
108
 
        else:
109
 
            cmd.extend([
110
 
                '--create-home',
111
 
                '--shell', shell,
112
 
                '--password', password,
113
 
            ])
114
 
        cmd.append(username)
115
 
        subprocess.check_call(cmd)
116
 
        user_info = pwd.getpwnam(username)
117
 
    return user_info
118
 
 
119
 
 
120
 
def add_group(group_name, system_group=False):
121
 
    """Add a group to the system"""
122
 
    try:
123
 
        group_info = grp.getgrnam(group_name)
124
 
        log('group {0} already exists!'.format(group_name))
125
 
    except KeyError:
126
 
        log('creating group {0}'.format(group_name))
127
 
        cmd = ['addgroup']
128
 
        if system_group:
129
 
            cmd.append('--system')
130
 
        else:
131
 
            cmd.extend([
132
 
                '--group',
133
 
            ])
134
 
        cmd.append(group_name)
135
 
        subprocess.check_call(cmd)
136
 
        group_info = grp.getgrnam(group_name)
137
 
    return group_info
138
 
 
139
 
 
140
 
def add_user_to_group(username, group):
141
 
    """Add a user to a group"""
142
 
    cmd = [
143
 
        'gpasswd', '-a',
144
 
        username,
145
 
        group
146
 
    ]
147
 
    log("Adding user {} to group {}".format(username, group))
148
 
    subprocess.check_call(cmd)
149
 
 
150
 
 
151
 
def rsync(from_path, to_path, flags='-r', options=None):
152
 
    """Replicate the contents of a path"""
153
 
    options = options or ['--delete', '--executability']
154
 
    cmd = ['/usr/bin/rsync', flags]
155
 
    cmd.extend(options)
156
 
    cmd.append(from_path)
157
 
    cmd.append(to_path)
158
 
    log(" ".join(cmd))
159
 
    return subprocess.check_output(cmd).decode('UTF-8').strip()
160
 
 
161
 
 
162
 
def symlink(source, destination):
163
 
    """Create a symbolic link"""
164
 
    log("Symlinking {} as {}".format(source, destination))
165
 
    cmd = [
166
 
        'ln',
167
 
        '-sf',
168
 
        source,
169
 
        destination,
170
 
    ]
171
 
    subprocess.check_call(cmd)
172
 
 
173
 
 
174
 
def mkdir(path, owner='root', group='root', perms=0o555, force=False):
175
 
    """Create a directory"""
176
 
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
177
 
                                          perms))
178
 
    uid = pwd.getpwnam(owner).pw_uid
179
 
    gid = grp.getgrnam(group).gr_gid
180
 
    realpath = os.path.abspath(path)
181
 
    path_exists = os.path.exists(realpath)
182
 
    if path_exists and force:
183
 
        if not os.path.isdir(realpath):
184
 
            log("Removing non-directory file {} prior to mkdir()".format(path))
185
 
            os.unlink(realpath)
186
 
            os.makedirs(realpath, perms)
187
 
    elif not path_exists:
188
 
        os.makedirs(realpath, perms)
189
 
    os.chown(realpath, uid, gid)
190
 
    os.chmod(realpath, perms)
191
 
 
192
 
 
193
 
def write_file(path, content, owner='root', group='root', perms=0o444):
194
 
    """Create or overwrite a file with the contents of a byte string."""
195
 
    log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
196
 
    uid = pwd.getpwnam(owner).pw_uid
197
 
    gid = grp.getgrnam(group).gr_gid
198
 
    with open(path, 'wb') as target:
199
 
        os.fchown(target.fileno(), uid, gid)
200
 
        os.fchmod(target.fileno(), perms)
201
 
        target.write(content)
202
 
 
203
 
 
204
 
def fstab_remove(mp):
205
 
    """Remove the given mountpoint entry from /etc/fstab
206
 
    """
207
 
    return Fstab.remove_by_mountpoint(mp)
208
 
 
209
 
 
210
 
def fstab_add(dev, mp, fs, options=None):
211
 
    """Adds the given device entry to the /etc/fstab file
212
 
    """
213
 
    return Fstab.add(dev, mp, fs, options=options)
214
 
 
215
 
 
216
 
def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
217
 
    """Mount a filesystem at a particular mountpoint"""
218
 
    cmd_args = ['mount']
219
 
    if options is not None:
220
 
        cmd_args.extend(['-o', options])
221
 
    cmd_args.extend([device, mountpoint])
222
 
    try:
223
 
        subprocess.check_output(cmd_args)
224
 
    except subprocess.CalledProcessError as e:
225
 
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
226
 
        return False
227
 
 
228
 
    if persist:
229
 
        return fstab_add(device, mountpoint, filesystem, options=options)
230
 
    return True
231
 
 
232
 
 
233
 
def umount(mountpoint, persist=False):
234
 
    """Unmount a filesystem"""
235
 
    cmd_args = ['umount', mountpoint]
236
 
    try:
237
 
        subprocess.check_output(cmd_args)
238
 
    except subprocess.CalledProcessError as e:
239
 
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
240
 
        return False
241
 
 
242
 
    if persist:
243
 
        return fstab_remove(mountpoint)
244
 
    return True
245
 
 
246
 
 
247
 
def mounts():
248
 
    """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
249
 
    with open('/proc/mounts') as f:
250
 
        # [['/mount/point','/dev/path'],[...]]
251
 
        system_mounts = [m[1::-1] for m in [l.strip().split()
252
 
                                            for l in f.readlines()]]
253
 
    return system_mounts
254
 
 
255
 
 
256
 
def file_hash(path, hash_type='md5'):
257
 
    """
258
 
    Generate a hash checksum of the contents of 'path' or None if not found.
259
 
 
260
 
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
261
 
                          such as md5, sha1, sha256, sha512, etc.
262
 
    """
263
 
    if os.path.exists(path):
264
 
        h = getattr(hashlib, hash_type)()
265
 
        with open(path, 'rb') as source:
266
 
            h.update(source.read())
267
 
        return h.hexdigest()
268
 
    else:
269
 
        return None
270
 
 
271
 
 
272
 
def check_hash(path, checksum, hash_type='md5'):
273
 
    """
274
 
    Validate a file using a cryptographic checksum.
275
 
 
276
 
    :param str checksum: Value of the checksum used to validate the file.
277
 
    :param str hash_type: Hash algorithm used to generate `checksum`.
278
 
        Can be any hash alrgorithm supported by :mod:`hashlib`,
279
 
        such as md5, sha1, sha256, sha512, etc.
280
 
    :raises ChecksumError: If the file fails the checksum
281
 
 
282
 
    """
283
 
    actual_checksum = file_hash(path, hash_type)
284
 
    if checksum != actual_checksum:
285
 
        raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
286
 
 
287
 
 
288
 
class ChecksumError(ValueError):
289
 
    pass
290
 
 
291
 
 
292
 
def restart_on_change(restart_map, stopstart=False):
293
 
    """Restart services based on configuration files changing
294
 
 
295
 
    This function is used a decorator, for example::
296
 
 
297
 
        @restart_on_change({
298
 
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
299
 
            })
300
 
        def ceph_client_changed():
301
 
            pass  # your code here
302
 
 
303
 
    In this example, the cinder-api and cinder-volume services
304
 
    would be restarted if /etc/ceph/ceph.conf is changed by the
305
 
    ceph_client_changed function.
306
 
    """
307
 
    def wrap(f):
308
 
        def wrapped_f(*args, **kwargs):
309
 
            checksums = {}
310
 
            for path in restart_map:
311
 
                checksums[path] = file_hash(path)
312
 
            f(*args, **kwargs)
313
 
            restarts = []
314
 
            for path in restart_map:
315
 
                if checksums[path] != file_hash(path):
316
 
                    restarts += restart_map[path]
317
 
            services_list = list(OrderedDict.fromkeys(restarts))
318
 
            if not stopstart:
319
 
                for service_name in services_list:
320
 
                    service('restart', service_name)
321
 
            else:
322
 
                for action in ['stop', 'start']:
323
 
                    for service_name in services_list:
324
 
                        service(action, service_name)
325
 
        return wrapped_f
326
 
    return wrap
327
 
 
328
 
 
329
 
def lsb_release():
330
 
    """Return /etc/lsb-release in a dict"""
331
 
    d = {}
332
 
    with open('/etc/lsb-release', 'r') as lsb:
333
 
        for l in lsb:
334
 
            k, v = l.split('=')
335
 
            d[k.strip()] = v.strip()
336
 
    return d
337
 
 
338
 
 
339
 
def pwgen(length=None):
340
 
    """Generate a random pasword."""
341
 
    if length is None:
342
 
        # A random length is ok to use a weak PRNG
343
 
        length = random.choice(range(35, 45))
344
 
    alphanumeric_chars = [
345
 
        l for l in (string.ascii_letters + string.digits)
346
 
        if l not in 'l0QD1vAEIOUaeiou']
347
 
    # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
348
 
    # actual password
349
 
    random_generator = random.SystemRandom()
350
 
    random_chars = [
351
 
        random_generator.choice(alphanumeric_chars) for _ in range(length)]
352
 
    return(''.join(random_chars))
353
 
 
354
 
 
355
 
def list_nics(nic_type):
356
 
    '''Return a list of nics of given type(s)'''
357
 
    if isinstance(nic_type, six.string_types):
358
 
        int_types = [nic_type]
359
 
    else:
360
 
        int_types = nic_type
361
 
    interfaces = []
362
 
    for int_type in int_types:
363
 
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
364
 
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
365
 
        ip_output = (line for line in ip_output if line)
366
 
        for line in ip_output:
367
 
            if line.split()[1].startswith(int_type):
368
 
                matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
369
 
                if matched:
370
 
                    interface = matched.groups()[0]
371
 
                else:
372
 
                    interface = line.split()[1].replace(":", "")
373
 
                interfaces.append(interface)
374
 
 
375
 
    return interfaces
376
 
 
377
 
 
378
 
def set_nic_mtu(nic, mtu):
379
 
    '''Set MTU on a network interface'''
380
 
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
381
 
    subprocess.check_call(cmd)
382
 
 
383
 
 
384
 
def get_nic_mtu(nic):
385
 
    cmd = ['ip', 'addr', 'show', nic]
386
 
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
387
 
    mtu = ""
388
 
    for line in ip_output:
389
 
        words = line.split()
390
 
        if 'mtu' in words:
391
 
            mtu = words[words.index("mtu") + 1]
392
 
    return mtu
393
 
 
394
 
 
395
 
def get_nic_hwaddr(nic):
396
 
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
397
 
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
398
 
    hwaddr = ""
399
 
    words = ip_output.split()
400
 
    if 'link/ether' in words:
401
 
        hwaddr = words[words.index('link/ether') + 1]
402
 
    return hwaddr
403
 
 
404
 
 
405
 
def cmp_pkgrevno(package, revno, pkgcache=None):
406
 
    '''Compare supplied revno with the revno of the installed package
407
 
 
408
 
    *  1 => Installed revno is greater than supplied arg
409
 
    *  0 => Installed revno is the same as supplied arg
410
 
    * -1 => Installed revno is less than supplied arg
411
 
 
412
 
    This function imports apt_cache function from charmhelpers.fetch if
413
 
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
414
 
    you call this function, or pass an apt_pkg.Cache() instance.
415
 
    '''
416
 
    import apt_pkg
417
 
    if not pkgcache:
418
 
        from charmhelpers.fetch import apt_cache
419
 
        pkgcache = apt_cache()
420
 
    pkg = pkgcache[package]
421
 
    return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
422
 
 
423
 
 
424
 
@contextmanager
425
 
def chdir(d):
426
 
    cur = os.getcwd()
427
 
    try:
428
 
        yield os.chdir(d)
429
 
    finally:
430
 
        os.chdir(cur)
431
 
 
432
 
 
433
 
def chownr(path, owner, group, follow_links=True):
434
 
    uid = pwd.getpwnam(owner).pw_uid
435
 
    gid = grp.getgrnam(group).gr_gid
436
 
    if follow_links:
437
 
        chown = os.chown
438
 
    else:
439
 
        chown = os.lchown
440
 
 
441
 
    for root, dirs, files in os.walk(path):
442
 
        for name in dirs + files:
443
 
            full = os.path.join(root, name)
444
 
            broken_symlink = os.path.lexists(full) and not os.path.exists(full)
445
 
            if not broken_symlink:
446
 
                chown(full, uid, gid)
447
 
 
448
 
 
449
 
def lchownr(path, owner, group):
450
 
    chownr(path, owner, group, follow_links=False)