On Fri, Feb 8, 2013 at 5:36 PM, Stratos Psomadakis <[email protected]> wrote:
> 'rbd showmapped' output formatting differs between older and newer versions of
> the ceph tools. Try to use json output formatting if available (currently
> available only in the ceph master branch). For bobtail, argonaut and older
> releases fallback to manually parsing the 'rbd showmapped' output, handling
> the
> differences in the output format for each rbd version correctly.
>
> Signed-off-by: Stratos Psomadakis <[email protected]>
LGTM
Thanks,
Guido
> ---
> lib/bdev.py | 140
> +++++++++++++++++++++++++++++++++++++++++++----------------
> 1 file changed, 102 insertions(+), 38 deletions(-)
>
> diff --git a/lib/bdev.py b/lib/bdev.py
> index b221235..5f63c2a 100644
> --- a/lib/bdev.py
> +++ b/lib/bdev.py
> @@ -38,11 +38,17 @@ from ganeti import objects
> from ganeti import compat
> from ganeti import netutils
> from ganeti import pathutils
> +from ganeti import serializer
>
>
> # Size of reads in _CanReadDevice
> _DEVICE_READ_SIZE = 128 * 1024
>
> +class RbdShowmappedJsonError(Exception):
> + """`rbd showmmapped' JSON formatting error Exception class.
> +
> + """
> + pass
>
> def _IgnoreError(fn, *args, **kwargs):
> """Executes the given function, ignoring BlockDeviceErrors.
> @@ -2726,14 +2732,7 @@ class RADOSBlockDevice(BlockDev):
> name = unique_id[1]
>
> # Check if the mapping already exists.
> - showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
> - result = utils.RunCmd(showmap_cmd)
> - if result.failed:
> - _ThrowError("rbd showmapped failed (%s): %s",
> - result.fail_reason, result.output)
> -
> - rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
> -
> + rbd_dev = self._VolumeToBlockdev(pool, name)
> if rbd_dev:
> # The mapping exists. Return it.
> return rbd_dev
> @@ -2746,14 +2745,7 @@ class RADOSBlockDevice(BlockDev):
> result.fail_reason, result.output)
>
> # Find the corresponding rbd device.
> - showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
> - result = utils.RunCmd(showmap_cmd)
> - if result.failed:
> - _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
> - result.fail_reason, result.output)
> -
> - rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
> -
> + rbd_dev = self._VolumeToBlockdev(pool, name)
> if not rbd_dev:
> _ThrowError("rbd map succeeded, but could not find the rbd block"
> " device in output of showmapped, for volume: %s", name)
> @@ -2761,16 +2753,93 @@ class RADOSBlockDevice(BlockDev):
> # The device was successfully mapped. Return it.
> return rbd_dev
>
> + @classmethod
> + def _VolumeToBlockdev(cls, pool, volume_name):
> + """Do the 'volume name'-to-'rbd block device' resolving.
> +
> + @type pool: string
> + @param pool: RADOS pool to use
> + @type volume_name: string
> + @param volume_name: the name of the volume whose device we search for
> + @rtype: string or None
> + @return: block device path if the volume is mapped, else None
> +
> + """
> + try:
> + # Newer versions of the rbd tool support json output formatting. Use it
> + # if available.
> + showmap_cmd = [
> + constants.RBD_CMD,
> + "showmapped",
> + "-p",
> + pool,
> + "--format",
> + "json"
> + ]
> + result = utils.RunCmd(showmap_cmd)
> + if result.failed:
> + logging.error("rbd JSON output formatting returned error (%s): %s,"
> + "falling back to plain output parsing",
> + result.fail_reason, result.output)
> + raise RbdShowmappedJsonError
> +
> + return cls._ParseRbdShowmappedJson(result.output, volume_name)
> + except RbdShowmappedJsonError:
> + # For older versions of rbd, we have to parse the plain / text output
> + # manually.
> + showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
> + result = utils.RunCmd(showmap_cmd)
> + if result.failed:
> + _ThrowError("rbd showmapped failed (%s): %s",
> + result.fail_reason, result.output)
> +
> + return cls._ParseRbdShowmappedPlain(result.output, volume_name)
> +
> @staticmethod
> - def _ParseRbdShowmappedOutput(output, volume_name):
> - """Parse the output of `rbd showmapped'.
> + def _ParseRbdShowmappedJson(output, volume_name):
> + """Parse the json output of `rbd showmapped'.
> +
> + This method parses the json output of `rbd showmapped' and returns the
> rbd
> + block device path (e.g. /dev/rbd0) that matches the given rbd volume.
> +
> + @type output: string
> + @param output: the json output of `rbd showmapped'
> + @type volume_name: string
> + @param volume_name: the name of the volume whose device we search for
> + @rtype: string or None
> + @return: block device path if the volume is mapped, else None
> +
> + """
> + try:
> + devices = serializer.LoadJson(output)
> + except ValueError, err:
> + _ThrowError("Unable to parse JSON data: %s" % err)
> +
> + rbd_dev = None
> + for d in devices.values(): # pylint: disable=E1103
> + try:
> + name = d["name"]
> + except KeyError:
> + _ThrowError("'name' key missing from json object %s", devices)
> +
> + if name == volume_name:
> + if rbd_dev is not None:
> + _ThrowError("rbd volume %s is mapped more than once", volume_name)
> +
> + rbd_dev = d["device"]
> +
> + return rbd_dev
> +
> + @staticmethod
> + def _ParseRbdShowmappedPlain(output, volume_name):
> + """Parse the (plain / text) output of `rbd showmapped'.
>
> This method parses the output of `rbd showmapped' and returns
> the rbd block device path (e.g. /dev/rbd0) that matches the
> given rbd volume.
>
> @type output: string
> - @param output: the whole output of `rbd showmapped'
> + @param output: the plain text output of `rbd showmapped'
> @type volume_name: string
> @param volume_name: the name of the volume whose device we search for
> @rtype: string or None
> @@ -2781,30 +2850,31 @@ class RADOSBlockDevice(BlockDev):
> volumefield = 2
> devicefield = 4
>
> - field_sep = "\t"
> -
> lines = output.splitlines()
> - splitted_lines = map(lambda l: l.split(field_sep), lines)
>
> - # Check empty output.
> + # Try parsing the new output format (ceph >= 0.55).
> + splitted_lines = map(lambda l: l.split(), lines)
> +
> + # Check for empty output.
> if not splitted_lines:
> - _ThrowError("rbd showmapped returned empty output")
> + return None
>
> - # Check showmapped header line, to determine number of fields.
> + # Check showmapped output, to determine number of fields.
> field_cnt = len(splitted_lines[0])
> if field_cnt != allfields:
> - _ThrowError("Cannot parse rbd showmapped output because its format"
> - " seems to have changed; expected %s fields, found %s",
> - allfields, field_cnt)
> + # Parsing the new format failed. Fallback to parsing the old output
> + # format (< 0.55).
> + splitted_lines = map(lambda l: l.split("\t"), lines)
> + if field_cnt != allfields:
> + _ThrowError("Cannot parse rbd showmapped output expected %s fields,"
> + " found %s", allfields, field_cnt)
>
> matched_lines = \
> filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
> splitted_lines)
>
> if len(matched_lines) > 1:
> - _ThrowError("The rbd volume %s is mapped more than once."
> - " This shouldn't happen, try to unmap the extra"
> - " devices manually.", volume_name)
> + _ThrowError("rbd volume %s mapped more than once", volume_name)
>
> if matched_lines:
> # rbd block device found. Return it.
> @@ -2845,13 +2915,7 @@ class RADOSBlockDevice(BlockDev):
> name = unique_id[1]
>
> # Check if the mapping already exists.
> - showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
> - result = utils.RunCmd(showmap_cmd)
> - if result.failed:
> - _ThrowError("rbd showmapped failed [during unmap](%s): %s",
> - result.fail_reason, result.output)
> -
> - rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
> + rbd_dev = self._VolumeToBlockdev(pool, name)
>
> if rbd_dev:
> # The mapping exists. Unmap the rbd device.
> --
> 1.7.10.4
>
--
Guido Trotter
Ganeti engineering
Google Germany