~1chb1n/charms/trusty/nova-cloud-controller/15.10-stable-flip-tests-helper-syncs

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/hahelpers/ceph.py

Check in start of py redux.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright 2012 Canonical Ltd.
 
3
#
 
4
# This file is sourced from lp:openstack-charm-helpers
 
5
#
 
6
# Authors:
 
7
#  James Page <james.page@ubuntu.com>
 
8
#  Adam Gandelman <adamg@ubuntu.com>
 
9
#
 
10
 
 
11
import commands
 
12
import os
 
13
import shutil
 
14
 
 
15
from subprocess import (
 
16
    check_call,
 
17
    check_output,
 
18
    CalledProcessError
 
19
)
 
20
 
 
21
from charmhelpers.core.hookenv import (
 
22
    relation_get,
 
23
    relation_ids,
 
24
    related_units,
 
25
    log,
 
26
    INFO,
 
27
)
 
28
 
 
29
from charmhelpers.core.host import (
 
30
    apt_install,
 
31
    mount,
 
32
    mounts,
 
33
    service_start,
 
34
    service_stop,
 
35
    umount,
 
36
)
 
37
 
 
38
KEYRING = '/etc/ceph/ceph.client.%s.keyring'
 
39
KEYFILE = '/etc/ceph/ceph.client.%s.key'
 
40
 
 
41
CEPH_CONF = """[global]
 
42
 auth supported = %(auth)s
 
43
 keyring = %(keyring)s
 
44
 mon host = %(mon_hosts)s
 
45
"""
 
46
 
 
47
 
 
48
def running(service):
 
49
    # this local util can be dropped as soon the following branch lands
 
50
    # in lp:charm-helpers
 
51
    # https://code.launchpad.net/~gandelman-a/charm-helpers/service_running/
 
52
    try:
 
53
        output = check_output(['service', service, 'status'])
 
54
    except CalledProcessError:
 
55
        return False
 
56
    else:
 
57
        if ("start/running" in output or "is running" in output):
 
58
            return True
 
59
        else:
 
60
            return False
 
61
 
 
62
 
 
63
def install():
 
64
    ceph_dir = "/etc/ceph"
 
65
    if not os.path.isdir(ceph_dir):
 
66
        os.mkdir(ceph_dir)
 
67
    apt_install('ceph-common', fatal=True)
 
68
 
 
69
 
 
70
def rbd_exists(service, pool, rbd_img):
 
71
    (rc, out) = commands.getstatusoutput('rbd list --id %s --pool %s' %
 
72
                                         (service, pool))
 
73
    return rbd_img in out
 
74
 
 
75
 
 
76
def create_rbd_image(service, pool, image, sizemb):
 
77
    cmd = [
 
78
        'rbd',
 
79
        'create',
 
80
        image,
 
81
        '--size',
 
82
        str(sizemb),
 
83
        '--id',
 
84
        service,
 
85
        '--pool',
 
86
        pool
 
87
    ]
 
88
    check_call(cmd)
 
89
 
 
90
 
 
91
def pool_exists(service, name):
 
92
    (rc, out) = commands.getstatusoutput("rados --id %s lspools" % service)
 
93
    return name in out
 
94
 
 
95
 
 
96
def create_pool(service, name):
 
97
    cmd = [
 
98
        'rados',
 
99
        '--id',
 
100
        service,
 
101
        'mkpool',
 
102
        name
 
103
    ]
 
104
    check_call(cmd)
 
105
 
 
106
 
 
107
def keyfile_path(service):
 
108
    return KEYFILE % service
 
109
 
 
110
 
 
111
def keyring_path(service):
 
112
    return KEYRING % service
 
113
 
 
114
 
 
115
def create_keyring(service, key):
 
116
    keyring = keyring_path(service)
 
117
    if os.path.exists(keyring):
 
118
        log('ceph: Keyring exists at %s.' % keyring, level=INFO)
 
119
    cmd = [
 
120
        'ceph-authtool',
 
121
        keyring,
 
122
        '--create-keyring',
 
123
        '--name=client.%s' % service,
 
124
        '--add-key=%s' % key
 
125
    ]
 
126
    check_call(cmd)
 
127
    log('ceph: Created new ring at %s.' % keyring, level=INFO)
 
128
 
 
129
 
 
130
def create_key_file(service, key):
 
131
    # create a file containing the key
 
132
    keyfile = keyfile_path(service)
 
133
    if os.path.exists(keyfile):
 
134
        log('ceph: Keyfile exists at %s.' % keyfile, level=INFO)
 
135
    fd = open(keyfile, 'w')
 
136
    fd.write(key)
 
137
    fd.close()
 
138
    log('ceph: Created new keyfile at %s.' % keyfile, level=INFO)
 
139
 
 
140
 
 
141
def get_ceph_nodes():
 
142
    hosts = []
 
143
    for r_id in relation_ids('ceph'):
 
144
        for unit in related_units(r_id):
 
145
            hosts.append(relation_get('private-address', unit=unit, rid=r_id))
 
146
    return hosts
 
147
 
 
148
 
 
149
def configure(service, key, auth):
 
150
    create_keyring(service, key)
 
151
    create_key_file(service, key)
 
152
    hosts = get_ceph_nodes()
 
153
    mon_hosts = ",".join(map(str, hosts))
 
154
    keyring = keyring_path(service)
 
155
    with open('/etc/ceph/ceph.conf', 'w') as ceph_conf:
 
156
        ceph_conf.write(CEPH_CONF % locals())
 
157
    modprobe_kernel_module('rbd')
 
158
 
 
159
 
 
160
def image_mapped(image_name):
 
161
    (rc, out) = commands.getstatusoutput('rbd showmapped')
 
162
    return image_name in out
 
163
 
 
164
 
 
165
def map_block_storage(service, pool, image):
 
166
    cmd = [
 
167
        'rbd',
 
168
        'map',
 
169
        '%s/%s' % (pool, image),
 
170
        '--user',
 
171
        service,
 
172
        '--secret',
 
173
        keyfile_path(service),
 
174
    ]
 
175
    check_call(cmd)
 
176
 
 
177
 
 
178
def filesystem_mounted(fs):
 
179
    return fs in [f for m, f in mounts()]
 
180
 
 
181
 
 
182
def make_filesystem(blk_device, fstype='ext4'):
 
183
    log('ceph: Formatting block device %s as filesystem %s.' %
 
184
        (blk_device, fstype), level=INFO)
 
185
    cmd = ['mkfs', '-t', fstype, blk_device]
 
186
    check_call(cmd)
 
187
 
 
188
 
 
189
def place_data_on_ceph(service, blk_device, data_src_dst, fstype='ext4'):
 
190
    # mount block device into /mnt
 
191
    mount(blk_device, '/mnt')
 
192
 
 
193
    # copy data to /mnt
 
194
    try:
 
195
        copy_files(data_src_dst, '/mnt')
 
196
    except:
 
197
        pass
 
198
 
 
199
    # umount block device
 
200
    umount('/mnt')
 
201
 
 
202
    _dir = os.stat(data_src_dst)
 
203
    uid = _dir.st_uid
 
204
    gid = _dir.st_gid
 
205
 
 
206
    # re-mount where the data should originally be
 
207
    mount(blk_device, data_src_dst, persist=True)
 
208
 
 
209
    # ensure original ownership of new mount.
 
210
    cmd = ['chown', '-R', '%s:%s' % (uid, gid), data_src_dst]
 
211
    check_call(cmd)
 
212
 
 
213
 
 
214
# TODO: re-use
 
215
def modprobe_kernel_module(module):
 
216
    log('ceph: Loading kernel module', level=INFO)
 
217
    cmd = ['modprobe', module]
 
218
    check_call(cmd)
 
219
    cmd = 'echo %s >> /etc/modules' % module
 
220
    check_call(cmd, shell=True)
 
221
 
 
222
 
 
223
def copy_files(src, dst, symlinks=False, ignore=None):
 
224
    for item in os.listdir(src):
 
225
        s = os.path.join(src, item)
 
226
        d = os.path.join(dst, item)
 
227
        if os.path.isdir(s):
 
228
            shutil.copytree(s, d, symlinks, ignore)
 
229
        else:
 
230
            shutil.copy2(s, d)
 
231
 
 
232
 
 
233
def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
 
234
                        blk_device, fstype, system_services=[]):
 
235
    """
 
236
    To be called from the current cluster leader.
 
237
    Ensures given pool and RBD image exists, is mapped to a block device,
 
238
    and the device is formatted and mounted at the given mount_point.
 
239
 
 
240
    If formatting a device for the first time, data existing at mount_point
 
241
    will be migrated to the RBD device before being remounted.
 
242
 
 
243
    All services listed in system_services will be stopped prior to data
 
244
    migration and restarted when complete.
 
245
    """
 
246
    # Ensure pool, RBD image, RBD mappings are in place.
 
247
    if not pool_exists(service, pool):
 
248
        log('ceph: Creating new pool %s.' % pool, level=INFO)
 
249
        create_pool(service, pool)
 
250
 
 
251
    if not rbd_exists(service, pool, rbd_img):
 
252
        log('ceph: Creating RBD image (%s).' % rbd_img, level=INFO)
 
253
        create_rbd_image(service, pool, rbd_img, sizemb)
 
254
 
 
255
    if not image_mapped(rbd_img):
 
256
        log('ceph: Mapping RBD Image as a Block Device.', level=INFO)
 
257
        map_block_storage(service, pool, rbd_img)
 
258
 
 
259
    # make file system
 
260
    # TODO: What happens if for whatever reason this is run again and
 
261
    # the data is already in the rbd device and/or is mounted??
 
262
    # When it is mounted already, it will fail to make the fs
 
263
    # XXX: This is really sketchy!  Need to at least add an fstab entry
 
264
    #      otherwise this hook will blow away existing data if its executed
 
265
    #      after a reboot.
 
266
    if not filesystem_mounted(mount_point):
 
267
        make_filesystem(blk_device, fstype)
 
268
 
 
269
        for svc in system_services:
 
270
            if running(svc):
 
271
                log('Stopping services %s prior to migrating data.' % svc,
 
272
                    level=INFO)
 
273
                service_stop(svc)
 
274
 
 
275
        place_data_on_ceph(service, blk_device, mount_point, fstype)
 
276
 
 
277
        for svc in system_services:
 
278
            service_start(svc)