Revision bdecfea2

b/lib/bdev.py
38 38
from ganeti import compat
39 39
from ganeti import netutils
40 40
from ganeti import pathutils
41
from ganeti import serializer
41 42

  
42 43

  
43 44
# Size of reads in _CanReadDevice
44 45
_DEVICE_READ_SIZE = 128 * 1024
45 46

  
46 47

  
48
class RbdShowmappedJsonError(Exception):
49
  """`rbd showmmapped' JSON formatting error Exception class.
50

  
51
  """
52
  pass
53

  
54

  
47 55
def _IgnoreError(fn, *args, **kwargs):
48 56
  """Executes the given function, ignoring BlockDeviceErrors.
49 57

  
......
2726 2734
    name = unique_id[1]
2727 2735

  
2728 2736
    # Check if the mapping already exists.
2729
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2730
    result = utils.RunCmd(showmap_cmd)
2731
    if result.failed:
2732
      _ThrowError("rbd showmapped failed (%s): %s",
2733
                  result.fail_reason, result.output)
2734

  
2735
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2736

  
2737
    rbd_dev = self._VolumeToBlockdev(pool, name)
2737 2738
    if rbd_dev:
2738 2739
      # The mapping exists. Return it.
2739 2740
      return rbd_dev
......
2746 2747
                  result.fail_reason, result.output)
2747 2748

  
2748 2749
    # Find the corresponding rbd device.
2749
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2750
    result = utils.RunCmd(showmap_cmd)
2751
    if result.failed:
2752
      _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
2753
                  result.fail_reason, result.output)
2754

  
2755
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2756

  
2750
    rbd_dev = self._VolumeToBlockdev(pool, name)
2757 2751
    if not rbd_dev:
2758 2752
      _ThrowError("rbd map succeeded, but could not find the rbd block"
2759 2753
                  " device in output of showmapped, for volume: %s", name)
......
2761 2755
    # The device was successfully mapped. Return it.
2762 2756
    return rbd_dev
2763 2757

  
2758
  @classmethod
2759
  def _VolumeToBlockdev(cls, pool, volume_name):
2760
    """Do the 'volume name'-to-'rbd block device' resolving.
2761

  
2762
    @type pool: string
2763
    @param pool: RADOS pool to use
2764
    @type volume_name: string
2765
    @param volume_name: the name of the volume whose device we search for
2766
    @rtype: string or None
2767
    @return: block device path if the volume is mapped, else None
2768

  
2769
    """
2770
    try:
2771
      # Newer versions of the rbd tool support json output formatting. Use it
2772
      # if available.
2773
      showmap_cmd = [
2774
        constants.RBD_CMD,
2775
        "showmapped",
2776
        "-p",
2777
        pool,
2778
        "--format",
2779
        "json"
2780
        ]
2781
      result = utils.RunCmd(showmap_cmd)
2782
      if result.failed:
2783
        logging.error("rbd JSON output formatting returned error (%s): %s,"
2784
                      "falling back to plain output parsing",
2785
                      result.fail_reason, result.output)
2786
        raise RbdShowmappedJsonError
2787

  
2788
      return cls._ParseRbdShowmappedJson(result.output, volume_name)
2789
    except RbdShowmappedJsonError:
2790
      # For older versions of rbd, we have to parse the plain / text output
2791
      # manually.
2792
      showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2793
      result = utils.RunCmd(showmap_cmd)
2794
      if result.failed:
2795
        _ThrowError("rbd showmapped failed (%s): %s",
2796
                    result.fail_reason, result.output)
2797

  
2798
      return cls._ParseRbdShowmappedPlain(result.output, volume_name)
2799

  
2800
  @staticmethod
2801
  def _ParseRbdShowmappedJson(output, volume_name):
2802
    """Parse the json output of `rbd showmapped'.
2803

  
2804
    This method parses the json output of `rbd showmapped' and returns the rbd
2805
    block device path (e.g. /dev/rbd0) that matches the given rbd volume.
2806

  
2807
    @type output: string
2808
    @param output: the json output of `rbd showmapped'
2809
    @type volume_name: string
2810
    @param volume_name: the name of the volume whose device we search for
2811
    @rtype: string or None
2812
    @return: block device path if the volume is mapped, else None
2813

  
2814
    """
2815
    try:
2816
      devices = serializer.LoadJson(output)
2817
    except ValueError, err:
2818
      _ThrowError("Unable to parse JSON data: %s" % err)
2819

  
2820
    rbd_dev = None
2821
    for d in devices.values(): # pylint: disable=E1103
2822
      try:
2823
        name = d["name"]
2824
      except KeyError:
2825
        _ThrowError("'name' key missing from json object %s", devices)
2826

  
2827
      if name == volume_name:
2828
        if rbd_dev is not None:
2829
          _ThrowError("rbd volume %s is mapped more than once", volume_name)
2830

  
2831
        rbd_dev = d["device"]
2832

  
2833
    return rbd_dev
2834

  
2764 2835
  @staticmethod
2765
  def _ParseRbdShowmappedOutput(output, volume_name):
2766
    """Parse the output of `rbd showmapped'.
2836
  def _ParseRbdShowmappedPlain(output, volume_name):
2837
    """Parse the (plain / text) output of `rbd showmapped'.
2767 2838

  
2768 2839
    This method parses the output of `rbd showmapped' and returns
2769 2840
    the rbd block device path (e.g. /dev/rbd0) that matches the
2770 2841
    given rbd volume.
2771 2842

  
2772 2843
    @type output: string
2773
    @param output: the whole output of `rbd showmapped'
2844
    @param output: the plain text output of `rbd showmapped'
2774 2845
    @type volume_name: string
2775 2846
    @param volume_name: the name of the volume whose device we search for
2776 2847
    @rtype: string or None
......
2781 2852
    volumefield = 2
2782 2853
    devicefield = 4
2783 2854

  
2784
    field_sep = "\t"
2785

  
2786 2855
    lines = output.splitlines()
2787
    splitted_lines = map(lambda l: l.split(field_sep), lines)
2788 2856

  
2789
    # Check empty output.
2857
    # Try parsing the new output format (ceph >= 0.55).
2858
    splitted_lines = map(lambda l: l.split(), lines)
2859

  
2860
    # Check for empty output.
2790 2861
    if not splitted_lines:
2791
      _ThrowError("rbd showmapped returned empty output")
2862
      return None
2792 2863

  
2793
    # Check showmapped header line, to determine number of fields.
2864
    # Check showmapped output, to determine number of fields.
2794 2865
    field_cnt = len(splitted_lines[0])
2795 2866
    if field_cnt != allfields:
2796
      _ThrowError("Cannot parse rbd showmapped output because its format"
2797
                  " seems to have changed; expected %s fields, found %s",
2798
                  allfields, field_cnt)
2867
      # Parsing the new format failed. Fallback to parsing the old output
2868
      # format (< 0.55).
2869
      splitted_lines = map(lambda l: l.split("\t"), lines)
2870
      if field_cnt != allfields:
2871
        _ThrowError("Cannot parse rbd showmapped output expected %s fields,"
2872
                    " found %s", allfields, field_cnt)
2799 2873

  
2800 2874
    matched_lines = \
2801 2875
      filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
2802 2876
             splitted_lines)
2803 2877

  
2804 2878
    if len(matched_lines) > 1:
2805
      _ThrowError("The rbd volume %s is mapped more than once."
2806
                  " This shouldn't happen, try to unmap the extra"
2807
                  " devices manually.", volume_name)
2879
      _ThrowError("rbd volume %s mapped more than once", volume_name)
2808 2880

  
2809 2881
    if matched_lines:
2810 2882
      # rbd block device found. Return it.
......
2845 2917
    name = unique_id[1]
2846 2918

  
2847 2919
    # Check if the mapping already exists.
2848
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2849
    result = utils.RunCmd(showmap_cmd)
2850
    if result.failed:
2851
      _ThrowError("rbd showmapped failed [during unmap](%s): %s",
2852
                  result.fail_reason, result.output)
2853

  
2854
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2920
    rbd_dev = self._VolumeToBlockdev(pool, name)
2855 2921

  
2856 2922
    if rbd_dev:
2857 2923
      # The mapping exists. Unmap the rbd device.

Also available in: Unified diff