Revision 7181fba0 lib/bdev.py
b/lib/bdev.py | ||
---|---|---|
2346 | 2346 |
_ThrowError("Grow is not supported for PersistentBlockDev storage") |
2347 | 2347 |
|
2348 | 2348 |
|
2349 |
class RADOSBlockDevice(BlockDev): |
|
2350 |
"""A RADOS Block Device (rbd). |
|
2351 |
|
|
2352 |
This class implements the RADOS Block Device for the backend. You need |
|
2353 |
the rbd kernel driver, the RADOS Tools and a working RADOS cluster for |
|
2354 |
this to be functional. |
|
2355 |
|
|
2356 |
""" |
|
2357 |
def __init__(self, unique_id, children, size, params): |
|
2358 |
"""Attaches to an rbd device. |
|
2359 |
|
|
2360 |
""" |
|
2361 |
super(RADOSBlockDevice, self).__init__(unique_id, children, size, params) |
|
2362 |
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: |
|
2363 |
raise ValueError("Invalid configuration data %s" % str(unique_id)) |
|
2364 |
|
|
2365 |
self.driver, self.rbd_name = unique_id |
|
2366 |
|
|
2367 |
self.major = self.minor = None |
|
2368 |
self.Attach() |
|
2369 |
|
|
2370 |
@classmethod |
|
2371 |
def Create(cls, unique_id, children, size, params): |
|
2372 |
"""Create a new rbd device. |
|
2373 |
|
|
2374 |
Provision a new rbd volume inside a RADOS pool. |
|
2375 |
|
|
2376 |
""" |
|
2377 |
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: |
|
2378 |
raise errors.ProgrammerError("Invalid configuration data %s" % |
|
2379 |
str(unique_id)) |
|
2380 |
rbd_pool = params[constants.LDP_POOL] |
|
2381 |
rbd_name = unique_id[1] |
|
2382 |
|
|
2383 |
# Provision a new rbd volume (Image) inside the RADOS cluster. |
|
2384 |
cmd = [constants.RBD_CMD, "create", "-p", rbd_pool, |
|
2385 |
rbd_name, "--size", "%s" % size] |
|
2386 |
result = utils.RunCmd(cmd) |
|
2387 |
if result.failed: |
|
2388 |
_ThrowError("rbd creation failed (%s): %s", |
|
2389 |
result.fail_reason, result.output) |
|
2390 |
|
|
2391 |
return RADOSBlockDevice(unique_id, children, size, params) |
|
2392 |
|
|
2393 |
def Remove(self): |
|
2394 |
"""Remove the rbd device. |
|
2395 |
|
|
2396 |
""" |
|
2397 |
rbd_pool = self.params[constants.LDP_POOL] |
|
2398 |
rbd_name = self.unique_id[1] |
|
2399 |
|
|
2400 |
if not self.minor and not self.Attach(): |
|
2401 |
# The rbd device doesn't exist. |
|
2402 |
return |
|
2403 |
|
|
2404 |
# First shutdown the device (remove mappings). |
|
2405 |
self.Shutdown() |
|
2406 |
|
|
2407 |
# Remove the actual Volume (Image) from the RADOS cluster. |
|
2408 |
cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name] |
|
2409 |
result = utils.RunCmd(cmd) |
|
2410 |
if result.failed: |
|
2411 |
_ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s", |
|
2412 |
result.fail_reason, result.output) |
|
2413 |
|
|
2414 |
def Rename(self, new_id): |
|
2415 |
"""Rename this device. |
|
2416 |
|
|
2417 |
""" |
|
2418 |
pass |
|
2419 |
|
|
2420 |
def Attach(self): |
|
2421 |
"""Attach to an existing rbd device. |
|
2422 |
|
|
2423 |
This method maps the rbd volume that matches our name with |
|
2424 |
an rbd device and then attaches to this device. |
|
2425 |
|
|
2426 |
""" |
|
2427 |
self.attached = False |
|
2428 |
|
|
2429 |
# Map the rbd volume to a block device under /dev |
|
2430 |
self.dev_path = self._MapVolumeToBlockdev(self.unique_id) |
|
2431 |
|
|
2432 |
try: |
|
2433 |
st = os.stat(self.dev_path) |
|
2434 |
except OSError, err: |
|
2435 |
logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) |
|
2436 |
return False |
|
2437 |
|
|
2438 |
if not stat.S_ISBLK(st.st_mode): |
|
2439 |
logging.error("%s is not a block device", self.dev_path) |
|
2440 |
return False |
|
2441 |
|
|
2442 |
self.major = os.major(st.st_rdev) |
|
2443 |
self.minor = os.minor(st.st_rdev) |
|
2444 |
self.attached = True |
|
2445 |
|
|
2446 |
return True |
|
2447 |
|
|
2448 |
def _MapVolumeToBlockdev(self, unique_id): |
|
2449 |
"""Maps existing rbd volumes to block devices. |
|
2450 |
|
|
2451 |
This method should be idempotent if the mapping already exists. |
|
2452 |
|
|
2453 |
@rtype: string |
|
2454 |
@return: the block device path that corresponds to the volume |
|
2455 |
|
|
2456 |
""" |
|
2457 |
pool = self.params[constants.LDP_POOL] |
|
2458 |
name = unique_id[1] |
|
2459 |
|
|
2460 |
# Check if the mapping already exists. |
|
2461 |
showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool] |
|
2462 |
result = utils.RunCmd(showmap_cmd) |
|
2463 |
if result.failed: |
|
2464 |
_ThrowError("rbd showmapped failed (%s): %s", |
|
2465 |
result.fail_reason, result.output) |
|
2466 |
|
|
2467 |
rbd_dev = self._ParseRbdShowmappedOutput(result.output, name) |
|
2468 |
|
|
2469 |
if rbd_dev: |
|
2470 |
# The mapping exists. Return it. |
|
2471 |
return rbd_dev |
|
2472 |
|
|
2473 |
# The mapping doesn't exist. Create it. |
|
2474 |
map_cmd = [constants.RBD_CMD, "map", "-p", pool, name] |
|
2475 |
result = utils.RunCmd(map_cmd) |
|
2476 |
if result.failed: |
|
2477 |
_ThrowError("rbd map failed (%s): %s", |
|
2478 |
result.fail_reason, result.output) |
|
2479 |
|
|
2480 |
# Find the corresponding rbd device. |
|
2481 |
showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool] |
|
2482 |
result = utils.RunCmd(showmap_cmd) |
|
2483 |
if result.failed: |
|
2484 |
_ThrowError("rbd map succeeded, but showmapped failed (%s): %s", |
|
2485 |
result.fail_reason, result.output) |
|
2486 |
|
|
2487 |
rbd_dev = self._ParseRbdShowmappedOutput(result.output, name) |
|
2488 |
|
|
2489 |
if not rbd_dev: |
|
2490 |
_ThrowError("rbd map succeeded, but could not find the rbd block" |
|
2491 |
" device in output of showmapped, for volume: %s", name) |
|
2492 |
|
|
2493 |
# The device was successfully mapped. Return it. |
|
2494 |
return rbd_dev |
|
2495 |
|
|
2496 |
@staticmethod |
|
2497 |
def _ParseRbdShowmappedOutput(output, volume_name): |
|
2498 |
"""Parse the output of `rbd showmapped'. |
|
2499 |
|
|
2500 |
This method parses the output of `rbd showmapped' and returns |
|
2501 |
the rbd block device path (e.g. /dev/rbd0) that matches the |
|
2502 |
given rbd volume. |
|
2503 |
|
|
2504 |
@type output: string |
|
2505 |
@param output: the whole output of `rbd showmapped' |
|
2506 |
@type volume_name: string |
|
2507 |
@param volume_name: the name of the volume whose device we search for |
|
2508 |
@rtype: string or None |
|
2509 |
@return: block device path if the volume is mapped, else None |
|
2510 |
|
|
2511 |
""" |
|
2512 |
allfields = 5 |
|
2513 |
volumefield = 2 |
|
2514 |
devicefield = 4 |
|
2515 |
|
|
2516 |
field_sep = "\t" |
|
2517 |
|
|
2518 |
lines = output.splitlines() |
|
2519 |
splitted_lines = map(lambda l: l.split(field_sep), lines) |
|
2520 |
|
|
2521 |
# Check empty output. |
|
2522 |
if not splitted_lines: |
|
2523 |
_ThrowError("rbd showmapped returned empty output") |
|
2524 |
|
|
2525 |
# Check showmapped header line, to determine number of fields. |
|
2526 |
field_cnt = len(splitted_lines[0]) |
|
2527 |
if field_cnt != allfields: |
|
2528 |
_ThrowError("Cannot parse rbd showmapped output because its format" |
|
2529 |
" seems to have changed; expected %s fields, found %s", |
|
2530 |
allfields, field_cnt) |
|
2531 |
|
|
2532 |
matched_lines = \ |
|
2533 |
filter(lambda l: len(l) == allfields and l[volumefield] == volume_name, |
|
2534 |
splitted_lines) |
|
2535 |
|
|
2536 |
if len(matched_lines) > 1: |
|
2537 |
_ThrowError("The rbd volume %s is mapped more than once." |
|
2538 |
" This shouldn't happen, try to unmap the extra" |
|
2539 |
" devices manually.", volume_name) |
|
2540 |
|
|
2541 |
if matched_lines: |
|
2542 |
# rbd block device found. Return it. |
|
2543 |
rbd_dev = matched_lines[0][devicefield] |
|
2544 |
return rbd_dev |
|
2545 |
|
|
2546 |
# The given volume is not mapped. |
|
2547 |
return None |
|
2548 |
|
|
2549 |
def Assemble(self): |
|
2550 |
"""Assemble the device. |
|
2551 |
|
|
2552 |
""" |
|
2553 |
pass |
|
2554 |
|
|
2555 |
def Shutdown(self): |
|
2556 |
"""Shutdown the device. |
|
2557 |
|
|
2558 |
""" |
|
2559 |
if not self.minor and not self.Attach(): |
|
2560 |
# The rbd device doesn't exist. |
|
2561 |
return |
|
2562 |
|
|
2563 |
# Unmap the block device from the Volume. |
|
2564 |
self._UnmapVolumeFromBlockdev(self.unique_id) |
|
2565 |
|
|
2566 |
self.minor = None |
|
2567 |
self.dev_path = None |
|
2568 |
|
|
2569 |
def _UnmapVolumeFromBlockdev(self, unique_id): |
|
2570 |
"""Unmaps the rbd device from the Volume it is mapped. |
|
2571 |
|
|
2572 |
Unmaps the rbd device from the Volume it was previously mapped to. |
|
2573 |
This method should be idempotent if the Volume isn't mapped. |
|
2574 |
|
|
2575 |
""" |
|
2576 |
pool = self.params[constants.LDP_POOL] |
|
2577 |
name = unique_id[1] |
|
2578 |
|
|
2579 |
# Check if the mapping already exists. |
|
2580 |
showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool] |
|
2581 |
result = utils.RunCmd(showmap_cmd) |
|
2582 |
if result.failed: |
|
2583 |
_ThrowError("rbd showmapped failed [during unmap](%s): %s", |
|
2584 |
result.fail_reason, result.output) |
|
2585 |
|
|
2586 |
rbd_dev = self._ParseRbdShowmappedOutput(result.output, name) |
|
2587 |
|
|
2588 |
if rbd_dev: |
|
2589 |
# The mapping exists. Unmap the rbd device. |
|
2590 |
unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev] |
|
2591 |
result = utils.RunCmd(unmap_cmd) |
|
2592 |
if result.failed: |
|
2593 |
_ThrowError("rbd unmap failed (%s): %s", |
|
2594 |
result.fail_reason, result.output) |
|
2595 |
|
|
2596 |
def Open(self, force=False): |
|
2597 |
"""Make the device ready for I/O. |
|
2598 |
|
|
2599 |
""" |
|
2600 |
pass |
|
2601 |
|
|
2602 |
def Close(self): |
|
2603 |
"""Notifies that the device will no longer be used for I/O. |
|
2604 |
|
|
2605 |
""" |
|
2606 |
pass |
|
2607 |
|
|
2608 |
def Grow(self, amount, dryrun): |
|
2609 |
"""Grow the Volume. |
|
2610 |
|
|
2611 |
@type amount: integer |
|
2612 |
@param amount: the amount (in mebibytes) to grow with |
|
2613 |
@type dryrun: boolean |
|
2614 |
@param dryrun: whether to execute the operation in simulation mode |
|
2615 |
only, without actually increasing the size |
|
2616 |
|
|
2617 |
""" |
|
2618 |
if not self.Attach(): |
|
2619 |
_ThrowError("Can't attach to rbd device during Grow()") |
|
2620 |
|
|
2621 |
if dryrun: |
|
2622 |
# the rbd tool does not support dry runs of resize operations. |
|
2623 |
# Since rbd volumes are thinly provisioned, we assume |
|
2624 |
# there is always enough free space for the operation. |
|
2625 |
return |
|
2626 |
|
|
2627 |
rbd_pool = self.params[constants.LDP_POOL] |
|
2628 |
rbd_name = self.unique_id[1] |
|
2629 |
new_size = self.size + amount |
|
2630 |
|
|
2631 |
# Resize the rbd volume (Image) inside the RADOS cluster. |
|
2632 |
cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool, |
|
2633 |
rbd_name, "--size", "%s" % new_size] |
|
2634 |
result = utils.RunCmd(cmd) |
|
2635 |
if result.failed: |
|
2636 |
_ThrowError("rbd resize failed (%s): %s", |
|
2637 |
result.fail_reason, result.output) |
|
2638 |
|
|
2639 |
|
|
2349 | 2640 |
DEV_MAP = { |
2350 | 2641 |
constants.LD_LV: LogicalVolume, |
2351 | 2642 |
constants.LD_DRBD8: DRBD8, |
2352 | 2643 |
constants.LD_BLOCKDEV: PersistentBlockDevice, |
2644 |
constants.LD_RBD: RADOSBlockDevice, |
|
2353 | 2645 |
} |
2354 | 2646 |
|
2355 | 2647 |
if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE: |
Also available in: Unified diff