~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-27 14:54:08 UTC
  • mfrom: (126.3.8 webui)
  • mto: This revision was merged to the branch mainline in revision 161.
  • Revision ID: joe.talbott@canonical.com-20140127145408-zpubebx02y6oumxq
merge doanac's cleanup branch

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']