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.
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.
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.
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')
31
if __name__ == '__main__':
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)
39
# XXX: Known limitations
40
# - fstab is neither consulted nor updated
43
from charmhelpers.core import hookenv
44
from charmhelpers.core import host
48
MOUNT_BASE = '/srv/juju/volumes'
51
class VolumeConfigurationError(Exception):
52
'''Volume configuration data is missing or invalid'''
57
'''Gather and sanity-check volume configuration data'''
59
config = hookenv.config()
63
if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
64
volume_config['ephemeral'] = True
66
volume_config['ephemeral'] = False
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),
74
if volume_map is None:
75
# probably an empty string
77
elif not isinstance(volume_map, dict):
78
hookenv.log("Volume-map should be a dictionary, not {}".format(
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)
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)
94
unit_mount_name = hookenv.local_unit().replace('/', '-')
95
volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
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()
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()
115
def unmount_volume(config):
116
if os.path.ismount(config['mountpoint']):
117
if not host.umount(config['mountpoint'], persist=True):
118
raise VolumeConfigurationError()
121
def managed_mounts():
122
'''List of all mounted managed volumes'''
123
return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
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.
132
config = get_config()
134
hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
135
raise VolumeConfigurationError()
137
if config['ephemeral']:
138
if os.path.ismount(config['mountpoint']):
140
unmount_volume(config)
145
if os.path.ismount(config['mountpoint']):
146
mounts = dict(managed_mounts())
147
if mounts.get(config['mountpoint']) != config['device']:
149
unmount_volume(config)
156
return config['mountpoint']