~hopem/charms/trusty/cinder/charm-helpers-sync-precise-ipv6-haproxy

« 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:19:37 UTC
  • mfrom: (56.2.8 cinder)
  • Revision ID: james.page@ubuntu.com-20141215091937-lzxj8mr2b2gqz764
[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
114
 
 
115
 
 
116
 
def create_pool(service, name, replicas=2):
117
 
    ''' Create a new RADOS pool '''
 
101
                                        'osd', 'ls',
 
102
                                        '--format=json']).decode('UTF-8'))
 
103
 
 
104
    return None
 
105
 
 
106
 
 
107
def create_pool(service, name, replicas=3):
 
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)
300
284
 
301
285
 
302
286
def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
303
 
                        blk_device, fstype, system_services=[]):
304
 
    """
305
 
    NOTE: This function must only be called from a single service unit for
 
287
                        blk_device, fstype, system_services=[],
 
288
                        replicas=3):
 
289
    """NOTE: This function must only be called from a single service unit for
306
290
    the same rbd_img otherwise data loss will occur.
307
291
 
308
292
    Ensures given pool and RBD image exists, is mapped to a block device,
316
300
    """
317
301
    # Ensure pool, RBD image, RBD mappings are in place.
318
302
    if not pool_exists(service, pool):
319
 
        log('ceph: Creating new pool {}.'.format(pool))
320
 
        create_pool(service, pool)
 
303
        log('Creating new pool {}.'.format(pool), level=INFO)
 
304
        create_pool(service, pool, replicas=replicas)
321
305
 
322
306
    if not rbd_exists(service, pool, rbd_img):
323
 
        log('ceph: Creating RBD image ({}).'.format(rbd_img))
 
307
        log('Creating RBD image ({}).'.format(rbd_img), level=INFO)
324
308
        create_rbd_image(service, pool, rbd_img, sizemb)
325
309
 
326
310
    if not image_mapped(rbd_img):
327
 
        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)
328
313
        map_block_storage(service, pool, rbd_img)
329
314
 
330
315
    # make file system
339
324
 
340
325
        for svc in system_services:
341
326
            if service_running(svc):
342
 
                log('ceph: Stopping services {} prior to migrating data.'
343
 
                    .format(svc))
 
327
                log('Stopping services {} prior to migrating data.'
 
328
                    .format(svc), level=DEBUG)
344
329
                service_stop(svc)
345
330
 
346
331
        place_data_on_block_device(blk_device, mount_point)
347
332
 
348
333
        for svc in system_services:
349
 
            log('ceph: Starting service {} after migrating data.'
350
 
                .format(svc))
 
334
            log('Starting service {} after migrating data.'
 
335
                .format(svc), level=DEBUG)
351
336
            service_start(svc)
352
337
 
353
338
 
354
339
def ensure_ceph_keyring(service, user=None, group=None):
355
 
    '''
356
 
    Ensures a ceph keyring is created for a named service
357
 
    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.
358
342
 
359
343
    Returns False if no ceph key is available in relation state.
360
 
    '''
 
344
    """
361
345
    key = None
362
346
    for rid in relation_ids('ceph'):
363
347
        for unit in related_units(rid):
364
348
            key = relation_get('key', rid=rid, unit=unit)
365
349
            if key:
366
350
                break
 
351
 
367
352
    if not key:
368
353
        return False
 
354
 
369
355
    create_keyring(service=service, key=key)
370
356
    keyring = _keyring_path(service)
371
357
    if user and group:
372
358
        check_call(['chown', '%s.%s' % (user, group), keyring])
 
359
 
373
360
    return True
374
361
 
375
362
 
376
363
def ceph_version():
377
 
    ''' Retrieve the local version of ceph '''
 
364
    """Retrieve the local version of ceph."""
378
365
    if os.path.exists('/usr/bin/ceph'):
379
366
        cmd = ['ceph', '-v']
380
 
        output = check_output(cmd)
 
367
        output = check_output(cmd).decode('US-ASCII')
381
368
        output = output.split()
382
369
        if len(output) > 3:
383
370
            return output[2]