Add instance port support.
[ganeti-local] / lib / bdev.py
index dfa153e..5069bb2 100644 (file)
@@ -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.
 
@@ -131,21 +129,18 @@ class BlockDev(object):
         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 +155,6 @@ class BlockDev(object):
     """
     raise NotImplementedError
 
-
   def Remove(self):
     """Remove this device.
 
@@ -171,7 +165,6 @@ class BlockDev(object):
     """
     raise NotImplementedError
 
-
   def Rename(self, new_id):
     """Rename this device.
 
@@ -180,14 +173,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 +191,6 @@ class BlockDev(object):
     """
     raise NotImplementedError
 
-
   def Shutdown(self):
     """Shut down the device, freeing its children.
 
@@ -211,7 +201,6 @@ class BlockDev(object):
     """
     raise NotImplementedError
 
-
   def SetSyncSpeed(self, speed):
     """Adjust the sync speed of the mirror.
 
@@ -224,7 +213,6 @@ class BlockDev(object):
         result = result and child.SetSyncSpeed(speed)
     return result
 
-
   def GetSyncStatus(self):
     """Returns the sync status of the device.
 
@@ -232,17 +220,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 +247,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 +260,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 +297,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,6 +386,9 @@ 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.
@@ -403,8 +400,8 @@ class LogicalVolume(BlockDev):
     """
     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,7 +412,6 @@ class LogicalVolume(BlockDev):
         return True
     return False
 
-
   def Assemble(self):
     """Assemble the device.
 
@@ -425,7 +421,6 @@ class LogicalVolume(BlockDev):
     """
     return True
 
-
   def Shutdown(self):
     """Shutdown the device.
 
@@ -435,7 +430,6 @@ class LogicalVolume(BlockDev):
     """
     return True
 
-
   def GetStatus(self):
     """Return the status of the device.
 
@@ -464,6 +458,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.
@@ -473,7 +500,6 @@ class LogicalVolume(BlockDev):
     """
     return True
 
-
   def Close(self):
     """Notifies that the device will no longer be used for I/O.
 
@@ -482,7 +508,6 @@ class LogicalVolume(BlockDev):
     """
     return True
 
-
   def Snapshot(self, size):
     """Create a snapshot copy of an lvm block device.
 
@@ -512,7 +537,6 @@ class LogicalVolume(BlockDev):
 
     return snap_name
 
-
   def SetInfo(self, text):
     """Update metadata with info text.
 
@@ -542,7 +566,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 +581,6 @@ class MDRaid1(BlockDev):
 
     return (minor is not None)
 
-
   @staticmethod
   def _GetUsedDevs():
     """Compute the list of in-use MD devices.
@@ -581,7 +603,6 @@ class MDRaid1(BlockDev):
 
     return used_md
 
-
   @staticmethod
   def _GetDevInfo(minor):
     """Get info about a MD device.
@@ -604,7 +625,6 @@ class MDRaid1(BlockDev):
           retval["state"] = kv[1].split(", ")
     return retval
 
-
   @staticmethod
   def _FindUnusedMinor():
     """Compute an unused MD minor.
@@ -623,7 +643,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 +655,6 @@ class MDRaid1(BlockDev):
         return minor
     return None
 
-
   @staticmethod
   def _ZeroSuperblock(dev_path):
     """Zero the possible locations for an MD superblock.
@@ -714,7 +732,6 @@ class MDRaid1(BlockDev):
       return None
     return MDRaid1(info["uuid"], children)
 
-
   def Remove(self):
     """Stub remove function for MD RAID 1 arrays.
 
@@ -756,7 +773,6 @@ class MDRaid1(BlockDev):
                                     result.output)
     self._children.extend(devices)
 
-
   def RemoveChildren(self, devices):
     """Remove member(s) from the md raid1.
 
@@ -769,9 +785,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 +814,6 @@ class MDRaid1(BlockDev):
     for dev in orig_devs:
       self._children.remove(dev)
 
-
   def GetStatus(self):
     """Return the status of the device.
 
@@ -810,7 +825,6 @@ class MDRaid1(BlockDev):
       retval = self.STATUS_ONLINE
     return retval
 
-
   def _SetFromMinor(self, minor):
     """Set our parameters based on the given minor.
 
@@ -820,7 +834,6 @@ class MDRaid1(BlockDev):
     self.minor = minor
     self.dev_path = "/dev/md%d" % minor
 
-
   def Assemble(self):
     """Assemble the MD device.
 
@@ -851,7 +864,6 @@ class MDRaid1(BlockDev):
       self.minor = free_minor
     return not result.failed
 
-
   def Shutdown(self):
     """Tear down the MD array.
 
@@ -871,7 +883,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 +903,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 +927,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 +941,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.
@@ -941,7 +952,6 @@ class MDRaid1(BlockDev):
     """
     return True
 
-
   def Close(self):
     """Notifies that the device will no longer be used for I/O.
 
@@ -1191,7 +1201,6 @@ class DRBDev(BaseDRBD):
           continue
     return data
 
-
   def _MatchesLocal(self, info):
     """Test if our local config matches with an existing device.
 
@@ -1219,7 +1228,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 +1253,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 +1269,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 +1282,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 +1294,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 +1333,6 @@ class DRBDev(BaseDRBD):
       return False
     return True
 
-
   @classmethod
   def _ShutdownNet(cls, minor):
     """Disconnect from the remote peer.
@@ -1338,10 +1341,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 +1395,6 @@ class DRBDev(BaseDRBD):
     self._SetFromMinor(minor)
     return True
 
-
   def Shutdown(self):
     """Shutdown the DRBD device.
 
@@ -1406,7 +1408,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 +1435,6 @@ class DRBDev(BaseDRBD):
     self._SetFromMinor(minor)
     return minor is not None
 
-
   def Open(self, force=False):
     """Make the local state primary.
 
@@ -1456,7 +1456,6 @@ class DRBDev(BaseDRBD):
       return False
     return True
 
-
   def Close(self):
     """Make the local state secondary.
 
@@ -1471,7 +1470,6 @@ class DRBDev(BaseDRBD):
       logger.Error("Can't switch drbd device to secondary: %s" % result.output)
       raise errors.BlockDeviceError("Can't switch drbd device to secondary")
 
-
   def SetSyncSpeed(self, kbytes):
     """Set the speed of the DRBD syncer.
 
@@ -1486,16 +1484,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 +1522,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 +1552,6 @@ class DRBDev(BaseDRBD):
 
     return result
 
-
   @staticmethod
   def _ZeroDevice(device):
     """Zero a device.
@@ -1572,7 +1568,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 +1589,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,11 +1609,12 @@ 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()
@@ -1628,7 +1623,7 @@ class DRBD8(BaseDRBD):
                                     " requested ganeti usage: kernel is"
                                     " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
 
-    if len(children) != 2:
+    if len(children) not in (0, 2):
       raise ValueError("Invalid configuration data %s" % str(children))
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
@@ -1707,7 +1702,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()
@@ -1794,15 +1789,21 @@ class DRBD8(BaseDRBD):
     device.
 
     """
-    backend = self._children[0]
+    if self._children:
+      backend, meta = self._children
+    else:
+      backend = meta = None
+
     if backend is not None:
-      retval = (info["local_dev"] == backend.dev_path)
+      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
     else:
       retval = ("local_dev" not in info)
-    meta = self._children[1]
+
     if meta is not None:
-      retval = retval and (info["meta_dev"] == meta.dev_path)
-      retval = retval and (info["meta_index"] == 0)
+      retval = retval and ("meta_dev" in info and
+                           info["meta_dev"] == meta.dev_path)
+      retval = retval and ("meta_index" in info and
+                           info["meta_index"] == 0)
     else:
       retval = retval and ("meta_dev" not in info and
                            "meta_index" not in info)
@@ -1857,6 +1858,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",
@@ -1890,6 +1896,61 @@ class DRBD8(BaseDRBD):
       return False
     return True
 
+  def AddChildren(self, devices):
+    """Add a disk to the DRBD device.
+
+    """
+    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")
+    info = self._GetDevInfo(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:
+      raise errors.BlockDeviceError("Children not ready during AddChildren")
+    backend.Open()
+    meta.Open()
+    if not self._CheckMetaSize(meta.dev_path):
+      raise errors.BlockDeviceError("Invalid meta device size")
+    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
+    if not self._IsValidMeta(meta.dev_path):
+      raise errors.BlockDeviceError("Cannot initalize meta device")
+
+    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
+      raise errors.BlockDeviceError("Can't attach to local storage")
+    self._children = devices
+
+  def RemoveChildren(self, devices):
+    """Detach the drbd device from local storage.
+
+    """
+    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.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 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")
+    self._children = []
+
   def SetSyncSpeed(self, kbytes):
     """Set the speed of the DRBD syncer.
 
@@ -1908,11 +1969,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():
@@ -1933,13 +2001,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
@@ -2025,6 +2095,28 @@ class DRBD8(BaseDRBD):
                                   "C")
         if res_r and self._MatchesNet(self._GetDevInfo(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(minor))):
+          break
+
     else:
       minor = None
 
@@ -2061,7 +2153,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:
@@ -2082,6 +2174,19 @@ class DRBD8(BaseDRBD):
     return True
 
   @classmethod
+  def _ShutdownLocal(cls, minor):
+    """Detach from the local device.
+
+    I/Os will continue to be served from the remote device. If we
+    don't have a remote device, this operation will fail.
+
+    """
+    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
+    if result.failed:
+      logger.Error("Can't detach local device: %s" % result.output)
+    return not result.failed
+
+  @classmethod
   def _ShutdownNet(cls, minor):
     """Disconnect from the remote peer.
 
@@ -2089,7 +2194,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
@@ -2117,27 +2223,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.