~psivaa/uci-engine/rabbitmq-restish-with-proxy

« back to all changes in this revision

Viewing changes to charms/precise/webui/hooks/charmhelpers/contrib/charmsupport/volumes.py

  • Committer: Joe Talbott
  • Date: 2014-01-23 17:11:17 UTC
  • mto: (126.2.10 webui)
  • mto: This revision was merged to the branch mainline in revision 161.
  • Revision ID: joe.talbott@canonical.com-20140123171117-82s9buwmb4lazj6f
Add webui charm.

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