On Wed, Jan 30, 2013 at 4:58 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]>
> ---
> Hi,
>
> this patch addresses the comments made in the thread, regarding 'rbd
> showmapped' output handling. This patch makes Ganeti compatible with current
> (bobtail), older (<=argonaut) and future versions of the ceph tools. :)
>
> Apparently, pylint has some trouble inferring the type of the
> simplejson.loads() result, so, for the moment,  I have disabled the check.
> I'm not sure if that's supposed to happen though.
>
> I also restructured the code a bit, to make it somewhat cleaner, after the
> changes in output handling.
>
> Thanks,
> Stratos
>
>  lib/bdev.py |   90 
> ++++++++++++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 68 insertions(+), 22 deletions(-)
>
> diff --git a/lib/bdev.py b/lib/bdev.py
> index b221235..cbcc04e 100644
> --- a/lib/bdev.py
> +++ b/lib/bdev.py
> @@ -39,6 +39,7 @@ from ganeti import compat
>  from ganeti import netutils
>  from ganeti import pathutils
>
> +import simplejson
>
>  # Size of reads in _CanReadDevice
>  _DEVICE_READ_SIZE = 128 * 1024
> @@ -2726,13 +2727,8 @@ 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.
> @@ -2746,14 +2742,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)
> @@ -2762,6 +2751,59 @@ class RADOSBlockDevice(BlockDev):
>      return rbd_dev
>
>    @staticmethod
> +  def _VolumeToBlockdev(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
> +

Missing rtype and return

> +    """
> +    # 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:
> +      # For older versions of rbd, we have to parse the 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)
> +
> +      rbd_dev = RADOSBlockDevice._ParseRbdShowmappedOutput(result.output,
> +              volume_name)
> +      return rbd_dev
> +
> +
> +    try:
> +      devices = simplejson.loads(result.output)
> +    except simplejson.JSONDecodeError:
> +      _ThrowError("Invalid json input %s!\n", result.output)
> +
> +    rbd_dev = None
> +    # pylint: disable=E1103
> +    for d in devices.itervalues():
> +      if "name" not in d:
> +        _ThrowError("'name' key missing from json object %s!\n",
> +                    devices)
> +
> +      if d["name"] == volume_name:
> +        if rbd_dev is not None:
> +          _ThrowError("The rbd volume %s is mapped more than once."
> +                      " This shouldn't happen, try to unmap the extra"
> +                      " devices manually.", volume_name)
> +
> +        rbd_dev = d["device"]
> +
> +    return rbd_dev
> +

This is actually quite confusing. Could you change it so that it's clearer?
Perhaps split it into two functions and then do something like:

try:
  return_ShowMapJson(...)
except Unsupported...:
  return _ShowMapBare(...)

Also, would there be a way to check for json support without calling it?
(eg. in --help?). This is because if json is supported but errors out,
falling back is not the right thing to do, right?




> +
> +  @staticmethod
>    def _ParseRbdShowmappedOutput(output, volume_name):
>      """Parse the output of `rbd showmapped'.
>
> @@ -2781,21 +2823,25 @@ 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.
>      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 because its format"
> +                    " seems to have changed; expected %s fields, found %s",
> +                    allfields, field_cnt)

How about we submit just this part as a separate patch for devel-2.7?
(or should it be both for that version?)

Thanks,

Guido

Reply via email to