X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/b00b95dd917ffadc143d8a75cb05793d8fe6b9e9..29df1f02bbc588ace14f4a40e346aa502b686462:/lib/bdev.py diff --git a/lib/bdev.py b/lib/bdev.py index a9a7d19..c7d2a83 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -94,7 +94,6 @@ class BlockDev(object): STATUS_ONLINE: "online", } - def __init__(self, unique_id, children): self._children = children self.dev_path = None @@ -102,7 +101,6 @@ class BlockDev(object): self.major = None self.minor = None - def Assemble(self): """Assemble the device from its components. @@ -124,28 +122,31 @@ class BlockDev(object): status = status and child.Assemble() if not status: break - status = status and child.Open() + + try: + child.Open() + except errors.BlockDeviceError: + for child in self._children: + child.Shutdown() + raise if not status: for child in self._children: child.Shutdown() return status - def Attach(self): """Find a device which matches our config and attach to it. """ raise NotImplementedError - def Close(self): """Notifies that the device will no longer be used for I/O. """ raise NotImplementedError - @classmethod def Create(cls, unique_id, children, size): """Create the device. @@ -160,7 +161,6 @@ class BlockDev(object): """ raise NotImplementedError - def Remove(self): """Remove this device. @@ -171,7 +171,6 @@ class BlockDev(object): """ raise NotImplementedError - def Rename(self, new_id): """Rename this device. @@ -180,14 +179,12 @@ class BlockDev(object): """ raise NotImplementedError - def GetStatus(self): """Return the status of the device. """ raise NotImplementedError - def Open(self, force=False): """Make the device ready for use. @@ -200,7 +197,6 @@ class BlockDev(object): """ raise NotImplementedError - def Shutdown(self): """Shut down the device, freeing its children. @@ -211,7 +207,6 @@ class BlockDev(object): """ raise NotImplementedError - def SetSyncSpeed(self, speed): """Adjust the sync speed of the mirror. @@ -224,7 +219,6 @@ class BlockDev(object): result = result and child.SetSyncSpeed(speed) return result - def GetSyncStatus(self): """Returns the sync status of the device. @@ -232,17 +226,23 @@ class BlockDev(object): status of the mirror. Returns: - (sync_percent, estimated_time, is_degraded) + (sync_percent, estimated_time, is_degraded, ldisk) + + If sync_percent is None, it means the device is not syncing. - If sync_percent is None, it means all is ok If estimated_time is None, it means we can't estimate - the time needed, otherwise it's the time left in seconds + the time needed, otherwise it's the time left in seconds. + If is_degraded is True, it means the device is missing redundancy. This is usually a sign that something went wrong in the device setup, if sync_percent is None. + The ldisk parameter represents the degradation of the local + data. This is only valid for some devices, the rest will always + return False (not degraded). + """ - return None, None, False + return None, None, False, False def CombinedSyncStatus(self): @@ -253,10 +253,10 @@ class BlockDev(object): children. """ - min_percent, max_time, is_degraded = self.GetSyncStatus() + min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus() if self._children: for child in self._children: - c_percent, c_time, c_degraded = child.GetSyncStatus() + c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus() if min_percent is None: min_percent = c_percent elif c_percent is not None: @@ -266,7 +266,8 @@ class BlockDev(object): elif c_time is not None: max_time = max(max_time, c_time) is_degraded = is_degraded or c_degraded - return min_percent, max_time, is_degraded + ldisk = ldisk or c_ldisk + return min_percent, max_time, is_degraded, ldisk def SetInfo(self, text): @@ -302,7 +303,6 @@ class LogicalVolume(BlockDev): self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name) self.Attach() - @classmethod def Create(cls, unique_id, children, size): """Create a new logical volume. @@ -392,19 +392,21 @@ class LogicalVolume(BlockDev): if result.failed: raise errors.BlockDeviceError("Failed to rename the logical volume: %s" % result.output) + self._lv_name = new_name + self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name) def Attach(self): """Attach to an existing LV. This method will try to see if an existing and active LV exists - which matches the our name. If so, its major/minor will be + which matches our name. If so, its major/minor will be recorded. """ result = utils.RunCmd(["lvdisplay", self.dev_path]) if result.failed: - logger.Error("Can't find LV %s: %s" % - (self.dev_path, result.fail_reason)) + logger.Error("Can't find LV %s: %s, %s" % + (self.dev_path, result.fail_reason, result.output)) return False match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$") for line in result.stdout.splitlines(): @@ -415,16 +417,18 @@ class LogicalVolume(BlockDev): return True return False - def Assemble(self): """Assemble the device. - This is a no-op for the LV device type. Eventually, we could - lvchange -ay here if we see that the LV is not active. + We alway run `lvchange -ay` on the LV to ensure it's active before + use, as there were cases when xenvg was not active after boot + (also possibly after disk issues). """ - return True - + result = utils.RunCmd(["lvchange", "-ay", self.dev_path]) + if result.failed: + logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output)) + return not result.failed def Shutdown(self): """Shutdown the device. @@ -435,7 +439,6 @@ class LogicalVolume(BlockDev): """ return True - def GetStatus(self): """Return the status of the device. @@ -464,6 +467,39 @@ class LogicalVolume(BlockDev): return retval + def GetSyncStatus(self): + """Returns the sync status of the device. + + If this device is a mirroring device, this function returns the + status of the mirror. + + Returns: + (sync_percent, estimated_time, is_degraded, ldisk) + + For logical volumes, sync_percent and estimated_time are always + None (no recovery in progress, as we don't handle the mirrored LV + case). The is_degraded parameter is the inverse of the ldisk + parameter. + + For the ldisk parameter, we check if the logical volume has the + 'virtual' type, which means it's not backed by existing storage + anymore (read from it return I/O error). This happens after a + physical disk failure and subsequent 'vgreduce --removemissing' on + the volume group. + + """ + result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path]) + if result.failed: + logger.Error("Can't display lv: %s" % result.fail_reason) + return None, None, True, True + out = result.stdout.strip() + # format: type/permissions/alloc/fixed_minor/state/open + if len(out) != 6: + logger.Debug("Error in lvs output: attrs=%s, len != 6" % out) + return None, None, True, True + ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have + # backing storage + return None, None, ldisk, ldisk def Open(self, force=False): """Make the device ready for I/O. @@ -471,8 +507,7 @@ class LogicalVolume(BlockDev): This is a no-op for the LV device type. """ - return True - + pass def Close(self): """Notifies that the device will no longer be used for I/O. @@ -480,8 +515,7 @@ class LogicalVolume(BlockDev): This is a no-op for the LV device type. """ - return True - + pass def Snapshot(self, size): """Create a snapshot copy of an lvm block device. @@ -512,7 +546,6 @@ class LogicalVolume(BlockDev): return snap_name - def SetInfo(self, text): """Update metadata with info text. @@ -542,7 +575,6 @@ class MDRaid1(BlockDev): self.major = 9 self.Attach() - def Attach(self): """Find an array which matches our config and attach to it. @@ -558,7 +590,6 @@ class MDRaid1(BlockDev): return (minor is not None) - @staticmethod def _GetUsedDevs(): """Compute the list of in-use MD devices. @@ -581,7 +612,6 @@ class MDRaid1(BlockDev): return used_md - @staticmethod def _GetDevInfo(minor): """Get info about a MD device. @@ -604,7 +634,6 @@ class MDRaid1(BlockDev): retval["state"] = kv[1].split(", ") return retval - @staticmethod def _FindUnusedMinor(): """Compute an unused MD minor. @@ -623,7 +652,6 @@ class MDRaid1(BlockDev): raise errors.BlockDeviceError("Can't find a free MD minor") return i - @classmethod def _FindMDByUUID(cls, uuid): """Find the minor of an MD array with a given UUID. @@ -636,7 +664,6 @@ class MDRaid1(BlockDev): return minor return None - @staticmethod def _ZeroSuperblock(dev_path): """Zero the possible locations for an MD superblock. @@ -714,7 +741,6 @@ class MDRaid1(BlockDev): return None return MDRaid1(info["uuid"], children) - def Remove(self): """Stub remove function for MD RAID 1 arrays. @@ -756,7 +782,6 @@ class MDRaid1(BlockDev): result.output) self._children.extend(devices) - def RemoveChildren(self, devices): """Remove member(s) from the md raid1. @@ -769,9 +794,9 @@ class MDRaid1(BlockDev): args = ["mdadm", "-f", self.dev_path] orig_devs = [] for dev in devices: - args.append(dev.dev_path) + args.append(dev) for c in self._children: - if c.dev_path == dev.dev_path: + if c.dev_path == dev: orig_devs.append(c) break else: @@ -798,7 +823,6 @@ class MDRaid1(BlockDev): for dev in orig_devs: self._children.remove(dev) - def GetStatus(self): """Return the status of the device. @@ -810,7 +834,6 @@ class MDRaid1(BlockDev): retval = self.STATUS_ONLINE return retval - def _SetFromMinor(self, minor): """Set our parameters based on the given minor. @@ -820,7 +843,6 @@ class MDRaid1(BlockDev): self.minor = minor self.dev_path = "/dev/md%d" % minor - def Assemble(self): """Assemble the MD device. @@ -851,7 +873,6 @@ class MDRaid1(BlockDev): self.minor = free_minor return not result.failed - def Shutdown(self): """Tear down the MD array. @@ -871,7 +892,6 @@ class MDRaid1(BlockDev): self.dev_path = None return True - def SetSyncSpeed(self, kbytes): """Set the maximum sync speed for the MD array. @@ -892,16 +912,17 @@ class MDRaid1(BlockDev): f.close() return result - def GetSyncStatus(self): """Returns the sync status of the device. Returns: - (sync_percent, estimated_time) + (sync_percent, estimated_time, is_degraded, ldisk) If sync_percent is None, it means all is ok If estimated_time is None, it means we can't esimate - the time needed, otherwise it's the time left in seconds + the time needed, otherwise it's the time left in seconds. + + The ldisk parameter is always true for MD devices. """ if self.minor is None and not self.Attach(): @@ -915,12 +936,12 @@ class MDRaid1(BlockDev): sync_status = f.readline().strip() f.close() if sync_status == "idle": - return None, None, not is_clean + return None, None, not is_clean, False f = file(sys_path + "sync_completed") sync_completed = f.readline().strip().split(" / ") f.close() if len(sync_completed) != 2: - return 0, None, not is_clean + return 0, None, not is_clean, False sync_done, sync_total = [float(i) for i in sync_completed] sync_percent = 100.0*sync_done/sync_total f = file(sys_path + "sync_speed") @@ -929,8 +950,7 @@ class MDRaid1(BlockDev): time_est = None else: time_est = (sync_total - sync_done) / 2 / sync_speed_k - return sync_percent, time_est, not is_clean - + return sync_percent, time_est, not is_clean, False def Open(self, force=False): """Make the device ready for I/O. @@ -939,8 +959,7 @@ class MDRaid1(BlockDev): the 2.6.18's new array_state thing. """ - return True - + pass def Close(self): """Notifies that the device will no longer be used for I/O. @@ -949,7 +968,7 @@ class MDRaid1(BlockDev): `Open()`. """ - return True + pass class BaseDRBD(BlockDev): @@ -960,7 +979,8 @@ class BaseDRBD(BlockDev): """ _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)" - r" \(api:(\d+)/proto:(\d+)\)") + r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)") + _DRBD_MAJOR = 147 _ST_UNCONFIGURED = "Unconfigured" _ST_WFCONNECTION = "WFConnection" @@ -1010,7 +1030,13 @@ class BaseDRBD(BlockDev): def _GetVersion(cls): """Return the DRBD version. - This will return a list [k_major, k_minor, k_point, api, proto]. + This will return a dict with keys: + k_major, + k_minor, + k_point, + api, + proto, + proto2 (only on drbd > 8.2.X) """ proc_data = cls._GetProcData() @@ -1019,7 +1045,18 @@ class BaseDRBD(BlockDev): if not version: raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" % first_line) - return [int(val) for val in version.groups()] + + values = version.groups() + retval = {'k_major': int(values[0]), + 'k_minor': int(values[1]), + 'k_point': int(values[2]), + 'api': int(values[3]), + 'proto': int(values[4]), + } + if values[5] is not None: + retval['proto2'] = values[5] + + return retval @staticmethod def _DevPath(minor): @@ -1112,12 +1149,12 @@ class DRBDev(BaseDRBD): def __init__(self, unique_id, children): super(DRBDev, self).__init__(unique_id, children) self.major = self._DRBD_MAJOR - [kmaj, kmin, kfix, api, proto] = self._GetVersion() - if kmaj != 0 and kmin != 7: + version = self._GetVersion() + if version['k_major'] != 0 and version['k_minor'] != 7: raise errors.BlockDeviceError("Mismatch in DRBD kernel version and" " requested ganeti usage: kernel is" - " %s.%s, ganeti wants 0.7" % (kmaj, kmin)) - + " %s.%s, ganeti wants 0.7" % + (version['k_major'], version['k_minor'])) if len(children) != 2: raise ValueError("Invalid configuration data %s" % str(children)) if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4: @@ -1191,7 +1228,6 @@ class DRBDev(BaseDRBD): continue return data - def _MatchesLocal(self, info): """Test if our local config matches with an existing device. @@ -1219,7 +1255,6 @@ class DRBDev(BaseDRBD): info["meta_index"] == -1) return retval - def _MatchesNet(self, info): """Test if our network config matches with an existing device. @@ -1245,7 +1280,6 @@ class DRBDev(BaseDRBD): info["remote_addr"] == (self._rhost, self._rport)) return retval - @classmethod def _AssembleLocal(cls, minor, backend, meta): """Configure the local part of a DRBD device. @@ -1262,7 +1296,6 @@ class DRBDev(BaseDRBD): logger.Error("Can't attach local disk: %s" % result.output) return not result.failed - @classmethod def _ShutdownLocal(cls, minor): """Detach from the local device. @@ -1276,7 +1309,6 @@ class DRBDev(BaseDRBD): logger.Error("Can't detach local device: %s" % result.output) return not result.failed - @staticmethod def _ShutdownAll(minor): """Deactivate the device. @@ -1289,7 +1321,6 @@ class DRBDev(BaseDRBD): logger.Error("Can't shutdown drbd device: %s" % result.output) return not result.failed - @classmethod def _AssembleNet(cls, minor, net_info, protocol): """Configure the network part of the device. @@ -1329,7 +1360,6 @@ class DRBDev(BaseDRBD): return False return True - @classmethod def _ShutdownNet(cls, minor): """Disconnect from the remote peer. @@ -1338,10 +1368,10 @@ class DRBDev(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"]) - logger.Error("Can't shutdown network: %s" % result.output) + if result.failed: + logger.Error("Can't shutdown network: %s" % result.output) return not result.failed - def Assemble(self): """Assemble the drbd. @@ -1392,7 +1422,6 @@ class DRBDev(BaseDRBD): self._SetFromMinor(minor) return True - def Shutdown(self): """Shutdown the DRBD device. @@ -1406,7 +1435,6 @@ class DRBDev(BaseDRBD): self.dev_path = None return True - def Attach(self): """Find a DRBD device which matches our config and attach to it. @@ -1434,7 +1462,6 @@ class DRBDev(BaseDRBD): self._SetFromMinor(minor) return minor is not None - def Open(self, force=False): """Make the local state primary. @@ -1452,10 +1479,9 @@ class DRBDev(BaseDRBD): cmd.append("--do-what-I-say") result = utils.RunCmd(cmd) if result.failed: - logger.Error("Can't make drbd device primary: %s" % result.output) - return False - return True - + msg = ("Can't make drbd device primary: %s" % result.output) + logger.Error(msg) + raise errors.BlockDeviceError(msg) def Close(self): """Make the local state secondary. @@ -1468,9 +1494,10 @@ class DRBDev(BaseDRBD): raise errors.BlockDeviceError("Can't find device") result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"]) if result.failed: - logger.Error("Can't switch drbd device to secondary: %s" % result.output) - raise errors.BlockDeviceError("Can't switch drbd device to secondary") - + msg = ("Can't switch drbd device to" + " secondary: %s" % result.output) + logger.Error(msg) + raise errors.BlockDeviceError(msg) def SetSyncSpeed(self, kbytes): """Set the speed of the DRBD syncer. @@ -1486,16 +1513,18 @@ class DRBDev(BaseDRBD): logger.Error("Can't change syncer rate: %s " % result.fail_reason) return not result.failed and children_result - def GetSyncStatus(self): """Returns the sync status of the device. Returns: - (sync_percent, estimated_time) + (sync_percent, estimated_time, is_degraded, ldisk) If sync_percent is None, it means all is ok If estimated_time is None, it means we can't esimate - the time needed, otherwise it's the time left in seconds + the time needed, otherwise it's the time left in seconds. + + The ldisk parameter will be returned as True, since the DRBD7 + devices have not been converted. """ if self.minor is None and not self.Attach(): @@ -1522,10 +1551,7 @@ class DRBDev(BaseDRBD): self.minor) client_state = match.group(1) is_degraded = client_state != "Connected" - return sync_percent, est_time, is_degraded - - - + return sync_percent, est_time, is_degraded, False def GetStatus(self): """Compute the status of the DRBD device @@ -1555,7 +1581,6 @@ class DRBDev(BaseDRBD): return result - @staticmethod def _ZeroDevice(device): """Zero a device. @@ -1572,7 +1597,6 @@ class DRBDev(BaseDRBD): if err.errno != errno.ENOSPC: raise - @classmethod def Create(cls, unique_id, children, size): """Create a new DRBD device. @@ -1594,7 +1618,6 @@ class DRBDev(BaseDRBD): logger.Info("Done zeroing device %s" % meta.dev_path) return cls(unique_id, children) - def Remove(self): """Stub remove for DRBD devices. @@ -1615,18 +1638,20 @@ class DRBD8(BaseDRBD): valid size and is zeroed on create. """ - _DRBD_MAJOR = 147 _MAX_MINORS = 255 _PARSE_SHOW = None def __init__(self, unique_id, children): + if children and children.count(None) > 0: + children = [] super(DRBD8, self).__init__(unique_id, children) self.major = self._DRBD_MAJOR - [kmaj, kmin, kfix, api, proto] = self._GetVersion() - if kmaj != 8: + version = self._GetVersion() + if version['k_major'] != 8 : raise errors.BlockDeviceError("Mismatch in DRBD kernel version and" " requested ganeti usage: kernel is" - " %s.%s, ganeti wants 8.x" % (kmaj, kmin)) + " %s.%s, ganeti wants 8.x" % + (version['k_major'], version['k_minor'])) if len(children) not in (0, 2): raise ValueError("Invalid configuration data %s" % str(children)) @@ -1707,7 +1732,7 @@ class DRBD8(BaseDRBD): rbrace = pyp.Literal("}").suppress() semi = pyp.Literal(";").suppress() # this also converts the value to an int - number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])])) + number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0])) comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine) defa = pyp.Literal("_is_default").suppress() @@ -1742,19 +1767,27 @@ class DRBD8(BaseDRBD): return bnf @classmethod - def _GetDevInfo(cls, minor): - """Get details about a given DRBD minor. - - This return, if available, the local backing device (as a path) - and the local and remote (ip, port) information. + def _GetShowData(cls, minor): + """Return the `drbdsetup show` data for a minor. """ - data = {} result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"]) if result.failed: logger.Error("Can't display the drbd config: %s" % result.fail_reason) - return data - out = result.stdout + return None + return result.stdout + + @classmethod + def _GetDevInfo(cls, out): + """Parse details about a given DRBD minor. + + This return, if available, the local backing device (as a path) + and the local and remote (ip, port) information from a string + containing the output of the `drbdsetup show` command as returned + by _GetShowData. + + """ + data = {} if not out: return data @@ -1863,6 +1896,11 @@ class DRBD8(BaseDRBD): """ lhost, lport, rhost, rport = net_info + if None in net_info: + # we don't want network connection and actually want to make + # sure its shutdown + return cls._ShutdownNet(minor) + args = ["drbdsetup", cls._DevPath(minor), "net", "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol, "-A", "discard-zero-changes", @@ -1881,7 +1919,7 @@ class DRBD8(BaseDRBD): timeout = time.time() + 10 ok = False while time.time() < timeout: - info = cls._GetDevInfo(minor) + info = cls._GetDevInfo(cls._GetShowData(minor)) if not "local_addr" in info or not "remote_addr" in info: time.sleep(1) continue @@ -1902,10 +1940,10 @@ class DRBD8(BaseDRBD): """ if self.minor is None: raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren") - if len(devices) != 2: raise errors.BlockDeviceError("Need two devices for AddChildren") - if self._children: + info = self._GetDevInfo(self._GetShowData(self.minor)) + if "local_dev" in info: raise errors.BlockDeviceError("DRBD8 already attached to a local disk") backend, meta = devices if backend.dev_path is None or meta.dev_path is None: @@ -1929,16 +1967,23 @@ class DRBD8(BaseDRBD): if self.minor is None: raise errors.BlockDeviceError("Can't attach to drbd8 during" " RemoveChildren") + # early return if we don't actually have backing storage + info = self._GetDevInfo(self._GetShowData(self.minor)) + if "local_dev" not in info: + return if len(self._children) != 2: raise errors.BlockDeviceError("We don't have two children: %s" % self._children) - + if self._children.count(None) == 2: # we don't actually have children :) + logger.Error("Requested detach while detached") + return if len(devices) != 2: raise errors.BlockDeviceError("We need two children in RemoveChildren") - for idx, dev in enumerate(devices): - if dev.dev_path != self._children[idx].dev_path: - raise errors.BlockDeviceError("Mismatch in local storage (%d) in" - " RemoveChildren" % idx) + for child, dev in zip(self._children, devices): + if dev != child.dev_path: + raise errors.BlockDeviceError("Mismatch in local storage" + " (%s != %s) in RemoveChildren" % + (dev, child.dev_path)) if not self._ShutdownLocal(self.minor): raise errors.BlockDeviceError("Can't detach from local storage") @@ -1962,11 +2007,18 @@ class DRBD8(BaseDRBD): """Returns the sync status of the device. Returns: - (sync_percent, estimated_time) + (sync_percent, estimated_time, is_degraded) If sync_percent is None, it means all is ok If estimated_time is None, it means we can't esimate - the time needed, otherwise it's the time left in seconds + the time needed, otherwise it's the time left in seconds. + + + We set the is_degraded parameter to True on two conditions: + network not connected or local disk missing. + + We compute the ldisk parameter based on wheter we have a local + disk or not. """ if self.minor is None and not self.Attach(): @@ -1987,13 +2039,15 @@ class DRBD8(BaseDRBD): else: sync_percent = None est_time = None - match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line) + match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line) if not match: raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" % self.minor) client_state = match.group(1) + local_disk_state = match.group(2) + ldisk = local_disk_state != "UpToDate" is_degraded = client_state != "Connected" - return sync_percent, est_time, is_degraded + return sync_percent, est_time, is_degraded or ldisk, ldisk def GetStatus(self): """Compute the status of the DRBD device @@ -2040,9 +2094,9 @@ class DRBD8(BaseDRBD): cmd.append("-o") result = utils.RunCmd(cmd) if result.failed: - logger.Error("Can't make drbd device primary: %s" % result.output) - return False - return True + msg = ("Can't make drbd device primary: %s" % result.output) + logger.Error(msg) + raise errors.BlockDeviceError(msg) def Close(self): """Make the local state secondary. @@ -2055,8 +2109,10 @@ class DRBD8(BaseDRBD): raise errors.BlockDeviceError("Can't find device") result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"]) if result.failed: - logger.Error("Can't switch drbd device to secondary: %s" % result.output) - raise errors.BlockDeviceError("Can't switch drbd device to secondary") + msg = ("Can't switch drbd device to" + " secondary: %s" % result.output) + logger.Error(msg) + raise errors.BlockDeviceError(msg) def Attach(self): """Find a DRBD device which matches our config and attach to it. @@ -2067,7 +2123,7 @@ class DRBD8(BaseDRBD): """ for minor in self._GetUsedDevs(): - info = self._GetDevInfo(minor) + info = self._GetDevInfo(self._GetShowData(minor)) match_l = self._MatchesLocal(info) match_r = self._MatchesNet(info) if match_l and match_r: @@ -2077,8 +2133,31 @@ class DRBD8(BaseDRBD): (self._lhost, self._lport, self._rhost, self._rport), "C") - if res_r and self._MatchesNet(self._GetDevInfo(minor)): + if res_r: + if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))): + break + # the weakest case: we find something that is only net attached + # even though we were passed some children at init time + if match_r and "local_dev" not in info: + break + if match_l and not match_r and "local_addr" in info: + # strange case - the device network part points to somewhere + # else, even though its local storage is ours; as we own the + # drbd space, we try to disconnect from the remote peer and + # reconnect to our correct one + if not self._ShutdownNet(minor): + raise errors.BlockDeviceError("Device has correct local storage," + " wrong remote peer and is unable to" + " disconnect in order to attach to" + " the correct peer") + # note: _AssembleNet also handles the case when we don't want + # local storage (i.e. one or more of the _[lr](host|port) is + # None) + if (self._AssembleNet(minor, (self._lhost, self._lport, + self._rhost, self._rport), "C") and + self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))): break + else: minor = None @@ -2115,7 +2194,7 @@ class DRBD8(BaseDRBD): minor = self._FindUnusedMinor() need_localdev_teardown = False - if self._children[0]: + if self._children and self._children[0] and self._children[1]: result = self._AssembleLocal(minor, self._children[0].dev_path, self._children[1].dev_path) if not result: @@ -2156,7 +2235,8 @@ class DRBD8(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"]) - logger.Error("Can't shutdown network: %s" % result.output) + if result.failed: + logger.Error("Can't shutdown network: %s" % result.output) return not result.failed @classmethod @@ -2184,27 +2264,6 @@ class DRBD8(BaseDRBD): self.dev_path = None return True - def Rename(self, new_uid): - """Re-connect this device to another peer. - - """ - if self.minor is None: - raise errors.BlockDeviceError("Device not attached during rename") - if self._rhost is not None: - # this means we did have a host when we attached, so we are connected - if not self._ShutdownNet(self.minor): - raise errors.BlockDeviceError("Can't disconnect from remote peer") - old_id = self.unique_id - else: - old_id = None - self.unique_id = new_uid - if not self._AssembleNet(self.minor, self.unique_id, "C"): - logger.Error("Can't attach to new peer!") - if self.old_id is not None: - self._AssembleNet(self.minor, old_id, "C") - self.unique_id = old_id - raise errors.BlockDeviceError("Can't attach to new peer") - def Remove(self): """Stub remove for DRBD devices. @@ -2268,10 +2327,10 @@ def AttachOrAssemble(dev_type, unique_id, children): device = DEV_MAP[dev_type](unique_id, children) if not device.Attach(): device.Assemble() - if not device.Attach(): - raise errors.BlockDeviceError("Can't find a valid block device for" - " %s/%s/%s" % - (dev_type, unique_id, children)) + if not device.Attach(): + raise errors.BlockDeviceError("Can't find a valid block device for" + " %s/%s/%s" % + (dev_type, unique_id, children)) return device