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