~stub/charms/trusty/nrpe/chsync

« back to all changes in this revision

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

  • Committer: Stuart Bishop
  • Date: 2016-07-08 14:49:27 UTC
  • Revision ID: stuart.bishop@canonical.com-20160708144927-jl3oh7s819h77nmz
Resync charmhelpers

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright 2014-2015 Canonical Limited.
2
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/>.
 
3
# Licensed under the Apache License, Version 2.0 (the "License");
 
4
# you may not use this file except in compliance with the License.
 
5
# You may obtain a copy of the License at
 
6
#
 
7
#  http://www.apache.org/licenses/LICENSE-2.0
 
8
#
 
9
# Unless required by applicable law or agreed to in writing, software
 
10
# distributed under the License is distributed on an "AS IS" BASIS,
 
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
# See the License for the specific language governing permissions and
 
13
# limitations under the License.
16
14
 
17
15
"""Tools for working with the host system"""
18
16
# Copyright 2012 Canonical Ltd.
24
22
import os
25
23
import re
26
24
import pwd
 
25
import glob
27
26
import grp
28
27
import random
29
28
import string
30
29
import subprocess
31
30
import hashlib
 
31
import functools
 
32
import itertools
32
33
from contextlib import contextmanager
33
34
from collections import OrderedDict
34
35
 
62
63
    return service_result
63
64
 
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
 
65
120
def service(action, service_name):
66
121
    """Control a system service"""
67
 
    cmd = ['service', service_name, action]
 
122
    if init_is_systemd():
 
123
        cmd = ['systemctl', action, service_name]
 
124
    else:
 
125
        cmd = ['service', service_name, action]
68
126
    return subprocess.call(cmd) == 0
69
127
 
70
128
 
71
 
def service_running(service):
 
129
_UPSTART_CONF = "/etc/init/{}.conf"
 
130
_INIT_D_CONF = "/etc/init.d/{}"
 
131
 
 
132
 
 
133
def service_running(service_name):
72
134
    """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:
 
135
    if init_is_systemd():
 
136
        return service('is-active', service_name)
 
137
    else:
 
138
        if os.path.exists(_UPSTART_CONF.format(service_name)):
 
139
            try:
 
140
                output = subprocess.check_output(
 
141
                    ['status', service_name],
 
142
                    stderr=subprocess.STDOUT).decode('UTF-8')
 
143
            except subprocess.CalledProcessError:
 
144
                return False
 
145
            else:
 
146
                # This works for upstart scripts where the 'service' command
 
147
                # returns a consistent string to represent running 'start/running'
 
148
                if "start/running" in output:
 
149
                    return True
 
150
        elif os.path.exists(_INIT_D_CONF.format(service_name)):
 
151
            # Check System V scripts init script return codes
 
152
            return service('status', service_name)
78
153
        return False
79
 
    else:
80
 
        if ("start/running" in output or "is running" in output):
81
 
            return True
82
 
        else:
83
 
            return False
84
154
 
85
155
 
86
156
def service_available(service_name):
90
160
            ['service', service_name, 'status'],
91
161
            stderr=subprocess.STDOUT).decode('UTF-8')
92
162
    except subprocess.CalledProcessError as e:
93
 
        return 'unrecognized service' not in e.output
 
163
        return b'unrecognized service' not in e.output
94
164
    else:
95
165
        return True
96
166
 
97
167
 
98
 
def adduser(username, password=None, shell='/bin/bash', system_user=False):
99
 
    """Add a user to the system"""
 
168
SYSTEMD_SYSTEM = '/run/systemd/system'
 
169
 
 
170
 
 
171
def init_is_systemd():
 
172
    """Return True if the host system uses systemd, False otherwise."""
 
173
    return os.path.isdir(SYSTEMD_SYSTEM)
 
174
 
 
175
 
 
176
def adduser(username, password=None, shell='/bin/bash', system_user=False,
 
177
            primary_group=None, secondary_groups=None, uid=None, home_dir=None):
 
178
    """Add a user to the system.
 
179
 
 
180
    Will log but otherwise succeed if the user already exists.
 
181
 
 
182
    :param str username: Username to create
 
183
    :param str password: Password for user; if ``None``, create a system user
 
184
    :param str shell: The default shell for the user
 
185
    :param bool system_user: Whether to create a login or system user
 
186
    :param str primary_group: Primary group for user; defaults to username
 
187
    :param list secondary_groups: Optional list of additional groups
 
188
    :param int uid: UID for user being created
 
189
    :param str home_dir: Home directory for user
 
190
 
 
191
    :returns: The password database entry struct, as returned by `pwd.getpwnam`
 
192
    """
100
193
    try:
101
194
        user_info = pwd.getpwnam(username)
102
195
        log('user {0} already exists!'.format(username))
 
196
        if uid:
 
197
            user_info = pwd.getpwuid(int(uid))
 
198
            log('user with uid {0} already exists!'.format(uid))
103
199
    except KeyError:
104
200
        log('creating user {0}'.format(username))
105
201
        cmd = ['useradd']
 
202
        if uid:
 
203
            cmd.extend(['--uid', str(uid)])
 
204
        if home_dir:
 
205
            cmd.extend(['--home', str(home_dir)])
106
206
        if system_user or password is None:
107
207
            cmd.append('--system')
108
208
        else:
111
211
                '--shell', shell,
112
212
                '--password', password,
113
213
            ])
 
214
        if not primary_group:
 
215
            try:
 
216
                grp.getgrnam(username)
 
217
                primary_group = username  # avoid "group exists" error
 
218
            except KeyError:
 
219
                pass
 
220
        if primary_group:
 
221
            cmd.extend(['-g', primary_group])
 
222
        if secondary_groups:
 
223
            cmd.extend(['-G', ','.join(secondary_groups)])
114
224
        cmd.append(username)
115
225
        subprocess.check_call(cmd)
116
226
        user_info = pwd.getpwnam(username)
117
227
    return user_info
118
228
 
119
229
 
120
 
def add_group(group_name, system_group=False):
121
 
    """Add a group to the system"""
 
230
def user_exists(username):
 
231
    """Check if a user exists"""
 
232
    try:
 
233
        pwd.getpwnam(username)
 
234
        user_exists = True
 
235
    except KeyError:
 
236
        user_exists = False
 
237
    return user_exists
 
238
 
 
239
 
 
240
def uid_exists(uid):
 
241
    """Check if a uid exists"""
 
242
    try:
 
243
        pwd.getpwuid(uid)
 
244
        uid_exists = True
 
245
    except KeyError:
 
246
        uid_exists = False
 
247
    return uid_exists
 
248
 
 
249
 
 
250
def group_exists(groupname):
 
251
    """Check if a group exists"""
 
252
    try:
 
253
        grp.getgrnam(groupname)
 
254
        group_exists = True
 
255
    except KeyError:
 
256
        group_exists = False
 
257
    return group_exists
 
258
 
 
259
 
 
260
def gid_exists(gid):
 
261
    """Check if a gid exists"""
 
262
    try:
 
263
        grp.getgrgid(gid)
 
264
        gid_exists = True
 
265
    except KeyError:
 
266
        gid_exists = False
 
267
    return gid_exists
 
268
 
 
269
 
 
270
def add_group(group_name, system_group=False, gid=None):
 
271
    """Add a group to the system
 
272
 
 
273
    Will log but otherwise succeed if the group already exists.
 
274
 
 
275
    :param str group_name: group to create
 
276
    :param bool system_group: Create system group
 
277
    :param int gid: GID for user being created
 
278
 
 
279
    :returns: The password database entry struct, as returned by `grp.getgrnam`
 
280
    """
122
281
    try:
123
282
        group_info = grp.getgrnam(group_name)
124
283
        log('group {0} already exists!'.format(group_name))
 
284
        if gid:
 
285
            group_info = grp.getgrgid(gid)
 
286
            log('group with gid {0} already exists!'.format(gid))
125
287
    except KeyError:
126
288
        log('creating group {0}'.format(group_name))
127
289
        cmd = ['addgroup']
 
290
        if gid:
 
291
            cmd.extend(['--gid', str(gid)])
128
292
        if system_group:
129
293
            cmd.append('--system')
130
294
        else:
139
303
 
140
304
def add_user_to_group(username, group):
141
305
    """Add a user to a group"""
142
 
    cmd = [
143
 
        'gpasswd', '-a',
144
 
        username,
145
 
        group
146
 
    ]
 
306
    cmd = ['gpasswd', '-a', username, group]
147
307
    log("Adding user {} to group {}".format(username, group))
148
308
    subprocess.check_call(cmd)
149
309
 
202
362
 
203
363
 
204
364
def fstab_remove(mp):
205
 
    """Remove the given mountpoint entry from /etc/fstab
206
 
    """
 
365
    """Remove the given mountpoint entry from /etc/fstab"""
207
366
    return Fstab.remove_by_mountpoint(mp)
208
367
 
209
368
 
210
369
def fstab_add(dev, mp, fs, options=None):
211
 
    """Adds the given device entry to the /etc/fstab file
212
 
    """
 
370
    """Adds the given device entry to the /etc/fstab file"""
213
371
    return Fstab.add(dev, mp, fs, options=options)
214
372
 
215
373
 
253
411
    return system_mounts
254
412
 
255
413
 
 
414
def fstab_mount(mountpoint):
 
415
    """Mount filesystem using fstab"""
 
416
    cmd_args = ['mount', mountpoint]
 
417
    try:
 
418
        subprocess.check_output(cmd_args)
 
419
    except subprocess.CalledProcessError as e:
 
420
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
421
        return False
 
422
    return True
 
423
 
 
424
 
256
425
def file_hash(path, hash_type='md5'):
257
 
    """
258
 
    Generate a hash checksum of the contents of 'path' or None if not found.
 
426
    """Generate a hash checksum of the contents of 'path' or None if not found.
259
427
 
260
428
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
261
429
                          such as md5, sha1, sha256, sha512, etc.
269
437
        return None
270
438
 
271
439
 
 
440
def path_hash(path):
 
441
    """Generate a hash checksum of all files matching 'path'. Standard
 
442
    wildcards like '*' and '?' are supported, see documentation for the 'glob'
 
443
    module for more information.
 
444
 
 
445
    :return: dict: A { filename: hash } dictionary for all matched files.
 
446
                   Empty if none found.
 
447
    """
 
448
    return {
 
449
        filename: file_hash(filename)
 
450
        for filename in glob.iglob(path)
 
451
    }
 
452
 
 
453
 
272
454
def check_hash(path, checksum, hash_type='md5'):
273
 
    """
274
 
    Validate a file using a cryptographic checksum.
 
455
    """Validate a file using a cryptographic checksum.
275
456
 
276
457
    :param str checksum: Value of the checksum used to validate the file.
277
458
    :param str hash_type: Hash algorithm used to generate `checksum`.
286
467
 
287
468
 
288
469
class ChecksumError(ValueError):
 
470
    """A class derived from Value error to indicate the checksum failed."""
289
471
    pass
290
472
 
291
473
 
292
 
def restart_on_change(restart_map, stopstart=False):
 
474
def restart_on_change(restart_map, stopstart=False, restart_functions=None):
293
475
    """Restart services based on configuration files changing
294
476
 
295
477
    This function is used a decorator, for example::
296
478
 
297
479
        @restart_on_change({
298
480
            '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
 
481
            '/etc/apache/sites-enabled/*': [ 'apache2' ]
299
482
            })
300
 
        def ceph_client_changed():
 
483
        def config_changed():
301
484
            pass  # your code here
302
485
 
303
486
    In this example, the cinder-api and cinder-volume services
304
487
    would be restarted if /etc/ceph/ceph.conf is changed by the
305
 
    ceph_client_changed function.
 
488
    ceph_client_changed function. The apache2 service would be
 
489
    restarted if any file matching the pattern got changed, created
 
490
    or removed. Standard wildcards are supported, see documentation
 
491
    for the 'glob' module for more information.
 
492
 
 
493
    @param restart_map: {path_file_name: [service_name, ...]
 
494
    @param stopstart: DEFAULT false; whether to stop, start OR restart
 
495
    @param restart_functions: nonstandard functions to use to restart services
 
496
                              {svc: func, ...}
 
497
    @returns result from decorated function
306
498
    """
307
499
    def wrap(f):
 
500
        @functools.wraps(f)
308
501
        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)
 
502
            return restart_on_change_helper(
 
503
                (lambda: f(*args, **kwargs)), restart_map, stopstart,
 
504
                restart_functions)
325
505
        return wrapped_f
326
506
    return wrap
327
507
 
328
508
 
 
509
def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
 
510
                             restart_functions=None):
 
511
    """Helper function to perform the restart_on_change function.
 
512
 
 
513
    This is provided for decorators to restart services if files described
 
514
    in the restart_map have changed after an invocation of lambda_f().
 
515
 
 
516
    @param lambda_f: function to call.
 
517
    @param restart_map: {file: [service, ...]}
 
518
    @param stopstart: whether to stop, start or restart a service
 
519
    @param restart_functions: nonstandard functions to use to restart services
 
520
                              {svc: func, ...}
 
521
    @returns result of lambda_f()
 
522
    """
 
523
    if restart_functions is None:
 
524
        restart_functions = {}
 
525
    checksums = {path: path_hash(path) for path in restart_map}
 
526
    r = lambda_f()
 
527
    # create a list of lists of the services to restart
 
528
    restarts = [restart_map[path]
 
529
                for path in restart_map
 
530
                if path_hash(path) != checksums[path]]
 
531
    # create a flat list of ordered services without duplicates from lists
 
532
    services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
 
533
    if services_list:
 
534
        actions = ('stop', 'start') if stopstart else ('restart',)
 
535
        for service_name in services_list:
 
536
            if service_name in restart_functions:
 
537
                restart_functions[service_name](service_name)
 
538
            else:
 
539
                for action in actions:
 
540
                    service(action, service_name)
 
541
    return r
 
542
 
 
543
 
329
544
def lsb_release():
330
545
    """Return /etc/lsb-release in a dict"""
331
546
    d = {}
352
567
    return(''.join(random_chars))
353
568
 
354
569
 
355
 
def list_nics(nic_type):
356
 
    '''Return a list of nics of given type(s)'''
 
570
def is_phy_iface(interface):
 
571
    """Returns True if interface is not virtual, otherwise False."""
 
572
    if interface:
 
573
        sys_net = '/sys/class/net'
 
574
        if os.path.isdir(sys_net):
 
575
            for iface in glob.glob(os.path.join(sys_net, '*')):
 
576
                if '/virtual/' in os.path.realpath(iface):
 
577
                    continue
 
578
 
 
579
                if interface == os.path.basename(iface):
 
580
                    return True
 
581
 
 
582
    return False
 
583
 
 
584
 
 
585
def get_bond_master(interface):
 
586
    """Returns bond master if interface is bond slave otherwise None.
 
587
 
 
588
    NOTE: the provided interface is expected to be physical
 
589
    """
 
590
    if interface:
 
591
        iface_path = '/sys/class/net/%s' % (interface)
 
592
        if os.path.exists(iface_path):
 
593
            if '/virtual/' in os.path.realpath(iface_path):
 
594
                return None
 
595
 
 
596
            master = os.path.join(iface_path, 'master')
 
597
            if os.path.exists(master):
 
598
                master = os.path.realpath(master)
 
599
                # make sure it is a bond master
 
600
                if os.path.exists(os.path.join(master, 'bonding')):
 
601
                    return os.path.basename(master)
 
602
 
 
603
    return None
 
604
 
 
605
 
 
606
def list_nics(nic_type=None):
 
607
    """Return a list of nics of given type(s)"""
357
608
    if isinstance(nic_type, six.string_types):
358
609
        int_types = [nic_type]
359
610
    else:
360
611
        int_types = nic_type
 
612
 
361
613
    interfaces = []
362
 
    for int_type in int_types:
363
 
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
614
    if nic_type:
 
615
        for int_type in int_types:
 
616
            cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
617
            ip_output = subprocess.check_output(cmd).decode('UTF-8')
 
618
            ip_output = ip_output.split('\n')
 
619
            ip_output = (line for line in ip_output if line)
 
620
            for line in ip_output:
 
621
                if line.split()[1].startswith(int_type):
 
622
                    matched = re.search('.*: (' + int_type +
 
623
                                        r'[0-9]+\.[0-9]+)@.*', line)
 
624
                    if matched:
 
625
                        iface = matched.groups()[0]
 
626
                    else:
 
627
                        iface = line.split()[1].replace(":", "")
 
628
 
 
629
                    if iface not in interfaces:
 
630
                        interfaces.append(iface)
 
631
    else:
 
632
        cmd = ['ip', 'a']
364
633
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
365
 
        ip_output = (line for line in ip_output if line)
 
634
        ip_output = (line.strip() for line in ip_output if line)
 
635
 
 
636
        key = re.compile('^[0-9]+:\s+(.+):')
366
637
        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)
 
638
            matched = re.search(key, line)
 
639
            if matched:
 
640
                iface = matched.group(1)
 
641
                iface = iface.partition("@")[0]
 
642
                if iface not in interfaces:
 
643
                    interfaces.append(iface)
374
644
 
375
645
    return interfaces
376
646
 
377
647
 
378
648
def set_nic_mtu(nic, mtu):
379
 
    '''Set MTU on a network interface'''
 
649
    """Set the Maximum Transmission Unit (MTU) on a network interface."""
380
650
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
381
651
    subprocess.check_call(cmd)
382
652
 
383
653
 
384
654
def get_nic_mtu(nic):
 
655
    """Return the Maximum Transmission Unit (MTU) for a network interface."""
385
656
    cmd = ['ip', 'addr', 'show', nic]
386
657
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
387
658
    mtu = ""
393
664
 
394
665
 
395
666
def get_nic_hwaddr(nic):
 
667
    """Return the Media Access Control (MAC) for a network interface."""
396
668
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
397
669
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
398
670
    hwaddr = ""
403
675
 
404
676
 
405
677
def cmp_pkgrevno(package, revno, pkgcache=None):
406
 
    '''Compare supplied revno with the revno of the installed package
 
678
    """Compare supplied revno with the revno of the installed package
407
679
 
408
680
    *  1 => Installed revno is greater than supplied arg
409
681
    *  0 => Installed revno is the same as supplied arg
412
684
    This function imports apt_cache function from charmhelpers.fetch if
413
685
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
414
686
    you call this function, or pass an apt_pkg.Cache() instance.
415
 
    '''
 
687
    """
416
688
    import apt_pkg
417
689
    if not pkgcache:
418
690
        from charmhelpers.fetch import apt_cache
422
694
 
423
695
 
424
696
@contextmanager
425
 
def chdir(d):
 
697
def chdir(directory):
 
698
    """Change the current working directory to a different directory for a code
 
699
    block and return the previous directory after the block exits. Useful to
 
700
    run commands from a specificed directory.
 
701
 
 
702
    :param str directory: The directory path to change to for this context.
 
703
    """
426
704
    cur = os.getcwd()
427
705
    try:
428
 
        yield os.chdir(d)
 
706
        yield os.chdir(directory)
429
707
    finally:
430
708
        os.chdir(cur)
431
709
 
432
710
 
433
 
def chownr(path, owner, group, follow_links=True):
 
711
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
 
712
    """Recursively change user and group ownership of files and directories
 
713
    in given path. Doesn't chown path itself by default, only its children.
 
714
 
 
715
    :param str path: The string path to start changing ownership.
 
716
    :param str owner: The owner string to use when looking up the uid.
 
717
    :param str group: The group string to use when looking up the gid.
 
718
    :param bool follow_links: Also Chown links if True
 
719
    :param bool chowntopdir: Also chown path itself if True
 
720
    """
434
721
    uid = pwd.getpwnam(owner).pw_uid
435
722
    gid = grp.getgrnam(group).gr_gid
436
723
    if follow_links:
438
725
    else:
439
726
        chown = os.lchown
440
727
 
 
728
    if chowntopdir:
 
729
        broken_symlink = os.path.lexists(path) and not os.path.exists(path)
 
730
        if not broken_symlink:
 
731
            chown(path, uid, gid)
441
732
    for root, dirs, files in os.walk(path):
442
733
        for name in dirs + files:
443
734
            full = os.path.join(root, name)
447
738
 
448
739
 
449
740
def lchownr(path, owner, group):
 
741
    """Recursively change user and group ownership of files and directories
 
742
    in a given path, not following symbolic links. See the documentation for
 
743
    'os.lchown' for more information.
 
744
 
 
745
    :param str path: The string path to start changing ownership.
 
746
    :param str owner: The owner string to use when looking up the uid.
 
747
    :param str group: The group string to use when looking up the gid.
 
748
    """
450
749
    chownr(path, owner, group, follow_links=False)
 
750
 
 
751
 
 
752
def get_total_ram():
 
753
    """The total amount of system RAM in bytes.
 
754
 
 
755
    This is what is reported by the OS, and may be overcommitted when
 
756
    there are multiple containers hosted on the same machine.
 
757
    """
 
758
    with open('/proc/meminfo', 'r') as f:
 
759
        for line in f.readlines():
 
760
            if line:
 
761
                key, value, unit = line.split()
 
762
                if key == 'MemTotal:':
 
763
                    assert unit == 'kB', 'Unknown unit'
 
764
                    return int(value) * 1024  # Classic, not KiB.
 
765
        raise NotImplementedError()