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.
5
Configuration stanzas::
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.
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.
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')
33
if __name__ == '__main__':
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)
42
# XXX: Known limitations
43
# - fstab is neither consulted nor updated
46
from charmhelpers.core import hookenv
47
from charmhelpers.core import host
51
MOUNT_BASE = '/srv/juju/volumes'
54
class VolumeConfigurationError(Exception):
55
'''Volume configuration data is missing or invalid'''
60
'''Gather and sanity-check volume configuration data'''
62
config = hookenv.config()
66
if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
67
volume_config['ephemeral'] = True
69
volume_config['ephemeral'] = False
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),
77
if volume_map is None:
78
# probably an empty string
80
elif not isinstance(volume_map, dict):
81
hookenv.log("Volume-map should be a dictionary, not {}".format(
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)
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)
97
unit_mount_name = hookenv.local_unit().replace('/', '-')
98
volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
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()
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()
118
def unmount_volume(config):
119
if os.path.ismount(config['mountpoint']):
120
if not host.umount(config['mountpoint'], persist=True):
121
raise VolumeConfigurationError()
124
def managed_mounts():
125
'''List of all mounted managed volumes'''
126
return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
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.
135
config = get_config()
137
hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
138
raise VolumeConfigurationError()
140
if config['ephemeral']:
141
if os.path.ismount(config['mountpoint']):
143
unmount_volume(config)
148
if os.path.ismount(config['mountpoint']):
149
mounts = dict(managed_mounts())
150
if mounts.get(config['mountpoint']) != config['device']:
152
unmount_volume(config)
159
return config['mountpoint']