~corey.bryant/charms/trusty/ceilometer/git-ods

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/storage/linux/ceph.py

  • Committer: james.page at ubuntu
  • Date: 2014-12-15 09:22:07 UTC
  • mfrom: (57.1.8 ceilometer)
  • Revision ID: james.page@ubuntu.com-20141215092207-fe1sx9p4izbdyw61
[corey.bryant,r=james-page] Sort out charmhelpers issues.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
from subprocess import (
17
17
    check_call,
18
18
    check_output,
19
 
    CalledProcessError
 
19
    CalledProcessError,
20
20
)
21
 
 
22
21
from charmhelpers.core.hookenv import (
23
22
    relation_get,
24
23
    relation_ids,
25
24
    related_units,
26
25
    log,
 
26
    DEBUG,
27
27
    INFO,
28
28
    WARNING,
29
 
    ERROR
 
29
    ERROR,
30
30
)
31
 
 
32
31
from charmhelpers.core.host import (
33
32
    mount,
34
33
    mounts,
37
36
    service_running,
38
37
    umount,
39
38
)
40
 
 
41
39
from charmhelpers.fetch import (
42
40
    apt_install,
43
41
)
56
54
 
57
55
 
58
56
def install():
59
 
    ''' Basic Ceph client installation '''
 
57
    """Basic Ceph client installation."""
60
58
    ceph_dir = "/etc/ceph"
61
59
    if not os.path.exists(ceph_dir):
62
60
        os.mkdir(ceph_dir)
 
61
 
63
62
    apt_install('ceph-common', fatal=True)
64
63
 
65
64
 
66
65
def rbd_exists(service, pool, rbd_img):
67
 
    ''' Check to see if a RADOS block device exists '''
 
66
    """Check to see if a RADOS block device exists."""
68
67
    try:
69
 
        out = check_output(['rbd', 'list', '--id', service,
70
 
                            '--pool', pool])
 
68
        out = check_output(['rbd', 'list', '--id',
 
69
                            service, '--pool', pool]).decode('UTF-8')
71
70
    except CalledProcessError:
72
71
        return False
73
 
    else:
74
 
        return rbd_img in out
 
72
 
 
73
    return rbd_img in out
75
74
 
76
75
 
77
76
def create_rbd_image(service, pool, image, sizemb):
78
 
    ''' Create a new RADOS block device '''
79
 
    cmd = [
80
 
        'rbd',
81
 
        'create',
82
 
        image,
83
 
        '--size',
84
 
        str(sizemb),
85
 
        '--id',
86
 
        service,
87
 
        '--pool',
88
 
        pool
89
 
    ]
 
77
    """Create a new RADOS block device."""
 
78
    cmd = ['rbd', 'create', image, '--size', str(sizemb), '--id', service,
 
79
           '--pool', pool]
90
80
    check_call(cmd)
91
81
 
92
82
 
93
83
def pool_exists(service, name):
94
 
    ''' Check to see if a RADOS pool already exists '''
 
84
    """Check to see if a RADOS pool already exists."""
95
85
    try:
96
 
        out = check_output(['rados', '--id', service, 'lspools'])
 
86
        out = check_output(['rados', '--id', service,
 
87
                            'lspools']).decode('UTF-8')
97
88
    except CalledProcessError:
98
89
        return False
99
 
    else:
100
 
        return name in out
 
90
 
 
91
    return name in out
101
92
 
102
93
 
103
94
def get_osds(service):
104
 
    '''
105
 
    Return a list of all Ceph Object Storage Daemons
106
 
    currently in the cluster
107
 
    '''
 
95
    """Return a list of all Ceph Object Storage Daemons currently in the
 
96
    cluster.
 
97
    """
108
98
    version = ceph_version()
109
99
    if version and version >= '0.56':
110
100
        return json.loads(check_output(['ceph', '--id', service,
111
 
                                        'osd', 'ls', '--format=json']))
112
 
    else:
113
 
        return None
 
101
                                        'osd', 'ls',
 
102
                                        '--format=json']).decode('UTF-8'))
 
103
 
 
104
    return None
114
105
 
115
106
 
116
107
def create_pool(service, name, replicas=3):
117
 
    ''' Create a new RADOS pool '''
 
108
    """Create a new RADOS pool."""
118
109
    if pool_exists(service, name):
119
110
        log("Ceph pool {} already exists, skipping creation".format(name),
120
111
            level=WARNING)
121
112
        return
 
113
 
122
114
    # Calculate the number of placement groups based
123
115
    # on upstream recommended best practices.
124
116
    osds = get_osds(service)
125
117
    if osds:
126
 
        pgnum = (len(osds) * 100 / replicas)
 
118
        pgnum = (len(osds) * 100 // replicas)
127
119
    else:
128
120
        # NOTE(james-page): Default to 200 for older ceph versions
129
121
        # which don't support OSD query from cli
130
122
        pgnum = 200
131
 
    cmd = [
132
 
        'ceph', '--id', service,
133
 
        'osd', 'pool', 'create',
134
 
        name, str(pgnum)
135
 
    ]
 
123
 
 
124
    cmd = ['ceph', '--id', service, 'osd', 'pool', 'create', name, str(pgnum)]
136
125
    check_call(cmd)
137
 
    cmd = [
138
 
        'ceph', '--id', service,
139
 
        'osd', 'pool', 'set', name,
140
 
        'size', str(replicas)
141
 
    ]
 
126
 
 
127
    cmd = ['ceph', '--id', service, 'osd', 'pool', 'set', name, 'size',
 
128
           str(replicas)]
142
129
    check_call(cmd)
143
130
 
144
131
 
145
132
def delete_pool(service, name):
146
 
    ''' Delete a RADOS pool from ceph '''
147
 
    cmd = [
148
 
        'ceph', '--id', service,
149
 
        'osd', 'pool', 'delete',
150
 
        name, '--yes-i-really-really-mean-it'
151
 
    ]
 
133
    """Delete a RADOS pool from ceph."""
 
134
    cmd = ['ceph', '--id', service, 'osd', 'pool', 'delete', name,
 
135
           '--yes-i-really-really-mean-it']
152
136
    check_call(cmd)
153
137
 
154
138
 
161
145
 
162
146
 
163
147
def create_keyring(service, key):
164
 
    ''' Create a new Ceph keyring containing key'''
 
148
    """Create a new Ceph keyring containing key."""
165
149
    keyring = _keyring_path(service)
166
150
    if os.path.exists(keyring):
167
 
        log('ceph: Keyring exists at %s.' % keyring, level=WARNING)
 
151
        log('Ceph keyring exists at %s.' % keyring, level=WARNING)
168
152
        return
169
 
    cmd = [
170
 
        'ceph-authtool',
171
 
        keyring,
172
 
        '--create-keyring',
173
 
        '--name=client.{}'.format(service),
174
 
        '--add-key={}'.format(key)
175
 
    ]
 
153
 
 
154
    cmd = ['ceph-authtool', keyring, '--create-keyring',
 
155
           '--name=client.{}'.format(service), '--add-key={}'.format(key)]
176
156
    check_call(cmd)
177
 
    log('ceph: Created new ring at %s.' % keyring, level=INFO)
 
157
    log('Created new ceph keyring at %s.' % keyring, level=DEBUG)
178
158
 
179
159
 
180
160
def create_key_file(service, key):
181
 
    ''' Create a file containing key '''
 
161
    """Create a file containing key."""
182
162
    keyfile = _keyfile_path(service)
183
163
    if os.path.exists(keyfile):
184
 
        log('ceph: Keyfile exists at %s.' % keyfile, level=WARNING)
 
164
        log('Keyfile exists at %s.' % keyfile, level=WARNING)
185
165
        return
 
166
 
186
167
    with open(keyfile, 'w') as fd:
187
168
        fd.write(key)
188
 
    log('ceph: Created new keyfile at %s.' % keyfile, level=INFO)
 
169
 
 
170
    log('Created new keyfile at %s.' % keyfile, level=INFO)
189
171
 
190
172
 
191
173
def get_ceph_nodes():
192
 
    ''' Query named relation 'ceph' to detemine current nodes '''
 
174
    """Query named relation 'ceph' to determine current nodes."""
193
175
    hosts = []
194
176
    for r_id in relation_ids('ceph'):
195
177
        for unit in related_units(r_id):
196
178
            hosts.append(relation_get('private-address', unit=unit, rid=r_id))
 
179
 
197
180
    return hosts
198
181
 
199
182
 
200
183
def configure(service, key, auth, use_syslog):
201
 
    ''' Perform basic configuration of Ceph '''
 
184
    """Perform basic configuration of Ceph."""
202
185
    create_keyring(service, key)
203
186
    create_key_file(service, key)
204
187
    hosts = get_ceph_nodes()
211
194
 
212
195
 
213
196
def image_mapped(name):
214
 
    ''' Determine whether a RADOS block device is mapped locally '''
 
197
    """Determine whether a RADOS block device is mapped locally."""
215
198
    try:
216
 
        out = check_output(['rbd', 'showmapped'])
 
199
        out = check_output(['rbd', 'showmapped']).decode('UTF-8')
217
200
    except CalledProcessError:
218
201
        return False
219
 
    else:
220
 
        return name in out
 
202
 
 
203
    return name in out
221
204
 
222
205
 
223
206
def map_block_storage(service, pool, image):
224
 
    ''' Map a RADOS block device for local use '''
 
207
    """Map a RADOS block device for local use."""
225
208
    cmd = [
226
209
        'rbd',
227
210
        'map',
235
218
 
236
219
 
237
220
def filesystem_mounted(fs):
238
 
    ''' Determine whether a filesytems is already mounted '''
 
221
    """Determine whether a filesytems is already mounted."""
239
222
    return fs in [f for f, m in mounts()]
240
223
 
241
224
 
242
225
def make_filesystem(blk_device, fstype='ext4', timeout=10):
243
 
    ''' Make a new filesystem on the specified block device '''
 
226
    """Make a new filesystem on the specified block device."""
244
227
    count = 0
245
228
    e_noent = os.errno.ENOENT
246
229
    while not os.path.exists(blk_device):
247
230
        if count >= timeout:
248
 
            log('ceph: gave up waiting on block device %s' % blk_device,
 
231
            log('Gave up waiting on block device %s' % blk_device,
249
232
                level=ERROR)
250
233
            raise IOError(e_noent, os.strerror(e_noent), blk_device)
251
 
        log('ceph: waiting for block device %s to appear' % blk_device,
252
 
            level=INFO)
 
234
 
 
235
        log('Waiting for block device %s to appear' % blk_device,
 
236
            level=DEBUG)
253
237
        count += 1
254
238
        time.sleep(1)
255
239
    else:
256
 
        log('ceph: Formatting block device %s as filesystem %s.' %
 
240
        log('Formatting block device %s as filesystem %s.' %
257
241
            (blk_device, fstype), level=INFO)
258
242
        check_call(['mkfs', '-t', fstype, blk_device])
259
243
 
260
244
 
261
245
def place_data_on_block_device(blk_device, data_src_dst):
262
 
    ''' Migrate data in data_src_dst to blk_device and then remount '''
 
246
    """Migrate data in data_src_dst to blk_device and then remount."""
263
247
    # mount block device into /mnt
264
248
    mount(blk_device, '/mnt')
265
249
    # copy data to /mnt
279
263
 
280
264
# TODO: re-use
281
265
def modprobe(module):
282
 
    ''' Load a kernel module and configure for auto-load on reboot '''
283
 
    log('ceph: Loading kernel module', level=INFO)
 
266
    """Load a kernel module and configure for auto-load on reboot."""
 
267
    log('Loading kernel module', level=INFO)
284
268
    cmd = ['modprobe', module]
285
269
    check_call(cmd)
286
270
    with open('/etc/modules', 'r+') as modules:
289
273
 
290
274
 
291
275
def copy_files(src, dst, symlinks=False, ignore=None):
292
 
    ''' Copy files from src to dst '''
 
276
    """Copy files from src to dst."""
293
277
    for item in os.listdir(src):
294
278
        s = os.path.join(src, item)
295
279
        d = os.path.join(dst, item)
302
286
def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
303
287
                        blk_device, fstype, system_services=[],
304
288
                        replicas=3):
305
 
    """
306
 
    NOTE: This function must only be called from a single service unit for
 
289
    """NOTE: This function must only be called from a single service unit for
307
290
    the same rbd_img otherwise data loss will occur.
308
291
 
309
292
    Ensures given pool and RBD image exists, is mapped to a block device,
317
300
    """
318
301
    # Ensure pool, RBD image, RBD mappings are in place.
319
302
    if not pool_exists(service, pool):
320
 
        log('ceph: Creating new pool {}.'.format(pool))
 
303
        log('Creating new pool {}.'.format(pool), level=INFO)
321
304
        create_pool(service, pool, replicas=replicas)
322
305
 
323
306
    if not rbd_exists(service, pool, rbd_img):
324
 
        log('ceph: Creating RBD image ({}).'.format(rbd_img))
 
307
        log('Creating RBD image ({}).'.format(rbd_img), level=INFO)
325
308
        create_rbd_image(service, pool, rbd_img, sizemb)
326
309
 
327
310
    if not image_mapped(rbd_img):
328
 
        log('ceph: Mapping RBD Image {} as a Block Device.'.format(rbd_img))
 
311
        log('Mapping RBD Image {} as a Block Device.'.format(rbd_img),
 
312
            level=INFO)
329
313
        map_block_storage(service, pool, rbd_img)
330
314
 
331
315
    # make file system
340
324
 
341
325
        for svc in system_services:
342
326
            if service_running(svc):
343
 
                log('ceph: Stopping services {} prior to migrating data.'
344
 
                    .format(svc))
 
327
                log('Stopping services {} prior to migrating data.'
 
328
                    .format(svc), level=DEBUG)
345
329
                service_stop(svc)
346
330
 
347
331
        place_data_on_block_device(blk_device, mount_point)
348
332
 
349
333
        for svc in system_services:
350
 
            log('ceph: Starting service {} after migrating data.'
351
 
                .format(svc))
 
334
            log('Starting service {} after migrating data.'
 
335
                .format(svc), level=DEBUG)
352
336
            service_start(svc)
353
337
 
354
338
 
355
339
def ensure_ceph_keyring(service, user=None, group=None):
356
 
    '''
357
 
    Ensures a ceph keyring is created for a named service
358
 
    and optionally ensures user and group ownership.
 
340
    """Ensures a ceph keyring is created for a named service and optionally
 
341
    ensures user and group ownership.
359
342
 
360
343
    Returns False if no ceph key is available in relation state.
361
 
    '''
 
344
    """
362
345
    key = None
363
346
    for rid in relation_ids('ceph'):
364
347
        for unit in related_units(rid):
365
348
            key = relation_get('key', rid=rid, unit=unit)
366
349
            if key:
367
350
                break
 
351
 
368
352
    if not key:
369
353
        return False
 
354
 
370
355
    create_keyring(service=service, key=key)
371
356
    keyring = _keyring_path(service)
372
357
    if user and group:
373
358
        check_call(['chown', '%s.%s' % (user, group), keyring])
 
359
 
374
360
    return True
375
361
 
376
362
 
377
363
def ceph_version():
378
 
    ''' Retrieve the local version of ceph '''
 
364
    """Retrieve the local version of ceph."""
379
365
    if os.path.exists('/usr/bin/ceph'):
380
366
        cmd = ['ceph', '-v']
381
 
        output = check_output(cmd)
 
367
        output = check_output(cmd).decode('US-ASCII')
382
368
        output = output.split()
383
369
        if len(output) > 3:
384
370
            return output[2]