STATUS_ONLINE: "online",
}
-
def __init__(self, unique_id, children):
self._children = children
self.dev_path = None
self.major = None
self.minor = None
-
def Assemble(self):
"""Assemble the device from its components.
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.
"""
raise NotImplementedError
-
def Remove(self):
"""Remove this device.
"""
raise NotImplementedError
-
def Rename(self, new_id):
"""Rename this device.
"""
raise NotImplementedError
-
def GetStatus(self):
"""Return the status of the device.
"""
raise NotImplementedError
-
def Open(self, force=False):
"""Make the device ready for use.
"""
raise NotImplementedError
-
def Shutdown(self):
"""Shut down the device, freeing its children.
"""
raise NotImplementedError
-
def SetSyncSpeed(self, speed):
"""Adjust the sync speed of the mirror.
result = result and child.SetSyncSpeed(speed)
return result
-
def GetSyncStatus(self):
"""Returns the sync status of the device.
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):
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:
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):
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.
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.
"""
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():
return True
return False
-
def Assemble(self):
"""Assemble the device.
"""
return True
-
def Shutdown(self):
"""Shutdown the device.
"""
return True
-
def GetStatus(self):
"""Return the status of the device.
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.
"""
return True
-
def Close(self):
"""Notifies that the device will no longer be used for I/O.
"""
return True
-
def Snapshot(self, size):
"""Create a snapshot copy of an lvm block device.
return snap_name
-
def SetInfo(self, text):
"""Update metadata with info text.
self.major = 9
self.Attach()
-
def Attach(self):
"""Find an array which matches our config and attach to it.
return (minor is not None)
-
@staticmethod
def _GetUsedDevs():
"""Compute the list of in-use MD devices.
return used_md
-
@staticmethod
def _GetDevInfo(minor):
"""Get info about a MD device.
retval["state"] = kv[1].split(", ")
return retval
-
@staticmethod
def _FindUnusedMinor():
"""Compute an unused MD minor.
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.
return minor
return None
-
@staticmethod
def _ZeroSuperblock(dev_path):
"""Zero the possible locations for an MD superblock.
return None
return MDRaid1(info["uuid"], children)
-
def Remove(self):
"""Stub remove function for MD RAID 1 arrays.
result.output)
self._children.extend(devices)
-
def RemoveChildren(self, devices):
"""Remove member(s) from the md raid1.
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:
for dev in orig_devs:
self._children.remove(dev)
-
def GetStatus(self):
"""Return the status of the device.
retval = self.STATUS_ONLINE
return retval
-
def _SetFromMinor(self, minor):
"""Set our parameters based on the given minor.
self.minor = minor
self.dev_path = "/dev/md%d" % minor
-
def Assemble(self):
"""Assemble the MD device.
self.minor = free_minor
return not result.failed
-
def Shutdown(self):
"""Tear down the MD array.
self.dev_path = None
return True
-
def SetSyncSpeed(self, kbytes):
"""Set the maximum sync speed for the MD array.
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():
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")
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.
"""
return True
-
def Close(self):
"""Notifies that the device will no longer be used for I/O.
continue
return data
-
def _MatchesLocal(self, info):
"""Test if our local config matches with an existing device.
info["meta_index"] == -1)
return retval
-
def _MatchesNet(self, info):
"""Test if our network config matches with an existing device.
info["remote_addr"] == (self._rhost, self._rport))
return retval
-
@classmethod
def _AssembleLocal(cls, minor, backend, meta):
"""Configure the local part of a DRBD device.
logger.Error("Can't attach local disk: %s" % result.output)
return not result.failed
-
@classmethod
def _ShutdownLocal(cls, minor):
"""Detach from the local device.
logger.Error("Can't detach local device: %s" % result.output)
return not result.failed
-
@staticmethod
def _ShutdownAll(minor):
"""Deactivate the device.
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.
return False
return True
-
@classmethod
def _ShutdownNet(cls, minor):
"""Disconnect from the remote peer.
"""
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.
self._SetFromMinor(minor)
return True
-
def Shutdown(self):
"""Shutdown the DRBD device.
self.dev_path = None
return True
-
def Attach(self):
"""Find a DRBD device which matches our config and attach to it.
self._SetFromMinor(minor)
return minor is not None
-
def Open(self, force=False):
"""Make the local state primary.
return False
return True
-
def Close(self):
"""Make the local state secondary.
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.
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():
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
return result
-
@staticmethod
def _ZeroDevice(device):
"""Zero a device.
if err.errno != errno.ENOSPC:
raise
-
@classmethod
def Create(cls, unique_id, children, size):
"""Create a new DRBD device.
logger.Info("Done zeroing device %s" % meta.dev_path)
return cls(unique_id, children)
-
def Remove(self):
"""Stub remove for DRBD devices.
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()
" 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))
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()
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)
"""
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",
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.
"""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():
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
"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
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:
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.
"""
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
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.