~cprov/charms/trusty/core-image-publisher/glanceclient-builddeps

« back to all changes in this revision

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

  • Committer: Celso Providelo
  • Date: 2015-03-25 04:13:43 UTC
  • Revision ID: celso.providelo@canonical.com-20150325041343-jw05jaz6jscs3c8f
fork of core-image-watcher

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 '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
        length = random.choice(range(35, 45))
 
343
    alphanumeric_chars = [
 
344
        l for l in (string.ascii_letters + string.digits)
 
345
        if l not in 'l0QD1vAEIOUaeiou']
 
346
    random_chars = [
 
347
        random.choice(alphanumeric_chars) for _ in range(length)]
 
348
    return(''.join(random_chars))
 
349
 
 
350
 
 
351
def list_nics(nic_type):
 
352
    '''Return a list of nics of given type(s)'''
 
353
    if isinstance(nic_type, six.string_types):
 
354
        int_types = [nic_type]
 
355
    else:
 
356
        int_types = nic_type
 
357
    interfaces = []
 
358
    for int_type in int_types:
 
359
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
360
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
 
361
        ip_output = (line for line in ip_output if line)
 
362
        for line in ip_output:
 
363
            if line.split()[1].startswith(int_type):
 
364
                matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
 
365
                if matched:
 
366
                    interface = matched.groups()[0]
 
367
                else:
 
368
                    interface = line.split()[1].replace(":", "")
 
369
                interfaces.append(interface)
 
370
 
 
371
    return interfaces
 
372
 
 
373
 
 
374
def set_nic_mtu(nic, mtu):
 
375
    '''Set MTU on a network interface'''
 
376
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
 
377
    subprocess.check_call(cmd)
 
378
 
 
379
 
 
380
def get_nic_mtu(nic):
 
381
    cmd = ['ip', 'addr', 'show', nic]
 
382
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
 
383
    mtu = ""
 
384
    for line in ip_output:
 
385
        words = line.split()
 
386
        if 'mtu' in words:
 
387
            mtu = words[words.index("mtu") + 1]
 
388
    return mtu
 
389
 
 
390
 
 
391
def get_nic_hwaddr(nic):
 
392
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
 
393
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
 
394
    hwaddr = ""
 
395
    words = ip_output.split()
 
396
    if 'link/ether' in words:
 
397
        hwaddr = words[words.index('link/ether') + 1]
 
398
    return hwaddr
 
399
 
 
400
 
 
401
def cmp_pkgrevno(package, revno, pkgcache=None):
 
402
    '''Compare supplied revno with the revno of the installed package
 
403
 
 
404
    *  1 => Installed revno is greater than supplied arg
 
405
    *  0 => Installed revno is the same as supplied arg
 
406
    * -1 => Installed revno is less than supplied arg
 
407
 
 
408
    This function imports apt_cache function from charmhelpers.fetch if
 
409
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
 
410
    you call this function, or pass an apt_pkg.Cache() instance.
 
411
    '''
 
412
    import apt_pkg
 
413
    if not pkgcache:
 
414
        from charmhelpers.fetch import apt_cache
 
415
        pkgcache = apt_cache()
 
416
    pkg = pkgcache[package]
 
417
    return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
 
418
 
 
419
 
 
420
@contextmanager
 
421
def chdir(d):
 
422
    cur = os.getcwd()
 
423
    try:
 
424
        yield os.chdir(d)
 
425
    finally:
 
426
        os.chdir(cur)
 
427
 
 
428
 
 
429
def chownr(path, owner, group, follow_links=True):
 
430
    uid = pwd.getpwnam(owner).pw_uid
 
431
    gid = grp.getgrnam(group).gr_gid
 
432
    if follow_links:
 
433
        chown = os.chown
 
434
    else:
 
435
        chown = os.lchown
 
436
 
 
437
    for root, dirs, files in os.walk(path):
 
438
        for name in dirs + files:
 
439
            full = os.path.join(root, name)
 
440
            broken_symlink = os.path.lexists(full) and not os.path.exists(full)
 
441
            if not broken_symlink:
 
442
                chown(full, uid, gid)
 
443
 
 
444
 
 
445
def lchownr(path, owner, group):
 
446
    chownr(path, owner, group, follow_links=False)