~ionutbalutoiu/charms/trusty/neutron-api/next

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2014-06-24 11:05:17 UTC
  • Revision ID: james.page@ubuntu.com-20140624110517-7chb3mwca2mtnmlp
Resync helpers, add standard targets to Makefile

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