~openstack-charmers-archive/charms/trusty/nova-compute/next

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/charmsupport/volumes.py

  • Committer: Liam Young
  • Date: 2015-01-12 17:41:39 UTC
  • mfrom: (85.3.12 nova-compute)
  • Revision ID: liam.young@canonical.com-20150112174139-qjnw0hq7xuaieqd8
[gnuoy,r=james-page] Add support for nrpe

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''
 
2
Functions for managing volumes in juju units. One volume is supported per unit.
 
3
Subordinates may have their own storage, provided it is on its own partition.
 
4
 
 
5
Configuration stanzas::
 
6
 
 
7
  volume-ephemeral:
 
8
    type: boolean
 
9
    default: true
 
10
    description: >
 
11
      If false, a volume is mounted as sepecified in "volume-map"
 
12
      If true, ephemeral storage will be used, meaning that log data
 
13
         will only exist as long as the machine. YOU HAVE BEEN WARNED.
 
14
  volume-map:
 
15
    type: string
 
16
    default: {}
 
17
    description: >
 
18
      YAML map of units to device names, e.g:
 
19
        "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
 
20
      Service units will raise a configure-error if volume-ephemeral
 
21
      is 'true' and no volume-map value is set. Use 'juju set' to set a
 
22
      value and 'juju resolved' to complete configuration.
 
23
 
 
24
Usage::
 
25
 
 
26
    from charmsupport.volumes import configure_volume, VolumeConfigurationError
 
27
    from charmsupport.hookenv import log, ERROR
 
28
    def post_mount_hook():
 
29
        stop_service('myservice')
 
30
    def post_mount_hook():
 
31
        start_service('myservice')
 
32
 
 
33
    if __name__ == '__main__':
 
34
        try:
 
35
            configure_volume(before_change=pre_mount_hook,
 
36
                             after_change=post_mount_hook)
 
37
        except VolumeConfigurationError:
 
38
            log('Storage could not be configured', ERROR)
 
39
 
 
40
'''
 
41
 
 
42
# XXX: Known limitations
 
43
# - fstab is neither consulted nor updated
 
44
 
 
45
import os
 
46
from charmhelpers.core import hookenv
 
47
from charmhelpers.core import host
 
48
import yaml
 
49
 
 
50
 
 
51
MOUNT_BASE = '/srv/juju/volumes'
 
52
 
 
53
 
 
54
class VolumeConfigurationError(Exception):
 
55
    '''Volume configuration data is missing or invalid'''
 
56
    pass
 
57
 
 
58
 
 
59
def get_config():
 
60
    '''Gather and sanity-check volume configuration data'''
 
61
    volume_config = {}
 
62
    config = hookenv.config()
 
63
 
 
64
    errors = False
 
65
 
 
66
    if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
 
67
        volume_config['ephemeral'] = True
 
68
    else:
 
69
        volume_config['ephemeral'] = False
 
70
 
 
71
    try:
 
72
        volume_map = yaml.safe_load(config.get('volume-map', '{}'))
 
73
    except yaml.YAMLError as e:
 
74
        hookenv.log("Error parsing YAML volume-map: {}".format(e),
 
75
                    hookenv.ERROR)
 
76
        errors = True
 
77
    if volume_map is None:
 
78
        # probably an empty string
 
79
        volume_map = {}
 
80
    elif not isinstance(volume_map, dict):
 
81
        hookenv.log("Volume-map should be a dictionary, not {}".format(
 
82
            type(volume_map)))
 
83
        errors = True
 
84
 
 
85
    volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
 
86
    if volume_config['device'] and volume_config['ephemeral']:
 
87
        # asked for ephemeral storage but also defined a volume ID
 
88
        hookenv.log('A volume is defined for this unit, but ephemeral '
 
89
                    'storage was requested', hookenv.ERROR)
 
90
        errors = True
 
91
    elif not volume_config['device'] and not volume_config['ephemeral']:
 
92
        # asked for permanent storage but did not define volume ID
 
93
        hookenv.log('Ephemeral storage was requested, but there is no volume '
 
94
                    'defined for this unit.', hookenv.ERROR)
 
95
        errors = True
 
96
 
 
97
    unit_mount_name = hookenv.local_unit().replace('/', '-')
 
98
    volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
 
99
 
 
100
    if errors:
 
101
        return None
 
102
    return volume_config
 
103
 
 
104
 
 
105
def mount_volume(config):
 
106
    if os.path.exists(config['mountpoint']):
 
107
        if not os.path.isdir(config['mountpoint']):
 
108
            hookenv.log('Not a directory: {}'.format(config['mountpoint']))
 
109
            raise VolumeConfigurationError()
 
110
    else:
 
111
        host.mkdir(config['mountpoint'])
 
112
    if os.path.ismount(config['mountpoint']):
 
113
        unmount_volume(config)
 
114
    if not host.mount(config['device'], config['mountpoint'], persist=True):
 
115
        raise VolumeConfigurationError()
 
116
 
 
117
 
 
118
def unmount_volume(config):
 
119
    if os.path.ismount(config['mountpoint']):
 
120
        if not host.umount(config['mountpoint'], persist=True):
 
121
            raise VolumeConfigurationError()
 
122
 
 
123
 
 
124
def managed_mounts():
 
125
    '''List of all mounted managed volumes'''
 
126
    return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
 
127
 
 
128
 
 
129
def configure_volume(before_change=lambda: None, after_change=lambda: None):
 
130
    '''Set up storage (or don't) according to the charm's volume configuration.
 
131
       Returns the mount point or "ephemeral". before_change and after_change
 
132
       are optional functions to be called if the volume configuration changes.
 
133
    '''
 
134
 
 
135
    config = get_config()
 
136
    if not config:
 
137
        hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
 
138
        raise VolumeConfigurationError()
 
139
 
 
140
    if config['ephemeral']:
 
141
        if os.path.ismount(config['mountpoint']):
 
142
            before_change()
 
143
            unmount_volume(config)
 
144
            after_change()
 
145
        return 'ephemeral'
 
146
    else:
 
147
        # persistent storage
 
148
        if os.path.ismount(config['mountpoint']):
 
149
            mounts = dict(managed_mounts())
 
150
            if mounts.get(config['mountpoint']) != config['device']:
 
151
                before_change()
 
152
                unmount_volume(config)
 
153
                mount_volume(config)
 
154
                after_change()
 
155
        else:
 
156
            before_change()
 
157
            mount_volume(config)
 
158
            after_change()
 
159
        return config['mountpoint']