X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/abdf0113c7089a252d99f35a165419479a21012b..911a495b6553598d29c7b23294651be3cb602ce5:/lib/bdev.py diff --git a/lib/bdev.py b/lib/bdev.py index d73d63f..7ba3470 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -26,9 +26,9 @@ import time import errno import pyparsing as pyp import os +import logging from ganeti import utils -from ganeti import logger from ganeti import errors from ganeti import constants @@ -82,6 +82,7 @@ class BlockDev(object): self.unique_id = unique_id self.major = None self.minor = None + self.attached = False def Assemble(self): """Assemble the device from its components. @@ -255,6 +256,16 @@ class BlockDev(object): for child in self._children: child.SetInfo(text) + def Grow(self, amount): + """Grow the block device. + + Arguments: + amount: the amount (in mebibytes) to grow with + + Returns: None + + """ + raise NotImplementedError def __repr__(self): return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" % @@ -277,6 +288,8 @@ class LogicalVolume(BlockDev): raise ValueError("Invalid configuration data %s" % str(unique_id)) self._vg_name, self._lv_name = unique_id self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name) + self._degraded = True + self.major = self.minor = None self.Attach() @classmethod @@ -325,14 +338,14 @@ class LogicalVolume(BlockDev): "--separator=:"] result = utils.RunCmd(command) if result.failed: - logger.Error("Can't get the PV information: %s - %s" % - (result.fail_reason, result.output)) + logging.error("Can't get the PV information: %s - %s", + result.fail_reason, result.output) return None data = [] for line in result.stdout.splitlines(): fields = line.strip().split(':') if len(fields) != 4: - logger.Error("Can't parse pvs output: line '%s'" % line) + logging.error("Can't parse pvs output: line '%s'", line) return None # skip over pvs from another vg or ones which are not allocatable if fields[1] != vg_name or fields[3][0] != 'a': @@ -351,8 +364,8 @@ class LogicalVolume(BlockDev): result = utils.RunCmd(["lvremove", "-f", "%s/%s" % (self._vg_name, self._lv_name)]) if result.failed: - logger.Error("Can't lvremove: %s - %s" % - (result.fail_reason, result.output)) + logging.error("Can't lvremove: %s - %s", + result.fail_reason, result.output) return not result.failed @@ -382,19 +395,37 @@ class LogicalVolume(BlockDev): recorded. """ - result = utils.RunCmd(["lvdisplay", self.dev_path]) + self.attached = False + result = utils.RunCmd(["lvs", "--noheadings", "--separator=,", + "-olv_attr,lv_kernel_major,lv_kernel_minor", + self.dev_path]) if result.failed: - logger.Error("Can't find LV %s: %s, %s" % - (self.dev_path, result.fail_reason, result.output)) + logging.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(): - match_result = match.match(line) - if match_result: - self.major = int(match_result.group(1)) - self.minor = int(match_result.group(2)) - return True - return False + out = result.stdout.strip().rstrip(',') + out = out.split(",") + if len(out) != 3: + logging.error("Can't parse LVS output, len(%s) != 3", str(out)) + return False + + status, major, minor = out[:3] + if len(status) != 6: + logging.error("lvs lv_attr is not 6 characters (%s)", status) + return False + + try: + major = int(major) + minor = int(minor) + except ValueError, err: + logging.error("lvs major/minor cannot be parsed: %s", str(err)) + + self.major = major + self.minor = minor + self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing + # storage + self.attached = True + return True def Assemble(self): """Assemble the device. @@ -406,8 +437,9 @@ class LogicalVolume(BlockDev): """ 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 + logging.error("Can't activate lv %s: %s", self.dev_path, result.output) + return False + return self.Attach() def Shutdown(self): """Shutdown the device. @@ -438,20 +470,10 @@ class LogicalVolume(BlockDev): physical disk failure and subsequent 'vgreduce --removemissing' on the volume group. + The status was already read in Attach, so we just return it. + """ - result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path]) - if result.failed: - logger.Error("Can't display lv: %s - %s" % - (result.fail_reason, result.output)) - 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 + return None, None, self._degraded, self._degraded def Open(self, force=False): """Make the device ready for I/O. @@ -518,6 +540,71 @@ class LogicalVolume(BlockDev): raise errors.BlockDeviceError("Command: %s error: %s - %s" % (result.cmd, result.fail_reason, result.output)) + def Grow(self, amount): + """Grow the logical volume. + + """ + # we try multiple algorithms since the 'best' ones might not have + # space available in the right place, but later ones might (since + # they have less constraints); also note that only recent LVM + # supports 'cling' + for alloc_policy in "contiguous", "cling", "normal": + result = utils.RunCmd(["lvextend", "--alloc", alloc_policy, + "-L", "+%dm" % amount, self.dev_path]) + if not result.failed: + return + raise errors.BlockDeviceError("Can't grow LV %s: %s" % + (self.dev_path, result.output)) + + +class DRBD8Status(object): + """A DRBD status representation class. + + Note that this doesn't support unconfigured devices (cs:Unconfigured). + + """ + LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+st:([^/]+)/(\S+)" + "\s+ds:([^/]+)/(\S+)\s+.*$") + SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" + "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") + + def __init__(self, procline): + m = self.LINE_RE.match(procline) + if not m: + raise errors.BlockDeviceError("Can't parse input data '%s'" % procline) + self.cstatus = m.group(1) + self.lrole = m.group(2) + self.rrole = m.group(3) + self.ldisk = m.group(4) + self.rdisk = m.group(5) + + self.is_standalone = self.cstatus == "StandAlone" + self.is_wfconn = self.cstatus == "WFConnection" + self.is_connected = self.cstatus == "Connected" + self.is_primary = self.lrole == "Primary" + self.is_secondary = self.lrole == "Secondary" + self.peer_primary = self.rrole == "Primary" + self.peer_secondary = self.rrole == "Secondary" + self.both_primary = self.is_primary and self.peer_primary + self.both_secondary = self.is_secondary and self.peer_secondary + + self.is_diskless = self.ldisk == "Diskless" + self.is_disk_uptodate = self.ldisk == "UpToDate" + + m = self.SYNC_RE.match(procline) + if m: + self.sync_percent = float(m.group(1)) + hours = int(m.group(2)) + minutes = int(m.group(3)) + seconds = int(m.group(4)) + self.est_time = hours * 3600 + minutes * 60 + seconds + else: + self.sync_percent = None + self.est_time = None + + self.is_sync_target = self.peer_sync_source = self.cstatus == "SyncTarget" + self.peer_sync_target = self.is_sync_source = self.cstatus == "SyncSource" + self.is_resync = self.is_sync_target or self.is_sync_source class BaseDRBD(BlockDev): @@ -535,18 +622,20 @@ class BaseDRBD(BlockDev): _ST_WFCONNECTION = "WFConnection" _ST_CONNECTED = "Connected" + _STATUS_FILE = "/proc/drbd" + @staticmethod - def _GetProcData(): + def _GetProcData(filename=_STATUS_FILE): """Return data from /proc/drbd. """ - stat = open("/proc/drbd", "r") + stat = open(filename, "r") try: data = stat.read().splitlines() finally: stat.close() if not data: - raise errors.BlockDeviceError("Can't read any data from /proc/drbd") + raise errors.BlockDeviceError("Can't read any data from %s" % filename) return data @staticmethod @@ -643,9 +732,11 @@ class BaseDRBD(BlockDev): """ if minor is None: self.minor = self.dev_path = None + self.attached = False else: self.minor = minor self.dev_path = self._DevPath(minor) + self.attached = True @staticmethod def _CheckMetaSize(meta_device): @@ -657,20 +748,20 @@ class BaseDRBD(BlockDev): """ result = utils.RunCmd(["blockdev", "--getsize", meta_device]) if result.failed: - logger.Error("Failed to get device size: %s - %s" % - (result.fail_reason, result.output)) + logging.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) + logging.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)) + logging.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)) + logging.error("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024)) return False return True @@ -755,7 +846,7 @@ class DRBD8(BaseDRBD): if highest is None: # there are no minors in use at all return 0 if highest >= cls._MAX_MINORS: - logger.Error("Error: no free drbd minors!") + logging.error("Error: no free drbd minors!") raise errors.BlockDeviceError("Can't find a free DRBD minor") return highest + 1 @@ -770,7 +861,7 @@ class DRBD8(BaseDRBD): "v08", meta_device, "0", "dstate"]) if result.failed: - logger.Error("Invalid meta device %s: %s" % (meta_device, result.output)) + logging.error("Invalid meta device %s: %s", meta_device, result.output) return False return True @@ -831,8 +922,8 @@ class DRBD8(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"]) if result.failed: - logger.Error("Can't display the drbd config: %s - %s" % - (result.fail_reason, result.output)) + logging.error("Can't display the drbd config: %s - %s", + result.fail_reason, result.output) return None return result.stdout @@ -945,7 +1036,7 @@ class DRBD8(BaseDRBD): backend, meta, "0", "-e", "detach", "--create-device"] result = utils.RunCmd(args) if result.failed: - logger.Error("Can't attach local disk: %s" % result.output) + logging.error("Can't attach local disk: %s", result.output) return not result.failed @classmethod @@ -972,8 +1063,8 @@ class DRBD8(BaseDRBD): args.extend(["-a", hmac, "-x", secret]) result = utils.RunCmd(args) if result.failed: - logger.Error("Can't setup network for dbrd device: %s - %s" % - (result.fail_reason, result.output)) + logging.error("Can't setup network for dbrd device: %s - %s", + result.fail_reason, result.output) return False timeout = time.time() + 10 @@ -990,7 +1081,7 @@ class DRBD8(BaseDRBD): ok = True break if not ok: - logger.Error("Timeout while configuring network") + logging.error("Timeout while configuring network") return False return True @@ -1035,7 +1126,7 @@ class DRBD8(BaseDRBD): 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") + logging.error("Requested detach while detached") return if len(devices) != 2: raise errors.BlockDeviceError("We need two children in RemoveChildren") @@ -1055,15 +1146,27 @@ class DRBD8(BaseDRBD): """ children_result = super(DRBD8, self).SetSyncSpeed(kbytes) if self.minor is None: - logger.Info("Instance not attached to a device") + logging.info("Instance not attached to a device") return False result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" % kbytes]) if result.failed: - logger.Error("Can't change syncer rate: %s - %s" % - (result.fail_reason, result.output)) + logging.error("Can't change syncer rate: %s - %s", + result.fail_reason, result.output) return not result.failed and children_result + def GetProcStatus(self): + """Return device data from /proc. + + """ + if self.minor is None: + raise errors.BlockDeviceError("GetStats() called while not attached") + proc_info = self._MassageProcData(self._GetProcData()) + if self.minor not in proc_info: + raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" % + self.minor) + return DRBD8Status(proc_info[self.minor]) + def GetSyncStatus(self): """Returns the sync status of the device. @@ -1084,31 +1187,10 @@ class DRBD8(BaseDRBD): """ if self.minor is None and not self.Attach(): raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus") - proc_info = self._MassageProcData(self._GetProcData()) - if self.minor not in proc_info: - raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" % - self.minor) - line = proc_info[self.minor] - match = re.match("^.*sync'ed: *([0-9.]+)%.*" - " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line) - if match: - sync_percent = float(match.group(1)) - hours = int(match.group(2)) - minutes = int(match.group(3)) - seconds = int(match.group(4)) - est_time = hours * 3600 + minutes * 60 + seconds - else: - sync_percent = None - est_time = None - 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 or ldisk, ldisk + stats = self.GetProcStatus() + ldisk = not stats.is_disk_uptodate + is_degraded = not stats.is_connected + return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk def Open(self, force=False): """Make the local state primary. @@ -1120,7 +1202,7 @@ class DRBD8(BaseDRBD): """ if self.minor is None and not self.Attach(): - logger.Error("DRBD cannot attach to a device during open") + logging.error("DRBD cannot attach to a device during open") return False cmd = ["drbdsetup", self.dev_path, "primary"] if force: @@ -1128,7 +1210,7 @@ class DRBD8(BaseDRBD): result = utils.RunCmd(cmd) if result.failed: msg = ("Can't make drbd device primary: %s" % result.output) - logger.Error(msg) + logging.error(msg) raise errors.BlockDeviceError(msg) def Close(self): @@ -1138,13 +1220,13 @@ class DRBD8(BaseDRBD): """ if self.minor is None and not self.Attach(): - logger.Info("Instance not attached to a device") + logging.info("Instance not attached to a device") raise errors.BlockDeviceError("Can't find device") result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"]) if result.failed: msg = ("Can't switch drbd device to" " secondary: %s" % result.output) - logger.Error(msg) + logging.error(msg) raise errors.BlockDeviceError(msg) def Attach(self): @@ -1224,7 +1306,7 @@ class DRBD8(BaseDRBD): """ self.Attach() if self.minor is not None: - logger.Info("Already assembled") + logging.info("Already assembled") return True result = super(DRBD8, self).Assemble() @@ -1247,7 +1329,7 @@ class DRBD8(BaseDRBD): if not result: if need_localdev_teardown: # we will ignore failures from this - logger.Error("net setup failed, tearing down local device") + logging.error("net setup failed, tearing down local device") self._ShutdownAll(minor) return False self._SetFromMinor(minor) @@ -1263,7 +1345,7 @@ class DRBD8(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"]) if result.failed: - logger.Error("Can't detach local device: %s" % result.output) + logging.error("Can't detach local device: %s", result.output) return not result.failed @classmethod @@ -1275,7 +1357,7 @@ class DRBD8(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"]) if result.failed: - logger.Error("Can't shutdown network: %s" % result.output) + logging.error("Can't shutdown network: %s", result.output) return not result.failed @classmethod @@ -1287,7 +1369,7 @@ class DRBD8(BaseDRBD): """ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"]) if result.failed: - logger.Error("Can't shutdown drbd device: %s" % result.output) + logging.error("Can't shutdown drbd device: %s", result.output) return not result.failed def Shutdown(self): @@ -1295,7 +1377,7 @@ class DRBD8(BaseDRBD): """ if self.minor is None and not self.Attach(): - logger.Info("DRBD device not attached to a device during Shutdown") + logging.info("DRBD device not attached to a device during Shutdown") return True if not self._ShutdownAll(self.minor): return False @@ -1330,6 +1412,21 @@ class DRBD8(BaseDRBD): raise errors.BlockDeviceError("Cannot initalize meta device") return cls(unique_id, children) + def Grow(self, amount): + """Resize the DRBD device and its backing storage. + + """ + if self.minor is None: + raise errors.ProgrammerError("drbd8: Grow called while not attached") + if len(self._children) != 2 or None in self._children: + raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device") + self._children[0].Grow(amount) + result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"]) + if result.failed: + raise errors.BlockDeviceError("resize failed for %s: %s" % + (self.dev_path, result.output)) + return + class FileStorage(BlockDev): """File device. @@ -1400,8 +1497,7 @@ class FileStorage(BlockDev): os.remove(self.dev_path) return True except OSError, err: - logger.Error("Can't remove file '%s': %s" - % (self.dev_path, err)) + logging.error("Can't remove file '%s': %s", self.dev_path, err) return False def Attach(self): @@ -1460,7 +1556,7 @@ def FindDevice(dev_type, unique_id, children): if dev_type not in DEV_MAP: raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type) device = DEV_MAP[dev_type](unique_id, children) - if not device.Attach(): + if not device.attached: return None return device @@ -1475,9 +1571,9 @@ def AttachOrAssemble(dev_type, unique_id, children): if dev_type not in DEV_MAP: raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type) device = DEV_MAP[dev_type](unique_id, children) - if not device.Attach(): + if not device.attached: device.Assemble() - if not device.Attach(): + if not device.attached: raise errors.BlockDeviceError("Can't find a valid block device for" " %s/%s/%s" % (dev_type, unique_id, children))