~ajkavanagh/charms/trusty/memcached/add-spaces-support

« back to all changes in this revision

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

  • Committer: Jorge Niedbalski
  • Date: 2016-09-20 18:01:20 UTC
  • mfrom: (72.2.3 memcached.py3)
  • Revision ID: jorge.niedbalski@canonical.com-20160920180120-apb4ut3cugwxcrer
[freyes, r=niedbalski] Fixes bug LP: #1576458

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.
30
28
import string
31
29
import subprocess
32
30
import hashlib
 
31
import functools
 
32
import itertools
 
33
import six
 
34
 
33
35
from contextlib import contextmanager
34
36
from collections import OrderedDict
35
 
 
36
 
import six
37
 
 
38
37
from .hookenv import log
39
38
from .fstab import Fstab
 
39
from charmhelpers.osplatform import get_platform
 
40
 
 
41
__platform__ = get_platform()
 
42
if __platform__ == "ubuntu":
 
43
    from charmhelpers.core.host_factory.ubuntu import (
 
44
        service_available,
 
45
        add_new_group,
 
46
        lsb_release,
 
47
        cmp_pkgrevno,
 
48
    )  # flake8: noqa -- ignore F401 for this import
 
49
elif __platform__ == "centos":
 
50
    from charmhelpers.core.host_factory.centos import (
 
51
        service_available,
 
52
        add_new_group,
 
53
        lsb_release,
 
54
        cmp_pkgrevno,
 
55
    )  # flake8: noqa -- ignore F401 for this import
40
56
 
41
57
 
42
58
def service_start(service_name):
63
79
    return service_result
64
80
 
65
81
 
 
82
def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
 
83
    """Pause a system service.
 
84
 
 
85
    Stop it, and prevent it from starting again at boot."""
 
86
    stopped = True
 
87
    if service_running(service_name):
 
88
        stopped = service_stop(service_name)
 
89
    upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
 
90
    sysv_file = os.path.join(initd_dir, service_name)
 
91
    if init_is_systemd():
 
92
        service('disable', service_name)
 
93
    elif os.path.exists(upstart_file):
 
94
        override_path = os.path.join(
 
95
            init_dir, '{}.override'.format(service_name))
 
96
        with open(override_path, 'w') as fh:
 
97
            fh.write("manual\n")
 
98
    elif os.path.exists(sysv_file):
 
99
        subprocess.check_call(["update-rc.d", service_name, "disable"])
 
100
    else:
 
101
        raise ValueError(
 
102
            "Unable to detect {0} as SystemD, Upstart {1} or"
 
103
            " SysV {2}".format(
 
104
                service_name, upstart_file, sysv_file))
 
105
    return stopped
 
106
 
 
107
 
 
108
def service_resume(service_name, init_dir="/etc/init",
 
109
                   initd_dir="/etc/init.d"):
 
110
    """Resume a system service.
 
111
 
 
112
    Reenable starting again at boot. Start the service"""
 
113
    upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
 
114
    sysv_file = os.path.join(initd_dir, service_name)
 
115
    if init_is_systemd():
 
116
        service('enable', service_name)
 
117
    elif os.path.exists(upstart_file):
 
118
        override_path = os.path.join(
 
119
            init_dir, '{}.override'.format(service_name))
 
120
        if os.path.exists(override_path):
 
121
            os.unlink(override_path)
 
122
    elif os.path.exists(sysv_file):
 
123
        subprocess.check_call(["update-rc.d", service_name, "enable"])
 
124
    else:
 
125
        raise ValueError(
 
126
            "Unable to detect {0} as SystemD, Upstart {1} or"
 
127
            " SysV {2}".format(
 
128
                service_name, upstart_file, sysv_file))
 
129
 
 
130
    started = service_running(service_name)
 
131
    if not started:
 
132
        started = service_start(service_name)
 
133
    return started
 
134
 
 
135
 
66
136
def service(action, service_name):
67
137
    """Control a system service"""
68
 
    cmd = ['service', service_name, action]
 
138
    if init_is_systemd():
 
139
        cmd = ['systemctl', action, service_name]
 
140
    else:
 
141
        cmd = ['service', service_name, action]
69
142
    return subprocess.call(cmd) == 0
70
143
 
71
144
 
72
 
def service_running(service):
 
145
_UPSTART_CONF = "/etc/init/{}.conf"
 
146
_INIT_D_CONF = "/etc/init.d/{}"
 
147
 
 
148
 
 
149
def service_running(service_name):
73
150
    """Determine whether a system service is running"""
74
 
    try:
75
 
        output = subprocess.check_output(
76
 
            ['service', service, 'status'],
77
 
            stderr=subprocess.STDOUT).decode('UTF-8')
78
 
    except subprocess.CalledProcessError:
 
151
    if init_is_systemd():
 
152
        return service('is-active', service_name)
 
153
    else:
 
154
        if os.path.exists(_UPSTART_CONF.format(service_name)):
 
155
            try:
 
156
                output = subprocess.check_output(
 
157
                    ['status', service_name],
 
158
                    stderr=subprocess.STDOUT).decode('UTF-8')
 
159
            except subprocess.CalledProcessError:
 
160
                return False
 
161
            else:
 
162
                # This works for upstart scripts where the 'service' command
 
163
                # returns a consistent string to represent running
 
164
                # 'start/running'
 
165
                if ("start/running" in output or
 
166
                        "is running" in output or
 
167
                        "up and running" in output):
 
168
                    return True
 
169
        elif os.path.exists(_INIT_D_CONF.format(service_name)):
 
170
            # Check System V scripts init script return codes
 
171
            return service('status', service_name)
79
172
        return False
80
 
    else:
81
 
        if ("start/running" in output or "is running" in output):
82
 
            return True
83
 
        else:
84
 
            return False
85
 
 
86
 
 
87
 
def service_available(service_name):
88
 
    """Determine whether a system service is available"""
89
 
    try:
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
95
 
    else:
96
 
        return True
97
 
 
98
 
 
99
 
def adduser(username, password=None, shell='/bin/bash', system_user=False):
100
 
    """Add a user to the system"""
 
173
 
 
174
 
 
175
SYSTEMD_SYSTEM = '/run/systemd/system'
 
176
 
 
177
 
 
178
def init_is_systemd():
 
179
    """Return True if the host system uses systemd, False otherwise."""
 
180
    return os.path.isdir(SYSTEMD_SYSTEM)
 
181
 
 
182
 
 
183
def adduser(username, password=None, shell='/bin/bash',
 
184
            system_user=False, primary_group=None,
 
185
            secondary_groups=None, uid=None, home_dir=None):
 
186
    """Add a user to the system.
 
187
 
 
188
    Will log but otherwise succeed if the user already exists.
 
189
 
 
190
    :param str username: Username to create
 
191
    :param str password: Password for user; if ``None``, create a system user
 
192
    :param str shell: The default shell for the user
 
193
    :param bool system_user: Whether to create a login or system user
 
194
    :param str primary_group: Primary group for user; defaults to username
 
195
    :param list secondary_groups: Optional list of additional groups
 
196
    :param int uid: UID for user being created
 
197
    :param str home_dir: Home directory for user
 
198
 
 
199
    :returns: The password database entry struct, as returned by `pwd.getpwnam`
 
200
    """
101
201
    try:
102
202
        user_info = pwd.getpwnam(username)
103
203
        log('user {0} already exists!'.format(username))
 
204
        if uid:
 
205
            user_info = pwd.getpwuid(int(uid))
 
206
            log('user with uid {0} already exists!'.format(uid))
104
207
    except KeyError:
105
208
        log('creating user {0}'.format(username))
106
209
        cmd = ['useradd']
 
210
        if uid:
 
211
            cmd.extend(['--uid', str(uid)])
 
212
        if home_dir:
 
213
            cmd.extend(['--home', str(home_dir)])
107
214
        if system_user or password is None:
108
215
            cmd.append('--system')
109
216
        else:
112
219
                '--shell', shell,
113
220
                '--password', password,
114
221
            ])
 
222
        if not primary_group:
 
223
            try:
 
224
                grp.getgrnam(username)
 
225
                primary_group = username  # avoid "group exists" error
 
226
            except KeyError:
 
227
                pass
 
228
        if primary_group:
 
229
            cmd.extend(['-g', primary_group])
 
230
        if secondary_groups:
 
231
            cmd.extend(['-G', ','.join(secondary_groups)])
115
232
        cmd.append(username)
116
233
        subprocess.check_call(cmd)
117
234
        user_info = pwd.getpwnam(username)
118
235
    return user_info
119
236
 
120
237
 
121
 
def add_group(group_name, system_group=False):
122
 
    """Add a group to the system"""
 
238
def user_exists(username):
 
239
    """Check if a user exists"""
 
240
    try:
 
241
        pwd.getpwnam(username)
 
242
        user_exists = True
 
243
    except KeyError:
 
244
        user_exists = False
 
245
    return user_exists
 
246
 
 
247
 
 
248
def uid_exists(uid):
 
249
    """Check if a uid exists"""
 
250
    try:
 
251
        pwd.getpwuid(uid)
 
252
        uid_exists = True
 
253
    except KeyError:
 
254
        uid_exists = False
 
255
    return uid_exists
 
256
 
 
257
 
 
258
def group_exists(groupname):
 
259
    """Check if a group exists"""
 
260
    try:
 
261
        grp.getgrnam(groupname)
 
262
        group_exists = True
 
263
    except KeyError:
 
264
        group_exists = False
 
265
    return group_exists
 
266
 
 
267
 
 
268
def gid_exists(gid):
 
269
    """Check if a gid exists"""
 
270
    try:
 
271
        grp.getgrgid(gid)
 
272
        gid_exists = True
 
273
    except KeyError:
 
274
        gid_exists = False
 
275
    return gid_exists
 
276
 
 
277
 
 
278
def add_group(group_name, system_group=False, gid=None):
 
279
    """Add a group to the system
 
280
 
 
281
    Will log but otherwise succeed if the group already exists.
 
282
 
 
283
    :param str group_name: group to create
 
284
    :param bool system_group: Create system group
 
285
    :param int gid: GID for user being created
 
286
 
 
287
    :returns: The password database entry struct, as returned by `grp.getgrnam`
 
288
    """
123
289
    try:
124
290
        group_info = grp.getgrnam(group_name)
125
291
        log('group {0} already exists!'.format(group_name))
 
292
        if gid:
 
293
            group_info = grp.getgrgid(gid)
 
294
            log('group with gid {0} already exists!'.format(gid))
126
295
    except KeyError:
127
296
        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)
 
297
        add_new_group(group_name, system_group, gid)
137
298
        group_info = grp.getgrnam(group_name)
138
299
    return group_info
139
300
 
140
301
 
141
302
def add_user_to_group(username, group):
142
303
    """Add a user to a group"""
143
 
    cmd = [
144
 
        'gpasswd', '-a',
145
 
        username,
146
 
        group
147
 
    ]
 
304
    cmd = ['gpasswd', '-a', username, group]
148
305
    log("Adding user {} to group {}".format(username, group))
149
306
    subprocess.check_call(cmd)
150
307
 
203
360
 
204
361
 
205
362
def fstab_remove(mp):
206
 
    """Remove the given mountpoint entry from /etc/fstab
207
 
    """
 
363
    """Remove the given mountpoint entry from /etc/fstab"""
208
364
    return Fstab.remove_by_mountpoint(mp)
209
365
 
210
366
 
211
367
def fstab_add(dev, mp, fs, options=None):
212
 
    """Adds the given device entry to the /etc/fstab file
213
 
    """
 
368
    """Adds the given device entry to the /etc/fstab file"""
214
369
    return Fstab.add(dev, mp, fs, options=options)
215
370
 
216
371
 
254
409
    return system_mounts
255
410
 
256
411
 
 
412
def fstab_mount(mountpoint):
 
413
    """Mount filesystem using fstab"""
 
414
    cmd_args = ['mount', mountpoint]
 
415
    try:
 
416
        subprocess.check_output(cmd_args)
 
417
    except subprocess.CalledProcessError as e:
 
418
        log('Error unmounting {}\n{}'.format(mountpoint, e.output))
 
419
        return False
 
420
    return True
 
421
 
 
422
 
257
423
def file_hash(path, hash_type='md5'):
258
 
    """
259
 
    Generate a hash checksum of the contents of 'path' or None if not found.
 
424
    """Generate a hash checksum of the contents of 'path' or None if not found.
260
425
 
261
426
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
262
427
                          such as md5, sha1, sha256, sha512, etc.
271
436
 
272
437
 
273
438
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.
 
439
    """Generate a hash checksum of all files matching 'path'. Standard
 
440
    wildcards like '*' and '?' are supported, see documentation for the 'glob'
 
441
    module for more information.
278
442
 
279
443
    :return: dict: A { filename: hash } dictionary for all matched files.
280
444
                   Empty if none found.
286
450
 
287
451
 
288
452
def check_hash(path, checksum, hash_type='md5'):
289
 
    """
290
 
    Validate a file using a cryptographic checksum.
 
453
    """Validate a file using a cryptographic checksum.
291
454
 
292
455
    :param str checksum: Value of the checksum used to validate the file.
293
456
    :param str hash_type: Hash algorithm used to generate `checksum`.
302
465
 
303
466
 
304
467
class ChecksumError(ValueError):
 
468
    """A class derived from Value error to indicate the checksum failed."""
305
469
    pass
306
470
 
307
471
 
308
 
def restart_on_change(restart_map, stopstart=False):
 
472
def restart_on_change(restart_map, stopstart=False, restart_functions=None):
309
473
    """Restart services based on configuration files changing
310
474
 
311
475
    This function is used a decorator, for example::
323
487
    restarted if any file matching the pattern got changed, created
324
488
    or removed. Standard wildcards are supported, see documentation
325
489
    for the 'glob' module for more information.
 
490
 
 
491
    @param restart_map: {path_file_name: [service_name, ...]
 
492
    @param stopstart: DEFAULT false; whether to stop, start OR restart
 
493
    @param restart_functions: nonstandard functions to use to restart services
 
494
                              {svc: func, ...}
 
495
    @returns result from decorated function
326
496
    """
327
497
    def wrap(f):
 
498
        @functools.wraps(f)
328
499
        def wrapped_f(*args, **kwargs):
329
 
            checksums = {path: path_hash(path) for path in restart_map}
330
 
            f(*args, **kwargs)
331
 
            restarts = []
332
 
            for path in restart_map:
333
 
                if path_hash(path) != checksums[path]:
334
 
                    restarts += restart_map[path]
335
 
            services_list = list(OrderedDict.fromkeys(restarts))
336
 
            if not stopstart:
337
 
                for service_name in services_list:
338
 
                    service('restart', service_name)
339
 
            else:
340
 
                for action in ['stop', 'start']:
341
 
                    for service_name in services_list:
342
 
                        service(action, service_name)
 
500
            return restart_on_change_helper(
 
501
                (lambda: f(*args, **kwargs)), restart_map, stopstart,
 
502
                restart_functions)
343
503
        return wrapped_f
344
504
    return wrap
345
505
 
346
506
 
347
 
def lsb_release():
348
 
    """Return /etc/lsb-release in a dict"""
349
 
    d = {}
350
 
    with open('/etc/lsb-release', 'r') as lsb:
351
 
        for l in lsb:
352
 
            k, v = l.split('=')
353
 
            d[k.strip()] = v.strip()
354
 
    return d
 
507
def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
 
508
                             restart_functions=None):
 
509
    """Helper function to perform the restart_on_change function.
 
510
 
 
511
    This is provided for decorators to restart services if files described
 
512
    in the restart_map have changed after an invocation of lambda_f().
 
513
 
 
514
    @param lambda_f: function to call.
 
515
    @param restart_map: {file: [service, ...]}
 
516
    @param stopstart: whether to stop, start or restart a service
 
517
    @param restart_functions: nonstandard functions to use to restart services
 
518
                              {svc: func, ...}
 
519
    @returns result of lambda_f()
 
520
    """
 
521
    if restart_functions is None:
 
522
        restart_functions = {}
 
523
    checksums = {path: path_hash(path) for path in restart_map}
 
524
    r = lambda_f()
 
525
    # create a list of lists of the services to restart
 
526
    restarts = [restart_map[path]
 
527
                for path in restart_map
 
528
                if path_hash(path) != checksums[path]]
 
529
    # create a flat list of ordered services without duplicates from lists
 
530
    services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
 
531
    if services_list:
 
532
        actions = ('stop', 'start') if stopstart else ('restart',)
 
533
        for service_name in services_list:
 
534
            if service_name in restart_functions:
 
535
                restart_functions[service_name](service_name)
 
536
            else:
 
537
                for action in actions:
 
538
                    service(action, service_name)
 
539
    return r
355
540
 
356
541
 
357
542
def pwgen(length=None):
370
555
    return(''.join(random_chars))
371
556
 
372
557
 
373
 
def list_nics(nic_type):
374
 
    '''Return a list of nics of given type(s)'''
 
558
def is_phy_iface(interface):
 
559
    """Returns True if interface is not virtual, otherwise False."""
 
560
    if interface:
 
561
        sys_net = '/sys/class/net'
 
562
        if os.path.isdir(sys_net):
 
563
            for iface in glob.glob(os.path.join(sys_net, '*')):
 
564
                if '/virtual/' in os.path.realpath(iface):
 
565
                    continue
 
566
 
 
567
                if interface == os.path.basename(iface):
 
568
                    return True
 
569
 
 
570
    return False
 
571
 
 
572
 
 
573
def get_bond_master(interface):
 
574
    """Returns bond master if interface is bond slave otherwise None.
 
575
 
 
576
    NOTE: the provided interface is expected to be physical
 
577
    """
 
578
    if interface:
 
579
        iface_path = '/sys/class/net/%s' % (interface)
 
580
        if os.path.exists(iface_path):
 
581
            if '/virtual/' in os.path.realpath(iface_path):
 
582
                return None
 
583
 
 
584
            master = os.path.join(iface_path, 'master')
 
585
            if os.path.exists(master):
 
586
                master = os.path.realpath(master)
 
587
                # make sure it is a bond master
 
588
                if os.path.exists(os.path.join(master, 'bonding')):
 
589
                    return os.path.basename(master)
 
590
 
 
591
    return None
 
592
 
 
593
 
 
594
def list_nics(nic_type=None):
 
595
    """Return a list of nics of given type(s)"""
375
596
    if isinstance(nic_type, six.string_types):
376
597
        int_types = [nic_type]
377
598
    else:
378
599
        int_types = nic_type
 
600
 
379
601
    interfaces = []
380
 
    for int_type in int_types:
381
 
        cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
602
    if nic_type:
 
603
        for int_type in int_types:
 
604
            cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
 
605
            ip_output = subprocess.check_output(cmd).decode('UTF-8')
 
606
            ip_output = ip_output.split('\n')
 
607
            ip_output = (line for line in ip_output if line)
 
608
            for line in ip_output:
 
609
                if line.split()[1].startswith(int_type):
 
610
                    matched = re.search('.*: (' + int_type +
 
611
                                        r'[0-9]+\.[0-9]+)@.*', line)
 
612
                    if matched:
 
613
                        iface = matched.groups()[0]
 
614
                    else:
 
615
                        iface = line.split()[1].replace(":", "")
 
616
 
 
617
                    if iface not in interfaces:
 
618
                        interfaces.append(iface)
 
619
    else:
 
620
        cmd = ['ip', 'a']
382
621
        ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
383
 
        ip_output = (line for line in ip_output if line)
 
622
        ip_output = (line.strip() for line in ip_output if line)
 
623
 
 
624
        key = re.compile('^[0-9]+:\s+(.+):')
384
625
        for line in ip_output:
385
 
            if line.split()[1].startswith(int_type):
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)
 
626
            matched = re.search(key, line)
 
627
            if matched:
 
628
                iface = matched.group(1)
 
629
                iface = iface.partition("@")[0]
 
630
                if iface not in interfaces:
 
631
                    interfaces.append(iface)
392
632
 
393
633
    return interfaces
394
634
 
395
635
 
396
636
def set_nic_mtu(nic, mtu):
397
 
    '''Set MTU on a network interface'''
 
637
    """Set the Maximum Transmission Unit (MTU) on a network interface."""
398
638
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
399
639
    subprocess.check_call(cmd)
400
640
 
401
641
 
402
642
def get_nic_mtu(nic):
 
643
    """Return the Maximum Transmission Unit (MTU) for a network interface."""
403
644
    cmd = ['ip', 'addr', 'show', nic]
404
645
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
405
646
    mtu = ""
411
652
 
412
653
 
413
654
def get_nic_hwaddr(nic):
 
655
    """Return the Media Access Control (MAC) for a network interface."""
414
656
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
415
657
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
416
658
    hwaddr = ""
420
662
    return hwaddr
421
663
 
422
664
 
423
 
def cmp_pkgrevno(package, revno, pkgcache=None):
424
 
    '''Compare supplied revno with the revno of the installed package
425
 
 
426
 
    *  1 => Installed revno is greater than supplied arg
427
 
    *  0 => Installed revno is the same as supplied arg
428
 
    * -1 => Installed revno is less than supplied arg
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.
433
 
    '''
434
 
    import apt_pkg
435
 
    if not pkgcache:
436
 
        from charmhelpers.fetch import apt_cache
437
 
        pkgcache = apt_cache()
438
 
    pkg = pkgcache[package]
439
 
    return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
440
 
 
441
 
 
442
665
@contextmanager
443
 
def chdir(d):
 
666
def chdir(directory):
 
667
    """Change the current working directory to a different directory for a code
 
668
    block and return the previous directory after the block exits. Useful to
 
669
    run commands from a specificed directory.
 
670
 
 
671
    :param str directory: The directory path to change to for this context.
 
672
    """
444
673
    cur = os.getcwd()
445
674
    try:
446
 
        yield os.chdir(d)
 
675
        yield os.chdir(directory)
447
676
    finally:
448
677
        os.chdir(cur)
449
678
 
450
679
 
451
 
def chownr(path, owner, group, follow_links=True):
 
680
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
 
681
    """Recursively change user and group ownership of files and directories
 
682
    in given path. Doesn't chown path itself by default, only its children.
 
683
 
 
684
    :param str path: The string path to start changing ownership.
 
685
    :param str owner: The owner string to use when looking up the uid.
 
686
    :param str group: The group string to use when looking up the gid.
 
687
    :param bool follow_links: Also Chown links if True
 
688
    :param bool chowntopdir: Also chown path itself if True
 
689
    """
452
690
    uid = pwd.getpwnam(owner).pw_uid
453
691
    gid = grp.getgrnam(group).gr_gid
454
692
    if follow_links:
456
694
    else:
457
695
        chown = os.lchown
458
696
 
 
697
    if chowntopdir:
 
698
        broken_symlink = os.path.lexists(path) and not os.path.exists(path)
 
699
        if not broken_symlink:
 
700
            chown(path, uid, gid)
459
701
    for root, dirs, files in os.walk(path):
460
702
        for name in dirs + files:
461
703
            full = os.path.join(root, name)
465
707
 
466
708
 
467
709
def lchownr(path, owner, group):
 
710
    """Recursively change user and group ownership of files and directories
 
711
    in a given path, not following symbolic links. See the documentation for
 
712
    'os.lchown' for more information.
 
713
 
 
714
    :param str path: The string path to start changing ownership.
 
715
    :param str owner: The owner string to use when looking up the uid.
 
716
    :param str group: The group string to use when looking up the gid.
 
717
    """
468
718
    chownr(path, owner, group, follow_links=False)
 
719
 
 
720
 
 
721
def get_total_ram():
 
722
    """The total amount of system RAM in bytes.
 
723
 
 
724
    This is what is reported by the OS, and may be overcommitted when
 
725
    there are multiple containers hosted on the same machine.
 
726
    """
 
727
    with open('/proc/meminfo', 'r') as f:
 
728
        for line in f.readlines():
 
729
            if line:
 
730
                key, value, unit = line.split()
 
731
                if key == 'MemTotal:':
 
732
                    assert unit == 'kB', 'Unknown unit'
 
733
                    return int(value) * 1024  # Classic, not KiB.
 
734
        raise NotImplementedError()