~arosales/charms/trusty/datameer/trunk

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/lib/ceph_utils.py

  • Committer: Antonio Rosales
  • Date: 2015-02-24 17:36:21 UTC
  • Revision ID: antonio.rosales@canonical.com-20150224173621-axjdwc682g82rdom
Create stub relations

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