~thomir-deactivatedaccount/charms/trusty/postgresql/fix-for-lxc

« back to all changes in this revision

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

  • Committer: Marco Ceppi
  • Date: 2015-08-07 16:14:56 UTC
  • mfrom: (121.1.7 replication-control)
  • Revision ID: marco@ceppi.net-20150807161456-t5r340idy7nngajs
[stub] Add replication-pause and replication-resume actions

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
 
1
17
"""Tools for working with the host system"""
2
18
# Copyright 2012 Canonical Ltd.
3
19
#
6
22
#  Matthew Wedgwood <matthew.wedgwood@canonical.com>
7
23
 
8
24
import os
 
25
import re
9
26
import pwd
 
27
import glob
10
28
import grp
11
29
import random
12
30
import string
13
31
import subprocess
14
32
import hashlib
15
 
import shutil
16
33
from contextlib import contextmanager
17
 
 
18
34
from collections import OrderedDict
19
35
 
20
 
from hookenv import log
21
 
from fstab import Fstab
 
36
import six
 
37
 
 
38
from .hookenv import log
 
39
from .fstab import Fstab
22
40
 
23
41
 
24
42
def service_start(service_name):
54
72
def service_running(service):
55
73
    """Determine whether a system service is running"""
56
74
    try:
57
 
        output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)
 
75
        output = subprocess.check_output(
 
76
            ['service', service, 'status'],
 
77
            stderr=subprocess.STDOUT).decode('UTF-8')
58
78
    except subprocess.CalledProcessError:
59
79
        return False
60
80
    else:
67
87
def service_available(service_name):
68
88
    """Determine whether a system service is available"""
69
89
    try:
70
 
        subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
71
 
    except subprocess.CalledProcessError:
72
 
        return False
 
90
        subprocess.check_output(
 
91
            ['service', service_name, 'status'],
 
92
            stderr=subprocess.STDOUT).decode('UTF-8')
 
93
    except subprocess.CalledProcessError as e:
 
94
        return b'unrecognized service' not in e.output
73
95
    else:
74
96
        return True
75
97
 
96
118
    return user_info
97
119
 
98
120
 
 
121
def add_group(group_name, system_group=False):
 
122
    """Add a group to the system"""
 
123
    try:
 
124
        group_info = grp.getgrnam(group_name)
 
125
        log('group {0} already exists!'.format(group_name))
 
126
    except KeyError:
 
127
        log('creating group {0}'.format(group_name))
 
128
        cmd = ['addgroup']
 
129
        if system_group:
 
130
            cmd.append('--system')
 
131
        else:
 
132
            cmd.extend([
 
133
                '--group',
 
134
            ])
 
135
        cmd.append(group_name)
 
136
        subprocess.check_call(cmd)
 
137
        group_info = grp.getgrnam(group_name)
 
138
    return group_info
 
139
 
 
140
 
99
141
def add_user_to_group(username, group):
100
142
    """Add a user to a group"""
101
143
    cmd = [
115
157
    cmd.append(from_path)
116
158
    cmd.append(to_path)
117
159
    log(" ".join(cmd))
118
 
    return subprocess.check_output(cmd).strip()
 
160
    return subprocess.check_output(cmd).decode('UTF-8').strip()
119
161
 
120
162
 
121
163
def symlink(source, destination):
130
172
    subprocess.check_call(cmd)
131
173
 
132
174
 
133
 
def mkdir(path, owner='root', group='root', perms=0555, force=False):
 
175
def mkdir(path, owner='root', group='root', perms=0o555, force=False):
134
176
    """Create a directory"""
135
177
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
136
178
                                          perms))
137
179
    uid = pwd.getpwnam(owner).pw_uid
138
180
    gid = grp.getgrnam(group).gr_gid
139
181
    realpath = os.path.abspath(path)
140
 
    if os.path.exists(realpath):
141
 
        if force and not os.path.isdir(realpath):
 
182
    path_exists = os.path.exists(realpath)
 
183
    if path_exists and force:
 
184
        if not os.path.isdir(realpath):
142
185
            log("Removing non-directory file {} prior to mkdir()".format(path))
143
186
            os.unlink(realpath)
144
 
    else:
 
187
            os.makedirs(realpath, perms)
 
188
    elif not path_exists:
145
189
        os.makedirs(realpath, perms)
146
190
    os.chown(realpath, uid, gid)
147
 
 
148
 
 
149
 
def write_file(path, content, owner='root', group='root', perms=0444):
150
 
    """Create or overwrite a file with the contents of a string"""
 
191
    os.chmod(realpath, perms)
 
192
 
 
193
 
 
194
def write_file(path, content, owner='root', group='root', perms=0o444):
 
195
    """Create or overwrite a file with the contents of a byte string."""
151
196
    log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
152
197
    uid = pwd.getpwnam(owner).pw_uid
153
198
    gid = grp.getgrnam(group).gr_gid
154
 
    with open(path, 'w') as target:
 
199
    with open(path, 'wb') as target:
155
200
        os.fchown(target.fileno(), uid, gid)
156
201
        os.fchmod(target.fileno(), perms)
157
202
        target.write(content)
177
222
    cmd_args.extend([device, mountpoint])
178
223
    try:
179
224
        subprocess.check_output(cmd_args)
180
 
    except subprocess.CalledProcessError, e:
 
225
    except subprocess.CalledProcessError as e:
181
226
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
182
227
        return False
183
228
 
191
236
    cmd_args = ['umount', mountpoint]
192
237
    try:
193
238
        subprocess.check_output(cmd_args)
194
 
    except subprocess.CalledProcessError, e:
 
239
    except subprocess.CalledProcessError as e:
195
240
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
196
241
        return False
197
242
 
209
254
    return system_mounts
210
255
 
211
256
 
212
 
def file_hash(path):
213
 
    """Generate a md5 hash of the contents of 'path' or None if not found """
 
257
def file_hash(path, hash_type='md5'):
 
258
    """
 
259
    Generate a hash checksum of the contents of 'path' or None if not found.
 
260
 
 
261
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
 
262
                          such as md5, sha1, sha256, sha512, etc.
 
263
    """
214
264
    if os.path.exists(path):
215
 
        h = hashlib.md5()
216
 
        with open(path, 'r') as source:
217
 
            h.update(source.read())  # IGNORE:E1101 - it does have update
 
265
        h = getattr(hashlib, hash_type)()
 
266
        with open(path, 'rb') as source:
 
267
            h.update(source.read())
218
268
        return h.hexdigest()
219
269
    else:
220
270
        return None
221
271
 
222
272
 
 
273
def path_hash(path):
 
274
    """
 
275
    Generate a hash checksum of all files matching 'path'. Standard wildcards
 
276
    like '*' and '?' are supported, see documentation for the 'glob' module for
 
277
    more information.
 
278
 
 
279
    :return: dict: A { filename: hash } dictionary for all matched files.
 
280
                   Empty if none found.
 
281
    """
 
282
    return {
 
283
        filename: file_hash(filename)
 
284
        for filename in glob.iglob(path)
 
285
    }
 
286
 
 
287
 
 
288
def check_hash(path, checksum, hash_type='md5'):
 
289
    """
 
290
    Validate a file using a cryptographic checksum.
 
291
 
 
292
    :param str checksum: Value of the checksum used to validate the file.
 
293
    :param str hash_type: Hash algorithm used to generate `checksum`.
 
294
        Can be any hash alrgorithm supported by :mod:`hashlib`,
 
295
        such as md5, sha1, sha256, sha512, etc.
 
296
    :raises ChecksumError: If the file fails the checksum
 
297
 
 
298
    """
 
299
    actual_checksum = file_hash(path, hash_type)
 
300
    if checksum != actual_checksum:
 
301
        raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
 
302
 
 
303
 
 
304
class ChecksumError(ValueError):
 
305
    pass
 
306
 
 
307
 
223
308
def restart_on_change(restart_map, stopstart=False):
224
309
    """Restart services based on configuration files changing
225
310
 
227
312
 
228
313
        @restart_on_change({
229
314
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
315
            '/etc/apache/sites-enabled/*': [ 'apache2' ]
230
316
            })
231
 
        def ceph_client_changed():
 
317
        def config_changed():
232
318
            pass  # your code here
233
319
 
234
320
    In this example, the cinder-api and cinder-volume services
235
321
    would be restarted if /etc/ceph/ceph.conf is changed by the
236
 
    ceph_client_changed function.
 
322
    ceph_client_changed function. The apache2 service would be
 
323
    restarted if any file matching the pattern got changed, created
 
324
    or removed. Standard wildcards are supported, see documentation
 
325
    for the 'glob' module for more information.
237
326
    """
238
327
    def wrap(f):
239
 
        def wrapped_f(*args):
240
 
            checksums = {}
241
 
            for path in restart_map:
242
 
                checksums[path] = file_hash(path)
243
 
            f(*args)
 
328
        def wrapped_f(*args, **kwargs):
 
329
            checksums = {path: path_hash(path) for path in restart_map}
 
330
            f(*args, **kwargs)
244
331
            restarts = []
245
332
            for path in restart_map:
246
 
                if checksums[path] != file_hash(path):
 
333
                if path_hash(path) != checksums[path]:
247
334
                    restarts += restart_map[path]
248
335
            services_list = list(OrderedDict.fromkeys(restarts))
249
336
            if not stopstart:
270
357
def pwgen(length=None):
271
358
    """Generate a random pasword."""
272
359
    if length is None:
 
360
        # A random length is ok to use a weak PRNG
273
361
        length = random.choice(range(35, 45))
274
362
    alphanumeric_chars = [
275
 
        l for l in (string.letters + string.digits)
 
363
        l for l in (string.ascii_letters + string.digits)
276
364
        if l not in 'l0QD1vAEIOUaeiou']
 
365
    # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
 
366
    # actual password
 
367
    random_generator = random.SystemRandom()
277
368
    random_chars = [
278
 
        random.choice(alphanumeric_chars) for _ in range(length)]
 
369
        random_generator.choice(alphanumeric_chars) for _ in range(length)]
279
370
    return(''.join(random_chars))
280
371
 
281
372
 
282
373
def list_nics(nic_type):
283
374
    '''Return a list of nics of given type(s)'''
284
 
    if isinstance(nic_type, basestring):
 
375
    if isinstance(nic_type, six.string_types):
285
376
        int_types = [nic_type]
286
377
    else:
287
378
        int_types = nic_type
288
379
    interfaces = []
289
380
    for int_type in int_types:
290
381
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
291
 
        ip_output = subprocess.check_output(cmd).split('\n')
 
382
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
292
383
        ip_output = (line for line in ip_output if line)
293
384
        for line in ip_output:
294
385
            if line.split()[1].startswith(int_type):
295
 
                interfaces.append(line.split()[1].replace(":", ""))
 
386
                matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
 
387
                if matched:
 
388
                    interface = matched.groups()[0]
 
389
                else:
 
390
                    interface = line.split()[1].replace(":", "")
 
391
                interfaces.append(interface)
 
392
 
296
393
    return interfaces
297
394
 
298
395
 
304
401
 
305
402
def get_nic_mtu(nic):
306
403
    cmd = ['ip', 'addr', 'show', nic]
307
 
    ip_output = subprocess.check_output(cmd).split('\n')
 
404
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
308
405
    mtu = ""
309
406
    for line in ip_output:
310
407
        words = line.split()
315
412
 
316
413
def get_nic_hwaddr(nic):
317
414
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
318
 
    ip_output = subprocess.check_output(cmd)
 
415
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
319
416
    hwaddr = ""
320
417
    words = ip_output.split()
321
418
    if 'link/ether' in words:
330
427
    *  0 => Installed revno is the same as supplied arg
331
428
    * -1 => Installed revno is less than supplied arg
332
429
 
 
430
    This function imports apt_cache function from charmhelpers.fetch if
 
431
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
 
432
    you call this function, or pass an apt_pkg.Cache() instance.
333
433
    '''
334
434
    import apt_pkg
335
 
    from charmhelpers.fetch import apt_cache
336
435
    if not pkgcache:
 
436
        from charmhelpers.fetch import apt_cache
337
437
        pkgcache = apt_cache()
338
438
    pkg = pkgcache[package]
339
439
    return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
348
448
        os.chdir(cur)
349
449
 
350
450
 
351
 
def chownr(path, owner, group):
 
451
def chownr(path, owner, group, follow_links=True):
352
452
    uid = pwd.getpwnam(owner).pw_uid
353
453
    gid = grp.getgrnam(group).gr_gid
 
454
    if follow_links:
 
455
        chown = os.chown
 
456
    else:
 
457
        chown = os.lchown
354
458
 
355
459
    for root, dirs, files in os.walk(path):
356
460
        for name in dirs + files:
357
461
            full = os.path.join(root, name)
358
462
            broken_symlink = os.path.lexists(full) and not os.path.exists(full)
359
463
            if not broken_symlink:
360
 
                os.chown(full, uid, gid)
 
464
                chown(full, uid, gid)
 
465
 
 
466
 
 
467
def lchownr(path, owner, group):
 
468
    chownr(path, owner, group, follow_links=False)