~charmers/charms/trusty/ubuntu-repository-cache/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
'''Functions related to charm storage maintenance'''

import glob
import os
import subprocess

# pylint can't find the modules  # pylint: disable=F0401
from charmhelpers.contrib.storage.linux import utils as Storage
from charmhelpers.core import (
    fstab,
    hookenv,
    host,
)
# pylint: enable=F0401

LOG = hookenv.log
SERVICE = 'ubuntu-repository-cache'


def setup():
    '''Set up storage for the charm'''

    config = hookenv.config()
    _setup_ephemeral(config['ephemeral-devices'])

    try:
        primary_mount = config['ephemeral-mounts'][0]
        secondary_mounts = config['ephemeral-mounts'][1:]
    except KeyError:
        primary_mount = '/'.join(('/srv', hookenv.service_name()))
        secondary_mounts = []

    # Make file system directories
    LOG('Creating filesystem entries for %s' % SERVICE)
    _mkdir(primary_mount, perms=0o775)

    _setup_metadata_path(primary_mount)
    _setup_packages_path(primary_mount, secondary_mounts)


def _setup_metadata_path(rootdir):
    '''Create the directory structure for metadata served by apache'''

    config = hookenv.config()
    apache_root = config['apache-root'] = '/'.join((rootdir, 'apache'))
    # Apache serves from www/ and metadata is in data/
    _mkdir(apache_root, owner='www-data', group='www-data', perms=0o775)
    _mkdir('/'.join((apache_root, 'www')),
           owner='www-data', group='www-data', perms=0o775)
    _mkdir('/'.join((apache_root, 'data')),
           owner='www-data', group='www-data', perms=0o775)
    host.symlink('/'.join((apache_root, 'data', 'ubuntu_active')),
                 '/'.join((apache_root, 'www', 'ubuntu')))


def _setup_packages_path(primary_mount, secondary_mounts):
    '''Create the squid cache directorys for package caches.

    The config 'squid-disk-caches' is populated with the path to each
    cache directory and the maximum space available, in MB, for that mount.
    In this list the primary device is added last and the maximum space
    calculation reserves room for the OS and metadata mirror.

    The order of the list is important because squid disk caches will
    be sized based on system memory, so some available disk space may
    not be used.  In that case, having the mount used for metadata
    mirrors last would let any additional space be used for that.
    '''

    config = hookenv.config()
    config['squid-disk-caches'] = []
    for mnt in secondary_mounts:
        squid_cache = '/'.join((mnt, 'squid'))
        _mkdir(squid_cache, owner='proxy', group='proxy', perms=0o770)
        max_size = _disk_free(squid_cache) * 0.95
        if max_size:
            config['squid-disk-caches'].append([squid_cache, max_size])

    # Add the primary mount last for squid.  This allocates it last
    # and saves additional space for metadata mirrors
    squid_cache = '/'.join((primary_mount, 'squid'))
    _mkdir(squid_cache, owner='proxy', group='proxy', perms=0o770)
    max_size = _disk_free(squid_cache)
    # Reserve 8GB for metadata and 4GB for the OS
    reserve = 8192 + 4092
    if max_size < reserve:
        LOG('Less than {}MB disk free @ {} for mirror.'.format(reserve,
                                                               primary_mount),
            hookenv.ERROR)
    else:
        max_size = max_size - reserve
        config['squid-disk-caches'].append([squid_cache, max_size])

    config.save()


def _setup_ephemeral(devices=None):
    '''Formats and mounts ephemeral devices

    Each ephemeral device is formated with ext4 and provided a label
    of the format '<service_name>_<#>'

    Devices are mounted at:
     /srv/<service_name>/ephemeral/<#>

    An fstab entry is added for each device to ensure the device is
    mounted on reboot.
    '''

    if not devices:
        LOG('No ephemeral devices provided')
        return

    base_dir = '/'.join(('/srv', hookenv.service_name()))
    _mkdir(base_dir, perms=0o775)
    ephemeral_dir = '/'.join((base_dir, 'ephemeral'))
    _mkdir(ephemeral_dir, perms=0o775)

    config = hookenv.config()
    config['ephemeral-mounts'] = []

    devno = 0
    for dev in devices.split(','):
        dev = dev.strip()
        LOG('Configuring ephemeral device {}.'.format(dev), hookenv.DEBUG)
        if not Storage.is_block_device(dev):
            LOG('%s is not a block device, skipping' % dev, hookenv.ERROR)
            continue

        partitions = glob.glob(dev + '*').remove(dev) or []
        for part in partitions:
            if Storage.is_device_mounted(part):
                LOG('Ephemeral device had a mounted partition %s skipping' %
                    part, hookenv.ERROR)
                continue

        if Storage.is_device_mounted(dev):
            LOG('Unmounting %s' % dev)
            host.umount(dev)

        label = '_'.join(('urc', str(devno)))
        mountpoint = '/'.join((ephemeral_dir, str(devno)))

        Storage.zap_disk(dev)
        subprocess.check_call(['mkfs', '-F', '-t', 'ext4', '-L', label, dev])

        # Remove any existing fstab entry
        ftab = fstab.Fstab()
        entry = ftab.get_entry_by_attr('device', dev)
        if entry:
            ftab.remove_entry(entry)

        # Add to fstab by label, not device name
        _mkdir(mountpoint, perms=0o775)
        devname = '='.join(('LABEL', label))
        ftab.add_entry(fstab.Fstab.Entry(devname, mountpoint, 'ext4',
                                         options=None))
        ftab.close()

        subprocess.check_call(['mount', mountpoint])
        os.chmod(mountpoint, 0o775)
        config['ephemeral-mounts'].append(mountpoint)
        devno += 1
    LOG('Ephemeral storage mounted @ {}'.format(config['ephemeral-mounts']),
        hookenv.DEBUG)
    config.save()


def _mkdir(path, owner='root', group='root', perms=0o555, force=False):
    '''Make directories recursively with a umask=0o000.

    This storage module makes calls to mkdir repeatedly with permissions
    that include group write permission.  This would be masked by the
    default 0o022 umask.   This simple helper function will clear
    the umask prior to calling host.mkdir and then restore it when done.
    '''

    prev_umask = os.umask(0o000)
    host.mkdir(path, owner=owner, group=group, perms=perms, force=force)
    os.umask(prev_umask)


def _disk_free(path):
    '''Return disk space free, in MB, for the specified path'''

    output = subprocess.check_output(['df', '-BM', path]).decode().split('\n')
    _, _, _, avail, _, _ = output[1].split()
    LOG('disk free for {} is {}MB'.format(path, avail), hookenv.DEBUG)
    return int(avail.rstrip('M'))


def update_checks(nrpe_config):
    '''Update nagios check for ephemeral storage space if present'''

    config = hookenv.config()
    try:
        LOG('Adding check for ephemeral stoage {}'.format(
            config['ephemeral-mounts']),
            hookenv.DEBUG)
    except KeyError:
        LOG('No checks added for ephemeral storage', hookenv.DEBUG)
        return

    nrpe_config.add_check(
        shortname='disks-ephemeral',
        description='disk space on ephemeral volumes',
        check_cmd='check_disk -w 15 -c 5 -r '
                  '^/srv/ubuntu-repository-cache/ephemeral/[0-9].*$',
        )