Commit abdf0113 authored by Iustin Pop's avatar Iustin Pop

Complete removal of md/drbd 0.7 code

This patch removes the last of the md and drbd 0.7 code. Cluster which
have the old device types will be broken if they have this applied.

Reviewed-by: imsnah
parent 5c54b832
......@@ -43,15 +43,14 @@ class BlockDev(object):
- online (=used, or ready for use)
A device can also be online but read-only, however we are not using
the readonly state (MD and LV have it, if needed in the future)
and we are usually looking at this like at a stack, so it's easier
to conceptualise the transition from not-existing to online and back
the readonly state (LV has it, if needed in the future) and we are
usually looking at this like at a stack, so it's easier to
conceptualise the transition from not-existing to online and back
like a linear one.
The many different states of the device are due to the fact that we
need to cover many device types:
- logical volumes are created, lvchange -a y $lv, and used
- md arrays are created or assembled and used
- drbd devices are attached to a local disk/remote peer and made primary
A block device is identified by three items:
......@@ -61,15 +60,13 @@ class BlockDev(object):
Not all devices implement both the first two as distinct items. LVM
logical volumes have their unique ID (the pair volume group, logical
volume name) in a 1-to-1 relation to the dev path. For MD devices,
the /dev path is dynamic and the unique ID is the UUID generated at
array creation plus the slave list. For DRBD devices, the /dev path
is again dynamic and the unique id is the pair (host1, dev1),
(host2, dev2).
volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
the /dev path is again dynamic and the unique id is the pair (host1,
dev1), (host2, dev2).
You can get to a device in two ways:
- creating the (real) device, which returns you
an attached instance (lvcreate, mdadm --create)
an attached instance (lvcreate)
- attaching of a python instance to an existing (real) device
The second point, the attachement to a device, is different
......@@ -149,9 +146,9 @@ class BlockDev(object):
def Remove(self):
"""Remove this device.
This makes sense only for some of the device types: LV and to a
lesser degree, md devices. Also note that if the device can't
attach, the removal can't be completed.
This makes sense only for some of the device types: LV and file
storeage. Also note that if the device can't attach, the removal
can't be completed.
"""
raise NotImplementedError
......@@ -523,1029 +520,167 @@ class LogicalVolume(BlockDev):
result.output))
class MDRaid1(BlockDev):
"""raid1 device implemented via md.
"""
def __init__(self, unique_id, children):
super(MDRaid1, self).__init__(unique_id, children)
self.major = 9
self.Attach()
def Attach(self):
"""Find an array which matches our config and attach to it.
This tries to find a MD array which has the same UUID as our own.
"""
minor = self._FindMDByUUID(self.unique_id)
if minor is not None:
self._SetFromMinor(minor)
else:
self.minor = None
self.dev_path = None
return (minor is not None)
@staticmethod
def _GetUsedDevs():
"""Compute the list of in-use MD devices.
It doesn't matter if the used device have other raid level, just
that they are in use.
"""
mdstat = open("/proc/mdstat", "r")
data = mdstat.readlines()
mdstat.close()
used_md = {}
valid_line = re.compile("^md([0-9]+) : .*$")
for line in data:
match = valid_line.match(line)
if match:
md_no = int(match.group(1))
used_md[md_no] = line
return used_md
@staticmethod
def _GetDevInfo(minor):
"""Get info about a MD device.
Currently only uuid is returned.
"""
result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
if result.failed:
logger.Error("Can't display md: %s - %s" %
(result.fail_reason, result.output))
return None
retval = {}
for line in result.stdout.splitlines():
line = line.strip()
kv = line.split(" : ", 1)
if kv:
if kv[0] == "UUID":
retval["uuid"] = kv[1].split()[0]
elif kv[0] == "State":
retval["state"] = kv[1].split(", ")
return retval
@staticmethod
def _FindUnusedMinor():
"""Compute an unused MD minor.
This code assumes that there are 256 minors only.
"""
used_md = MDRaid1._GetUsedDevs()
i = 0
while i < 256:
if i not in used_md:
break
i += 1
if i == 256:
logger.Error("Critical: Out of md minor numbers.")
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.
"""
md_list = cls._GetUsedDevs()
for minor in md_list:
info = cls._GetDevInfo(minor)
if info and info["uuid"] == uuid:
return minor
return None
@staticmethod
def _ZeroSuperblock(dev_path):
"""Zero the possible locations for an MD superblock.
The zero-ing can't be done via ``mdadm --zero-superblock`` as that
fails in versions 2.x with the same error code as non-writable
device.
The superblocks are located at (negative values are relative to
the end of the block device):
- -128k to end for version 0.90 superblock
- -8k to -12k for version 1.0 superblock (included in the above)
- 0k to 4k for version 1.1 superblock
- 4k to 8k for version 1.2 superblock
To cover all situations, the zero-ing will be:
- 0k to 128k
- -128k to end
As such, the minimum device size must be 128k, otherwise we'll get
I/O errors.
Note that this function depends on the fact that one can open,
read and write block devices normally.
"""
overwrite_size = 128 * 1024
empty_buf = '\0' * overwrite_size
fd = open(dev_path, "r+")
try:
fd.seek(0, 0)
p1 = fd.tell()
fd.write(empty_buf)
p2 = fd.tell()
logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
fd.seek(-overwrite_size, 2)
p1 = fd.tell()
fd.write(empty_buf)
p2 = fd.tell()
logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
finally:
fd.close()
@classmethod
def Create(cls, unique_id, children, size):
"""Create a new MD raid1 array.
"""
if not isinstance(children, (tuple, list)):
raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
str(children))
for i in children:
if not isinstance(i, BlockDev):
raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
for i in children:
try:
cls._ZeroSuperblock(i.dev_path)
except EnvironmentError, err:
logger.Error("Can't zero superblock for %s: %s" %
(i.dev_path, str(err)))
return None
minor = cls._FindUnusedMinor()
result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
"--auto=yes", "--force", "-l1",
"-n%d" % len(children)] +
[dev.dev_path for dev in children])
if result.failed:
logger.Error("Can't create md: %s: %s" % (result.fail_reason,
result.output))
return None
info = cls._GetDevInfo(minor)
if not info or not "uuid" in info:
logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
return None
return MDRaid1(info["uuid"], children)
def Remove(self):
"""Stub remove function for MD RAID 1 arrays.
We don't remove the superblock right now. Mark a to do.
"""
#TODO: maybe zero superblock on child devices?
return self.Shutdown()
def Rename(self, new_id):
"""Rename a device.
This is not supported for md raid1 devices.
"""
raise errors.ProgrammerError("Can't rename a md raid1 device")
def AddChildren(self, devices):
"""Add new member(s) to the md raid1.
"""
if self.minor is None and not self.Attach():
raise errors.BlockDeviceError("Can't attach to device")
args = ["mdadm", "-a", self.dev_path]
for dev in devices:
if dev.dev_path is None:
raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
dev.Open()
args.append(dev.dev_path)
result = utils.RunCmd(args)
if result.failed:
raise errors.BlockDeviceError("Failed to add new device to array: %s" %
result.output)
new_len = len(self._children) + len(devices)
result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
if result.failed:
raise errors.BlockDeviceError("Can't grow md array: %s" %
result.output)
self._children.extend(devices)
def RemoveChildren(self, devices):
"""Remove member(s) from the md raid1.
"""
if self.minor is None and not self.Attach():
raise errors.BlockDeviceError("Can't attach to device")
new_len = len(self._children) - len(devices)
if new_len < 1:
raise errors.BlockDeviceError("Can't reduce to less than one child")
args = ["mdadm", "-f", self.dev_path]
orig_devs = []
for dev in devices:
args.append(dev)
for c in self._children:
if c.dev_path == dev:
orig_devs.append(c)
break
else:
raise errors.BlockDeviceError("Can't find device '%s' for removal" %
dev)
result = utils.RunCmd(args)
if result.failed:
raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
result.output)
# it seems here we need a short delay for MD to update its
# superblocks
time.sleep(0.5)
args[1] = "-r"
result = utils.RunCmd(args)
if result.failed:
raise errors.BlockDeviceError("Failed to remove device(s) from array:"
" %s" % result.output)
result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
"-n", new_len])
if result.failed:
raise errors.BlockDeviceError("Can't shrink md array: %s" %
result.output)
for dev in orig_devs:
self._children.remove(dev)
def _SetFromMinor(self, minor):
"""Set our parameters based on the given minor.
This sets our minor variable and our dev_path.
"""
self.minor = minor
self.dev_path = "/dev/md%d" % minor
def Assemble(self):
"""Assemble the MD device.
At this point we should have:
- list of children devices
- uuid
"""
result = super(MDRaid1, self).Assemble()
if not result:
return result
md_list = self._GetUsedDevs()
for minor in md_list:
info = self._GetDevInfo(minor)
if info and info["uuid"] == self.unique_id:
self._SetFromMinor(minor)
logger.Info("MD array %s already started" % str(self))
return True
free_minor = self._FindUnusedMinor()
result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
self.unique_id, "/dev/md%d" % free_minor] +
[bdev.dev_path for bdev in self._children])
if result.failed:
logger.Error("Can't assemble MD array: %s: %s" %
(result.fail_reason, result.output))
self.minor = None
else:
self.minor = free_minor
return not result.failed
def Shutdown(self):
"""Tear down the MD array.
This does a 'mdadm --stop' so after this command, the array is no
longer available.
"""
if self.minor is None and not self.Attach():
logger.Info("MD object not attached to a device")
return True
result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
if result.failed:
logger.Error("Can't stop MD array: %s - %s" %
(result.fail_reason, result.output))
return False
self.minor = None
self.dev_path = None
return True
def SetSyncSpeed(self, kbytes):
"""Set the maximum sync speed for the MD array.
"""
result = super(MDRaid1, self).SetSyncSpeed(kbytes)
if self.minor is None:
logger.Error("MD array not attached to a device")
return False
f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
try:
f.write("%d" % kbytes)
finally:
f.close()
f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
try:
f.write("%d" % (kbytes/2))
finally:
f.close()
return result
def GetSyncStatus(self):
"""Returns the sync status of the device.
Returns:
(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 ldisk parameter is always true for MD devices.
"""
if self.minor is None and not self.Attach():
raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
dev_info = self._GetDevInfo(self.minor)
is_clean = ("state" in dev_info and
len(dev_info["state"]) == 1 and
dev_info["state"][0] in ("clean", "active"))
sys_path = "/sys/block/md%s/md/" % self.minor
f = file(sys_path + "sync_action")
sync_status = f.readline().strip()
f.close()
if sync_status == "idle":
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, 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")
sync_speed_k = int(f.readline().strip())
if sync_speed_k == 0:
time_est = None
else:
time_est = (sync_total - sync_done) / 2 / sync_speed_k
return sync_percent, time_est, not is_clean, False
def Open(self, force=False):
"""Make the device ready for I/O.
This is a no-op for the MDRaid1 device type, although we could use
the 2.6.18's new array_state thing.
"""
pass
def Close(self):
"""Notifies that the device will no longer be used for I/O.
This is a no-op for the MDRaid1 device type, but see comment for
`Open()`.
"""
pass
class BaseDRBD(BlockDev):
"""Base DRBD class.
This class contains a few bits of common functionality between the
0.7 and 8.x versions of DRBD.
"""
_VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
_DRBD_MAJOR = 147
_ST_UNCONFIGURED = "Unconfigured"
_ST_WFCONNECTION = "WFConnection"
_ST_CONNECTED = "Connected"
@staticmethod
def _GetProcData():
"""Return data from /proc/drbd.
"""
stat = open("/proc/drbd", "r")
try:
data = stat.read().splitlines()
finally:
stat.close()
if not data:
raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
return data
@staticmethod
def _MassageProcData(data):
"""Transform the output of _GetProdData into a nicer form.
Returns:
a dictionary of minor: joined lines from /proc/drbd for that minor
"""
lmatch = re.compile("^ *([0-9]+):.*$")
results = {}
old_minor = old_line = None
for line in data:
lresult = lmatch.match(line)
if lresult is not None:
if old_minor is not None:
results[old_minor] = old_line
old_minor = int(lresult.group(1))
old_line = line
else:
if old_minor is not None:
old_line += " " + line.strip()
# add last line
if old_minor is not None:
results[old_minor] = old_line
return results
@classmethod
def _GetVersion(cls):
"""Return the DRBD version.
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()
first_line = proc_data[0].strip()
version = cls._VERSION_RE.match(first_line)
if not version:
raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
first_line)
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):
"""Return the path to a drbd device for a given minor.
"""
return "/dev/drbd%d" % minor
@classmethod
def _GetUsedDevs(cls):
"""Compute the list of used DRBD devices.
"""
data = cls._GetProcData()
used_devs = {}
valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
for line in data:
match = valid_line.match(line)
if not match:
continue
minor = int(match.group(1))
state = match.group(2)
if state == cls._ST_UNCONFIGURED:
continue
used_devs[minor] = state, line
return used_devs
def _SetFromMinor(self, minor):
"""Set our parameters based on the given minor.
This sets our minor variable and our dev_path.
"""
if minor is None:
self.minor = self.dev_path = None
else:
self.minor = minor
self.dev_path = self._DevPath(minor)
@staticmethod
def _CheckMetaSize(meta_device):
"""Check if the given meta device looks like a valid one.
This currently only check the size, which must be around
128MiB.
"""
result = utils.RunCmd(["blockdev", "--getsize", meta_device])
if result.failed:
logger.Error("Failed to get device size: %s - %s" %
(result.fail_reason, result.output))
return False
try:
sectors = int(result.stdout)
except ValueError:
logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
return False
bytes = sectors * 512
if bytes < 128 * 1024 * 1024: # less than 128MiB
logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
return False
if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
return False
return True
def Rename(self, new_id):
"""Rename a device.
This is not supported for drbd devices.
"""
raise errors.ProgrammerError("Can't rename a drbd device")
class DRBDev(BaseDRBD):
"""DRBD block device.
This implements the local host part of the DRBD device, i.e. it
doesn't do anything to the supposed peer. If you need a fully
connected DRBD pair, you need to use this class on both hosts.
The unique_id for the drbd device is the (local_ip, local_port,
remote_ip, remote_port) tuple, and it must have two children: the
data device and the meta_device. The meta device is checked for
valid size and is zeroed on create.
"""
def __init__(self, unique_id, children):
super(DRBDev, self).__init__(unique_id, children)
self.major = self._DRBD_MAJOR
version = self._GetVersion()