'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 + + """ + # 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 + + + @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) matched_lines = \ filter(lambda l: len(l) == allfields and l[volumefield] == volume_name, -- 1.7.10.4
