~chris-gondolin/charms/trusty/keystone/ldap-ca-cert

« back to all changes in this revision

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

  • Committer: billy.olsen at canonical
  • Date: 2015-08-31 17:35:57 UTC
  • mfrom: (170.1.39 stable.remote)
  • Revision ID: billy.olsen@canonical.com-20150831173557-0r0ftkapbitq0s20
[ack,r=billy-olsen,1chb1n,tealeg,adam-collard] Add pause/resume actions to keystone.

This changes introduces the pause and resume action set to the keystone charm. These
actions can be used to pause keystone services on a unit for maintenance activities.

Show diffs side-by-side

added added

removed removed

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