~evarlast/charms/trusty/mongodb/fix-backup

« back to all changes in this revision

Viewing changes to charmhelpers/core/host.py

  • Committer: James Page
  • Date: 2016-06-10 10:52:50 UTC
  • mto: This revision was merged to the branch mainline in revision 88.
  • Revision ID: james.page@ubuntu.com-20160610105250-5fya7wull02rhmng
Resync charmhelper to resolve issues with Newton UCA comprehension

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
import string
31
31
import subprocess
32
32
import hashlib
 
33
import functools
 
34
import itertools
33
35
from contextlib import contextmanager
34
36
from collections import OrderedDict
35
37
 
126
128
    return subprocess.call(cmd) == 0
127
129
 
128
130
 
 
131
_UPSTART_CONF = "/etc/init/{}.conf"
 
132
_INIT_D_CONF = "/etc/init.d/{}"
 
133
 
 
134
 
129
135
def service_running(service_name):
130
136
    """Determine whether a system service is running"""
131
137
    if init_is_systemd():
132
138
        return service('is-active', service_name)
133
139
    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:
 
140
        if os.path.exists(_UPSTART_CONF.format(service_name)):
 
141
            try:
 
142
                output = subprocess.check_output(
 
143
                    ['status', service_name],
 
144
                    stderr=subprocess.STDOUT).decode('UTF-8')
 
145
            except subprocess.CalledProcessError:
144
146
                return False
 
147
            else:
 
148
                # This works for upstart scripts where the 'service' command
 
149
                # returns a consistent string to represent running 'start/running'
 
150
                if "start/running" in output:
 
151
                    return True
 
152
        elif os.path.exists(_INIT_D_CONF.format(service_name)):
 
153
            # Check System V scripts init script return codes
 
154
            return service('status', service_name)
 
155
        return False
145
156
 
146
157
 
147
158
def service_available(service_name):
160
171
 
161
172
 
162
173
def init_is_systemd():
 
174
    """Return True if the host system uses systemd, False otherwise."""
163
175
    return os.path.isdir(SYSTEMD_SYSTEM)
164
176
 
165
177
 
166
178
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.
 
179
            primary_group=None, secondary_groups=None, uid=None):
 
180
    """Add a user to the system.
170
181
 
171
182
    Will log but otherwise succeed if the user already exists.
172
183
 
174
185
    :param str password: Password for user; if ``None``, create a system user
175
186
    :param str shell: The default shell for the user
176
187
    :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
 
188
    :param str primary_group: Primary group for user; defaults to username
178
189
    :param list secondary_groups: Optional list of additional groups
 
190
    :param int uid: UID for user being created
179
191
 
180
192
    :returns: The password database entry struct, as returned by `pwd.getpwnam`
181
193
    """
182
194
    try:
183
195
        user_info = pwd.getpwnam(username)
184
196
        log('user {0} already exists!'.format(username))
 
197
        if uid:
 
198
            user_info = pwd.getpwuid(int(uid))
 
199
            log('user with uid {0} already exists!'.format(uid))
185
200
    except KeyError:
186
201
        log('creating user {0}'.format(username))
187
202
        cmd = ['useradd']
 
203
        if uid:
 
204
            cmd.extend(['--uid', str(uid)])
188
205
        if system_user or password is None:
189
206
            cmd.append('--system')
190
207
        else:
219
236
    return user_exists
220
237
 
221
238
 
222
 
def add_group(group_name, system_group=False):
223
 
    """Add a group to the system"""
 
239
def uid_exists(uid):
 
240
    """Check if a uid exists"""
 
241
    try:
 
242
        pwd.getpwuid(uid)
 
243
        uid_exists = True
 
244
    except KeyError:
 
245
        uid_exists = False
 
246
    return uid_exists
 
247
 
 
248
 
 
249
def group_exists(groupname):
 
250
    """Check if a group exists"""
 
251
    try:
 
252
        grp.getgrnam(groupname)
 
253
        group_exists = True
 
254
    except KeyError:
 
255
        group_exists = False
 
256
    return group_exists
 
257
 
 
258
 
 
259
def gid_exists(gid):
 
260
    """Check if a gid exists"""
 
261
    try:
 
262
        grp.getgrgid(gid)
 
263
        gid_exists = True
 
264
    except KeyError:
 
265
        gid_exists = False
 
266
    return gid_exists
 
267
 
 
268
 
 
269
def add_group(group_name, system_group=False, gid=None):
 
270
    """Add a group to the system
 
271
 
 
272
    Will log but otherwise succeed if the group already exists.
 
273
 
 
274
    :param str group_name: group to create
 
275
    :param bool system_group: Create system group
 
276
    :param int gid: GID for user being created
 
277
 
 
278
    :returns: The password database entry struct, as returned by `grp.getgrnam`
 
279
    """
224
280
    try:
225
281
        group_info = grp.getgrnam(group_name)
226
282
        log('group {0} already exists!'.format(group_name))
 
283
        if gid:
 
284
            group_info = grp.getgrgid(gid)
 
285
            log('group with gid {0} already exists!'.format(gid))
227
286
    except KeyError:
228
287
        log('creating group {0}'.format(group_name))
229
288
        cmd = ['addgroup']
 
289
        if gid:
 
290
            cmd.extend(['--gid', str(gid)])
230
291
        if system_group:
231
292
            cmd.append('--system')
232
293
        else:
300
361
 
301
362
 
302
363
def fstab_remove(mp):
303
 
    """Remove the given mountpoint entry from /etc/fstab
304
 
    """
 
364
    """Remove the given mountpoint entry from /etc/fstab"""
305
365
    return Fstab.remove_by_mountpoint(mp)
306
366
 
307
367
 
308
368
def fstab_add(dev, mp, fs, options=None):
309
 
    """Adds the given device entry to the /etc/fstab file
310
 
    """
 
369
    """Adds the given device entry to the /etc/fstab file"""
311
370
    return Fstab.add(dev, mp, fs, options=options)
312
371
 
313
372
 
363
422
 
364
423
 
365
424
def file_hash(path, hash_type='md5'):
366
 
    """
367
 
    Generate a hash checksum of the contents of 'path' or None if not found.
 
425
    """Generate a hash checksum of the contents of 'path' or None if not found.
368
426
 
369
427
    :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
370
428
                          such as md5, sha1, sha256, sha512, etc.
379
437
 
380
438
 
381
439
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.
 
440
    """Generate a hash checksum of all files matching 'path'. Standard
 
441
    wildcards like '*' and '?' are supported, see documentation for the 'glob'
 
442
    module for more information.
386
443
 
387
444
    :return: dict: A { filename: hash } dictionary for all matched files.
388
445
                   Empty if none found.
394
451
 
395
452
 
396
453
def check_hash(path, checksum, hash_type='md5'):
397
 
    """
398
 
    Validate a file using a cryptographic checksum.
 
454
    """Validate a file using a cryptographic checksum.
399
455
 
400
456
    :param str checksum: Value of the checksum used to validate the file.
401
457
    :param str hash_type: Hash algorithm used to generate `checksum`.
410
466
 
411
467
 
412
468
class ChecksumError(ValueError):
 
469
    """A class derived from Value error to indicate the checksum failed."""
413
470
    pass
414
471
 
415
472
 
416
 
def restart_on_change(restart_map, stopstart=False):
 
473
def restart_on_change(restart_map, stopstart=False, restart_functions=None):
417
474
    """Restart services based on configuration files changing
418
475
 
419
476
    This function is used a decorator, for example::
431
488
    restarted if any file matching the pattern got changed, created
432
489
    or removed. Standard wildcards are supported, see documentation
433
490
    for the 'glob' module for more information.
 
491
 
 
492
    @param restart_map: {path_file_name: [service_name, ...]
 
493
    @param stopstart: DEFAULT false; whether to stop, start OR restart
 
494
    @param restart_functions: nonstandard functions to use to restart services
 
495
                              {svc: func, ...}
 
496
    @returns result from decorated function
434
497
    """
435
498
    def wrap(f):
 
499
        @functools.wraps(f)
436
500
        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)
 
501
            return restart_on_change_helper(
 
502
                (lambda: f(*args, **kwargs)), restart_map, stopstart,
 
503
                restart_functions)
451
504
        return wrapped_f
452
505
    return wrap
453
506
 
454
507
 
 
508
def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
 
509
                             restart_functions=None):
 
510
    """Helper function to perform the restart_on_change function.
 
511
 
 
512
    This is provided for decorators to restart services if files described
 
513
    in the restart_map have changed after an invocation of lambda_f().
 
514
 
 
515
    @param lambda_f: function to call.
 
516
    @param restart_map: {file: [service, ...]}
 
517
    @param stopstart: whether to stop, start or restart a service
 
518
    @param restart_functions: nonstandard functions to use to restart services
 
519
                              {svc: func, ...}
 
520
    @returns result of lambda_f()
 
521
    """
 
522
    if restart_functions is None:
 
523
        restart_functions = {}
 
524
    checksums = {path: path_hash(path) for path in restart_map}
 
525
    r = lambda_f()
 
526
    # create a list of lists of the services to restart
 
527
    restarts = [restart_map[path]
 
528
                for path in restart_map
 
529
                if path_hash(path) != checksums[path]]
 
530
    # create a flat list of ordered services without duplicates from lists
 
531
    services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
 
532
    if services_list:
 
533
        actions = ('stop', 'start') if stopstart else ('restart',)
 
534
        for service_name in services_list:
 
535
            if service_name in restart_functions:
 
536
                restart_functions[service_name](service_name)
 
537
            else:
 
538
                for action in actions:
 
539
                    service(action, service_name)
 
540
    return r
 
541
 
 
542
 
455
543
def lsb_release():
456
544
    """Return /etc/lsb-release in a dict"""
457
545
    d = {}
515
603
 
516
604
 
517
605
def list_nics(nic_type=None):
518
 
    '''Return a list of nics of given type(s)'''
 
606
    """Return a list of nics of given type(s)"""
519
607
    if isinstance(nic_type, six.string_types):
520
608
        int_types = [nic_type]
521
609
    else:
557
645
 
558
646
 
559
647
def set_nic_mtu(nic, mtu):
560
 
    '''Set MTU on a network interface'''
 
648
    """Set the Maximum Transmission Unit (MTU) on a network interface."""
561
649
    cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
562
650
    subprocess.check_call(cmd)
563
651
 
564
652
 
565
653
def get_nic_mtu(nic):
 
654
    """Return the Maximum Transmission Unit (MTU) for a network interface."""
566
655
    cmd = ['ip', 'addr', 'show', nic]
567
656
    ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
568
657
    mtu = ""
574
663
 
575
664
 
576
665
def get_nic_hwaddr(nic):
 
666
    """Return the Media Access Control (MAC) for a network interface."""
577
667
    cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
578
668
    ip_output = subprocess.check_output(cmd).decode('UTF-8')
579
669
    hwaddr = ""
584
674
 
585
675
 
586
676
def cmp_pkgrevno(package, revno, pkgcache=None):
587
 
    '''Compare supplied revno with the revno of the installed package
 
677
    """Compare supplied revno with the revno of the installed package
588
678
 
589
679
    *  1 => Installed revno is greater than supplied arg
590
680
    *  0 => Installed revno is the same as supplied arg
593
683
    This function imports apt_cache function from charmhelpers.fetch if
594
684
    the pkgcache argument is None. Be sure to add charmhelpers.fetch if
595
685
    you call this function, or pass an apt_pkg.Cache() instance.
596
 
    '''
 
686
    """
597
687
    import apt_pkg
598
688
    if not pkgcache:
599
689
        from charmhelpers.fetch import apt_cache
603
693
 
604
694
 
605
695
@contextmanager
606
 
def chdir(d):
 
696
def chdir(directory):
 
697
    """Change the current working directory to a different directory for a code
 
698
    block and return the previous directory after the block exits. Useful to
 
699
    run commands from a specificed directory.
 
700
 
 
701
    :param str directory: The directory path to change to for this context.
 
702
    """
607
703
    cur = os.getcwd()
608
704
    try:
609
 
        yield os.chdir(d)
 
705
        yield os.chdir(directory)
610
706
    finally:
611
707
        os.chdir(cur)
612
708
 
613
709
 
614
710
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
615
 
    """
616
 
    Recursively change user and group ownership of files and directories
 
711
    """Recursively change user and group ownership of files and directories
617
712
    in given path. Doesn't chown path itself by default, only its children.
618
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.
619
717
    :param bool follow_links: Also Chown links if True
620
718
    :param bool chowntopdir: Also chown path itself if True
621
719
    """
639
737
 
640
738
 
641
739
def lchownr(path, owner, group):
 
740
    """Recursively change user and group ownership of files and directories
 
741
    in a given path, not following symbolic links. See the documentation for
 
742
    'os.lchown' for more information.
 
743
 
 
744
    :param str path: The string path to start changing ownership.
 
745
    :param str owner: The owner string to use when looking up the uid.
 
746
    :param str group: The group string to use when looking up the gid.
 
747
    """
642
748
    chownr(path, owner, group, follow_links=False)
643
749
 
644
750
 
645
751
def get_total_ram():
646
 
    '''The total amount of system RAM in bytes.
 
752
    """The total amount of system RAM in bytes.
647
753
 
648
754
    This is what is reported by the OS, and may be overcommitted when
649
755
    there are multiple containers hosted on the same machine.
650
 
    '''
 
756
    """
651
757
    with open('/proc/meminfo', 'r') as f:
652
758
        for line in f.readlines():
653
759
            if line: