~james-page/charms/trusty/swift-proxy/trunk

« back to all changes in this revision

Viewing changes to charmhelpers/core/host.py

  • Committer: James Page
  • Date: 2016-01-19 14:46:01 UTC
  • mfrom: (134.1.1 stable.remote)
  • Revision ID: james.page@ubuntu.com-20160119144601-66bdh4r0va0pn9og
Fix liberty/mitaka typo from previous test definition update batch.

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 glob
28
 
import grp
29
 
import random
30
 
import string
31
 
import subprocess
32
 
import hashlib
33
 
from contextlib import contextmanager
34
 
from collections import OrderedDict
35
 
 
36
 
import six
37
 
 
38
 
from .hookenv import log
39
 
from .fstab import Fstab
40
 
 
41
 
 
42
 
def service_start(service_name):
43
 
    """Start a system service"""
44
 
    return service('start', service_name)
45
 
 
46
 
 
47
 
def service_stop(service_name):
48
 
    """Stop a system service"""
49
 
    return service('stop', service_name)
50
 
 
51
 
 
52
 
def service_restart(service_name):
53
 
    """Restart a system service"""
54
 
    return service('restart', service_name)
55
 
 
56
 
 
57
 
def service_reload(service_name, restart_on_failure=False):
58
 
    """Reload a system service, optionally falling back to restart if
59
 
    reload fails"""
60
 
    service_result = service('reload', service_name)
61
 
    if not service_result and restart_on_failure:
62
 
        service_result = service('restart', service_name)
63
 
    return service_result
64
 
 
65
 
 
66
 
def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
67
 
    """Pause a system service.
68
 
 
69
 
    Stop it, and prevent it from starting again at boot."""
70
 
    stopped = True
71
 
    if service_running(service_name):
72
 
        stopped = service_stop(service_name)
73
 
    upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
74
 
    sysv_file = os.path.join(initd_dir, service_name)
75
 
    if init_is_systemd():
76
 
        service('disable', service_name)
77
 
    elif os.path.exists(upstart_file):
78
 
        override_path = os.path.join(
79
 
            init_dir, '{}.override'.format(service_name))
80
 
        with open(override_path, 'w') as fh:
81
 
            fh.write("manual\n")
82
 
    elif os.path.exists(sysv_file):
83
 
        subprocess.check_call(["update-rc.d", service_name, "disable"])
84
 
    else:
85
 
        raise ValueError(
86
 
            "Unable to detect {0} as SystemD, Upstart {1} or"
87
 
            " SysV {2}".format(
88
 
                service_name, upstart_file, sysv_file))
89
 
    return stopped
90
 
 
91
 
 
92
 
def service_resume(service_name, init_dir="/etc/init",
93
 
                   initd_dir="/etc/init.d"):
94
 
    """Resume a system service.
95
 
 
96
 
    Reenable starting again at boot. Start the service"""
97
 
    upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
98
 
    sysv_file = os.path.join(initd_dir, service_name)
99
 
    if init_is_systemd():
100
 
        service('enable', service_name)
101
 
    elif os.path.exists(upstart_file):
102
 
        override_path = os.path.join(
103
 
            init_dir, '{}.override'.format(service_name))
104
 
        if os.path.exists(override_path):
105
 
            os.unlink(override_path)
106
 
    elif os.path.exists(sysv_file):
107
 
        subprocess.check_call(["update-rc.d", service_name, "enable"])
108
 
    else:
109
 
        raise ValueError(
110
 
            "Unable to detect {0} as SystemD, Upstart {1} or"
111
 
            " SysV {2}".format(
112
 
                service_name, upstart_file, sysv_file))
113
 
 
114
 
    started = service_running(service_name)
115
 
    if not started:
116
 
        started = service_start(service_name)
117
 
    return started
118
 
 
119
 
 
120
 
def service(action, service_name):
121
 
    """Control a system service"""
122
 
    if init_is_systemd():
123
 
        cmd = ['systemctl', action, service_name]
124
 
    else:
125
 
        cmd = ['service', service_name, action]
126
 
    return subprocess.call(cmd) == 0
127
 
 
128
 
 
129
 
def service_running(service_name):
130
 
    """Determine whether a system service is running"""
131
 
    if init_is_systemd():
132
 
        return service('is-active', service_name)
133
 
    else:
134
 
        try:
135
 
            output = subprocess.check_output(
136
 
                ['service', service_name, 'status'],
137
 
                stderr=subprocess.STDOUT).decode('UTF-8')
138
 
        except subprocess.CalledProcessError:
139
 
            return False
140
 
        else:
141
 
            if ("start/running" in output or "is running" in output):
142
 
                return True
143
 
            else:
144
 
                return False
145
 
 
146
 
 
147
 
def service_available(service_name):
148
 
    """Determine whether a system service is available"""
149
 
    try:
150
 
        subprocess.check_output(
151
 
            ['service', service_name, 'status'],
152
 
            stderr=subprocess.STDOUT).decode('UTF-8')
153
 
    except subprocess.CalledProcessError as e:
154
 
        return b'unrecognized service' not in e.output
155
 
    else:
156
 
        return True
157
 
 
158
 
 
159
 
SYSTEMD_SYSTEM = '/run/systemd/system'
160
 
 
161
 
 
162
 
def init_is_systemd():
163
 
    return os.path.isdir(SYSTEMD_SYSTEM)
164
 
 
165
 
 
166
 
def adduser(username, password=None, shell='/bin/bash', system_user=False,
167
 
            primary_group=None, secondary_groups=None):
168
 
    """
169
 
    Add a user to the system.
170
 
 
171
 
    Will log but otherwise succeed if the user already exists.
172
 
 
173
 
    :param str username: Username to create
174
 
    :param str password: Password for user; if ``None``, create a system user
175
 
    :param str shell: The default shell for the user
176
 
    :param bool system_user: Whether to create a login or system user
177
 
    :param str primary_group: Primary group for user; defaults to their username
178
 
    :param list secondary_groups: Optional list of additional groups
179
 
 
180
 
    :returns: The password database entry struct, as returned by `pwd.getpwnam`
181
 
    """
182
 
    try:
183
 
        user_info = pwd.getpwnam(username)
184
 
        log('user {0} already exists!'.format(username))
185
 
    except KeyError:
186
 
        log('creating user {0}'.format(username))
187
 
        cmd = ['useradd']
188
 
        if system_user or password is None:
189
 
            cmd.append('--system')
190
 
        else:
191
 
            cmd.extend([
192
 
                '--create-home',
193
 
                '--shell', shell,
194
 
                '--password', password,
195
 
            ])
196
 
        if not primary_group:
197
 
            try:
198
 
                grp.getgrnam(username)
199
 
                primary_group = username  # avoid "group exists" error
200
 
            except KeyError:
201
 
                pass
202
 
        if primary_group:
203
 
            cmd.extend(['-g', primary_group])
204
 
        if secondary_groups:
205
 
            cmd.extend(['-G', ','.join(secondary_groups)])
206
 
        cmd.append(username)
207
 
        subprocess.check_call(cmd)
208
 
        user_info = pwd.getpwnam(username)
209
 
    return user_info
210
 
 
211
 
 
212
 
def user_exists(username):
213
 
    """Check if a user exists"""
214
 
    try:
215
 
        pwd.getpwnam(username)
216
 
        user_exists = True
217
 
    except KeyError:
218
 
        user_exists = False
219
 
    return user_exists
220
 
 
221
 
 
222
 
def add_group(group_name, system_group=False):
223
 
    """Add a group to the system"""
224
 
    try:
225
 
        group_info = grp.getgrnam(group_name)
226
 
        log('group {0} already exists!'.format(group_name))
227
 
    except KeyError:
228
 
        log('creating group {0}'.format(group_name))
229
 
        cmd = ['addgroup']
230
 
        if system_group:
231
 
            cmd.append('--system')
232
 
        else:
233
 
            cmd.extend([
234
 
                '--group',
235
 
            ])
236
 
        cmd.append(group_name)
237
 
        subprocess.check_call(cmd)
238
 
        group_info = grp.getgrnam(group_name)
239
 
    return group_info
240
 
 
241
 
 
242
 
def add_user_to_group(username, group):
243
 
    """Add a user to a group"""
244
 
    cmd = ['gpasswd', '-a', username, group]
245
 
    log("Adding user {} to group {}".format(username, group))
246
 
    subprocess.check_call(cmd)
247
 
 
248
 
 
249
 
def rsync(from_path, to_path, flags='-r', options=None):
250
 
    """Replicate the contents of a path"""
251
 
    options = options or ['--delete', '--executability']
252
 
    cmd = ['/usr/bin/rsync', flags]
253
 
    cmd.extend(options)
254
 
    cmd.append(from_path)
255
 
    cmd.append(to_path)
256
 
    log(" ".join(cmd))
257
 
    return subprocess.check_output(cmd).decode('UTF-8').strip()
258
 
 
259
 
 
260
 
def symlink(source, destination):
261
 
    """Create a symbolic link"""
262
 
    log("Symlinking {} as {}".format(source, destination))
263
 
    cmd = [
264
 
        'ln',
265
 
        '-sf',
266
 
        source,
267
 
        destination,
268
 
    ]
269
 
    subprocess.check_call(cmd)
270
 
 
271
 
 
272
 
def mkdir(path, owner='root', group='root', perms=0o555, force=False):
273
 
    """Create a directory"""
274
 
    log("Making dir {} {}:{} {:o}".format(path, owner, group,
275
 
                                          perms))
276
 
    uid = pwd.getpwnam(owner).pw_uid
277
 
    gid = grp.getgrnam(group).gr_gid
278
 
    realpath = os.path.abspath(path)
279
 
    path_exists = os.path.exists(realpath)
280
 
    if path_exists and force:
281
 
        if not os.path.isdir(realpath):
282
 
            log("Removing non-directory file {} prior to mkdir()".format(path))
283
 
            os.unlink(realpath)
284
 
            os.makedirs(realpath, perms)
285
 
    elif not path_exists:
286
 
        os.makedirs(realpath, perms)
287
 
    os.chown(realpath, uid, gid)
288
 
    os.chmod(realpath, perms)
289
 
 
290
 
 
291
 
def write_file(path, content, owner='root', group='root', perms=0o444):
292
 
    """Create or overwrite a file with the contents of a byte string."""
293
 
    log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
294
 
    uid = pwd.getpwnam(owner).pw_uid
295
 
    gid = grp.getgrnam(group).gr_gid
296
 
    with open(path, 'wb') as target:
297
 
        os.fchown(target.fileno(), uid, gid)
298
 
        os.fchmod(target.fileno(), perms)
299
 
        target.write(content)
300
 
 
301
 
 
302
 
def fstab_remove(mp):
303
 
    """Remove the given mountpoint entry from /etc/fstab
304
 
    """
305
 
    return Fstab.remove_by_mountpoint(mp)
306
 
 
307
 
 
308
 
def fstab_add(dev, mp, fs, options=None):
309
 
    """Adds the given device entry to the /etc/fstab file
310
 
    """
311
 
    return Fstab.add(dev, mp, fs, options=options)
312
 
 
313
 
 
314
 
def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
315
 
    """Mount a filesystem at a particular mountpoint"""
316
 
    cmd_args = ['mount']
317
 
    if options is not None:
318
 
        cmd_args.extend(['-o', options])
319
 
    cmd_args.extend([device, mountpoint])
320
 
    try:
321
 
        subprocess.check_output(cmd_args)
322
 
    except subprocess.CalledProcessError as e:
323
 
        log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
324
 
        return False
325
 
 
326
 
    if persist:
327
 
        return fstab_add(device, mountpoint, filesystem, options=options)
328
 
    return True
329
 
 
330
 
 
331
 
def umount(mountpoint, persist=False):
332
 
    """Unmount a filesystem"""
333
 
    cmd_args = ['umount', mountpoint]
334
 
    try:
335
 
        subprocess.check_output(cmd_args)
336
 
    except subprocess.CalledProcessError as e:
337
 
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
338
 
        return False
339
 
 
340
 
    if persist:
341
 
        return fstab_remove(mountpoint)
342
 
    return True
343
 
 
344
 
 
345
 
def mounts():
346
 
    """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
347
 
    with open('/proc/mounts') as f:
348
 
        # [['/mount/point','/dev/path'],[...]]
349
 
        system_mounts = [m[1::-1] for m in [l.strip().split()
350
 
                                            for l in f.readlines()]]
351
 
    return system_mounts
352
 
 
353
 
 
354
 
def fstab_mount(mountpoint):
355
 
    """Mount filesystem using fstab"""
356
 
    cmd_args = ['mount', mountpoint]
357
 
    try:
358
 
        subprocess.check_output(cmd_args)
359
 
    except subprocess.CalledProcessError as e:
360
 
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
361
 
        return False
362
 
    return True
363
 
 
364
 
 
365
 
def file_hash(path, hash_type='md5'):
366
 
    """
367
 
    Generate a hash checksum of the contents of 'path' or None if not found.
368
 
 
369
 
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
370
 
                          such as md5, sha1, sha256, sha512, etc.
371
 
    """
372
 
    if os.path.exists(path):
373
 
        h = getattr(hashlib, hash_type)()
374
 
        with open(path, 'rb') as source:
375
 
            h.update(source.read())
376
 
        return h.hexdigest()
377
 
    else:
378
 
        return None
379
 
 
380
 
 
381
 
def path_hash(path):
382
 
    """
383
 
    Generate a hash checksum of all files matching 'path'. Standard wildcards
384
 
    like '*' and '?' are supported, see documentation for the 'glob' module for
385
 
    more information.
386
 
 
387
 
    :return: dict: A { filename: hash } dictionary for all matched files.
388
 
                   Empty if none found.
389
 
    """
390
 
    return {
391
 
        filename: file_hash(filename)
392
 
        for filename in glob.iglob(path)
393
 
    }
394
 
 
395
 
 
396
 
def check_hash(path, checksum, hash_type='md5'):
397
 
    """
398
 
    Validate a file using a cryptographic checksum.
399
 
 
400
 
    :param str checksum: Value of the checksum used to validate the file.
401
 
    :param str hash_type: Hash algorithm used to generate `checksum`.
402
 
        Can be any hash alrgorithm supported by :mod:`hashlib`,
403
 
        such as md5, sha1, sha256, sha512, etc.
404
 
    :raises ChecksumError: If the file fails the checksum
405
 
 
406
 
    """
407
 
    actual_checksum = file_hash(path, hash_type)
408
 
    if checksum != actual_checksum:
409
 
        raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
410
 
 
411
 
 
412
 
class ChecksumError(ValueError):
413
 
    pass
414
 
 
415
 
 
416
 
def restart_on_change(restart_map, stopstart=False):
417
 
    """Restart services based on configuration files changing
418
 
 
419
 
    This function is used a decorator, for example::
420
 
 
421
 
        @restart_on_change({
422
 
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
423
 
            '/etc/apache/sites-enabled/*': [ 'apache2' ]
424
 
            })
425
 
        def config_changed():
426
 
            pass  # your code here
427
 
 
428
 
    In this example, the cinder-api and cinder-volume services
429
 
    would be restarted if /etc/ceph/ceph.conf is changed by the
430
 
    ceph_client_changed function. The apache2 service would be
431
 
    restarted if any file matching the pattern got changed, created
432
 
    or removed. Standard wildcards are supported, see documentation
433
 
    for the 'glob' module for more information.
434
 
    """
435
 
    def wrap(f):
436
 
        def wrapped_f(*args, **kwargs):
437
 
            checksums = {path: path_hash(path) for path in restart_map}
438
 
            f(*args, **kwargs)
439
 
            restarts = []
440
 
            for path in restart_map:
441
 
                if path_hash(path) != checksums[path]:
442
 
                    restarts += restart_map[path]
443
 
            services_list = list(OrderedDict.fromkeys(restarts))
444
 
            if not stopstart:
445
 
                for service_name in services_list:
446
 
                    service('restart', service_name)
447
 
            else:
448
 
                for action in ['stop', 'start']:
449
 
                    for service_name in services_list:
450
 
                        service(action, service_name)
451
 
        return wrapped_f
452
 
    return wrap
453
 
 
454
 
 
455
 
def lsb_release():
456
 
    """Return /etc/lsb-release in a dict"""
457
 
    d = {}
458
 
    with open('/etc/lsb-release', 'r') as lsb:
459
 
        for l in lsb:
460
 
            k, v = l.split('=')
461
 
            d[k.strip()] = v.strip()
462
 
    return d
463
 
 
464
 
 
465
 
def pwgen(length=None):
466
 
    """Generate a random pasword."""
467
 
    if length is None:
468
 
        # A random length is ok to use a weak PRNG
469
 
        length = random.choice(range(35, 45))
470
 
    alphanumeric_chars = [
471
 
        l for l in (string.ascii_letters + string.digits)
472
 
        if l not in 'l0QD1vAEIOUaeiou']
473
 
    # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
474
 
    # actual password
475
 
    random_generator = random.SystemRandom()
476
 
    random_chars = [
477
 
        random_generator.choice(alphanumeric_chars) for _ in range(length)]
478
 
    return(''.join(random_chars))
479
 
 
480
 
 
481
 
def is_phy_iface(interface):
482
 
    """Returns True if interface is not virtual, otherwise False."""
483
 
    if interface:
484
 
        sys_net = '/sys/class/net'
485
 
        if os.path.isdir(sys_net):
486
 
            for iface in glob.glob(os.path.join(sys_net, '*')):
487
 
                if '/virtual/' in os.path.realpath(iface):
488
 
                    continue
489
 
 
490
 
                if interface == os.path.basename(iface):
491
 
                    return True
492
 
 
493
 
    return False
494
 
 
495
 
 
496
 
def get_bond_master(interface):
497
 
    """Returns bond master if interface is bond slave otherwise None.
498
 
 
499
 
    NOTE: the provided interface is expected to be physical
500
 
    """
501
 
    if interface:
502
 
        iface_path = '/sys/class/net/%s' % (interface)
503
 
        if os.path.exists(iface_path):
504
 
            if '/virtual/' in os.path.realpath(iface_path):
505
 
                return None
506
 
 
507
 
            master = os.path.join(iface_path, 'master')
508
 
            if os.path.exists(master):
509
 
                master = os.path.realpath(master)
510
 
                # make sure it is a bond master
511
 
                if os.path.exists(os.path.join(master, 'bonding')):
512
 
                    return os.path.basename(master)
513
 
 
514
 
    return None
515
 
 
516
 
 
517
 
def list_nics(nic_type=None):
518
 
    '''Return a list of nics of given type(s)'''
519
 
    if isinstance(nic_type, six.string_types):
520
 
        int_types = [nic_type]
521
 
    else:
522
 
        int_types = nic_type
523
 
 
524
 
    interfaces = []
525
 
    if nic_type:
526
 
        for int_type in int_types:
527
 
            cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
528
 
            ip_output = subprocess.check_output(cmd).decode('UTF-8')
529
 
            ip_output = ip_output.split('\n')
530
 
            ip_output = (line for line in ip_output if line)
531
 
            for line in ip_output:
532
 
                if line.split()[1].startswith(int_type):
533
 
                    matched = re.search('.*: (' + int_type +
534
 
                                        r'[0-9]+\.[0-9]+)@.*', line)
535
 
                    if matched:
536
 
                        iface = matched.groups()[0]
537
 
                    else:
538
 
                        iface = line.split()[1].replace(":", "")
539
 
 
540
 
                    if iface not in interfaces:
541
 
                        interfaces.append(iface)
542
 
    else:
543
 
        cmd = ['ip', 'a']
544
 
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
545
 
        ip_output = (line.strip() for line in ip_output if line)
546
 
 
547
 
        key = re.compile('^[0-9]+:\s+(.+):')
548
 
        for line in ip_output:
549
 
            matched = re.search(key, line)
550
 
            if matched:
551
 
                iface = matched.group(1)
552
 
                iface = iface.partition("@")[0]
553
 
                if iface not in interfaces:
554
 
                    interfaces.append(iface)
555
 
 
556
 
    return interfaces
557
 
 
558
 
 
559
 
def set_nic_mtu(nic, mtu):
560
 
    '''Set MTU on a network interface'''
561
 
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
562
 
    subprocess.check_call(cmd)
563
 
 
564
 
 
565
 
def get_nic_mtu(nic):
566
 
    cmd = ['ip', 'addr', 'show', nic]
567
 
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
568
 
    mtu = ""
569
 
    for line in ip_output:
570
 
        words = line.split()
571
 
        if 'mtu' in words:
572
 
            mtu = words[words.index("mtu") + 1]
573
 
    return mtu
574
 
 
575
 
 
576
 
def get_nic_hwaddr(nic):
577
 
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
578
 
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
579
 
    hwaddr = ""
580
 
    words = ip_output.split()
581
 
    if 'link/ether' in words:
582
 
        hwaddr = words[words.index('link/ether') + 1]
583
 
    return hwaddr
584
 
 
585
 
 
586
 
def cmp_pkgrevno(package, revno, pkgcache=None):
587
 
    '''Compare supplied revno with the revno of the installed package
588
 
 
589
 
    *  1 => Installed revno is greater than supplied arg
590
 
    *  0 => Installed revno is the same as supplied arg
591
 
    * -1 => Installed revno is less than supplied arg
592
 
 
593
 
    This function imports apt_cache function from charmhelpers.fetch if
594
 
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
595
 
    you call this function, or pass an apt_pkg.Cache() instance.
596
 
    '''
597
 
    import apt_pkg
598
 
    if not pkgcache:
599
 
        from charmhelpers.fetch import apt_cache
600
 
        pkgcache = apt_cache()
601
 
    pkg = pkgcache[package]
602
 
    return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
603
 
 
604
 
 
605
 
@contextmanager
606
 
def chdir(d):
607
 
    cur = os.getcwd()
608
 
    try:
609
 
        yield os.chdir(d)
610
 
    finally:
611
 
        os.chdir(cur)
612
 
 
613
 
 
614
 
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
615
 
    """
616
 
    Recursively change user and group ownership of files and directories
617
 
    in given path. Doesn't chown path itself by default, only its children.
618
 
 
619
 
    :param bool follow_links: Also Chown links if True
620
 
    :param bool chowntopdir: Also chown path itself if True
621
 
    """
622
 
    uid = pwd.getpwnam(owner).pw_uid
623
 
    gid = grp.getgrnam(group).gr_gid
624
 
    if follow_links:
625
 
        chown = os.chown
626
 
    else:
627
 
        chown = os.lchown
628
 
 
629
 
    if chowntopdir:
630
 
        broken_symlink = os.path.lexists(path) and not os.path.exists(path)
631
 
        if not broken_symlink:
632
 
            chown(path, uid, gid)
633
 
    for root, dirs, files in os.walk(path):
634
 
        for name in dirs + files:
635
 
            full = os.path.join(root, name)
636
 
            broken_symlink = os.path.lexists(full) and not os.path.exists(full)
637
 
            if not broken_symlink:
638
 
                chown(full, uid, gid)
639
 
 
640
 
 
641
 
def lchownr(path, owner, group):
642
 
    chownr(path, owner, group, follow_links=False)
643
 
 
644
 
 
645
 
def get_total_ram():
646
 
    '''The total amount of system RAM in bytes.
647
 
 
648
 
    This is what is reported by the OS, and may be overcommitted when
649
 
    there are multiple containers hosted on the same machine.
650
 
    '''
651
 
    with open('/proc/meminfo', 'r') as f:
652
 
        for line in f.readlines():
653
 
            if line:
654
 
                key, value, unit = line.split()
655
 
                if key == 'MemTotal:':
656
 
                    assert unit == 'kB', 'Unknown unit'
657
 
                    return int(value) * 1024  # Classic, not KiB.
658
 
        raise NotImplementedError()