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
self.unique_id = unique_id
self.major = None
self.minor = None
+ self.attached = False
def Assemble(self):
"""Assemble the device from its components.
If this device is a mirroring device, this function returns the
status of the mirror.
- Returns:
- (sync_percent, estimated_time, is_degraded, ldisk)
-
If sync_percent is None, it means the device is not syncing.
If estimated_time is None, it means we can't estimate
data. This is only valid for some devices, the rest will always
return False (not degraded).
+ @rtype: tuple
+ @return: (sync_percent, estimated_time, is_degraded, ldisk)
+
"""
return None, None, False, False
def Grow(self, amount):
"""Grow the block device.
- Arguments:
- amount: the amount (in mebibytes) to grow with
-
- Returns: None
+ @param amount: the amount (in mebibytes) to grow with
"""
raise NotImplementedError
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
def GetPVInfo(vg_name):
"""Get the free space info for PVs in a volume group.
- Args:
- vg_name: the volume group name
+ @param vg_name: the volume group name
- Returns:
- list of (free_space, name) with free_space in mebibytes
+ @rtype: list
+ @return: list of tuples (free_space, name) with free_space in mebibytes
"""
command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
"--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':
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
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.
"""
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.
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
physical disk failure and subsequent 'vgreduce --removemissing' on
the volume group.
+ The status was already read in Attach, so we just return it.
+
+ @rtype: tuple
+ @return: (sync_percent, estimated_time, is_degraded, ldisk)
+
"""
- 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.
(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):
"""Base DRBD class.
_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
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
+ @return: a dictionary of minor: joined lines from /proc/drbd
+ for that minor
"""
lmatch = re.compile("^ *([0-9]+):.*$")
"""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)
+ - k_major
+ - k_minor
+ - k_point
+ - api
+ - proto
+ - proto2 (only on drbd > 8.2.X)
"""
proc_data = cls._GetProcData()
"""
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):
"""
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
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:
+ if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
raise ValueError("Invalid configuration data %s" % str(unique_id))
- self._lhost, self._lport, self._rhost, self._rport = unique_id
+ (self._lhost, self._lport,
+ self._rhost, self._rport,
+ self._aminor, self._secret) = unique_id
+ if (self._lhost is not None and self._lhost == self._rhost and
+ self._lport == self._rport):
+ raise ValueError("Invalid configuration data, same local/remote %s" %
+ (unique_id,))
self.Attach()
@classmethod
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
"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
"""
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
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
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
ok = True
break
if not ok:
- logger.Error("Timeout while configuring network")
+ logging.error("Timeout while configuring network")
return False
return True
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")
"""
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.
- Returns:
- (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
We compute the ldisk parameter based on wheter we have a local
disk or not.
+ @rtype: tuple
+ @return: (sync_percent, estimated_time, is_degraded, ldisk)
+
"""
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.
"""
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:
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):
"""
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):
the attach if can return success.
"""
- for minor in self._GetUsedDevs():
+ for minor in (self._aminor,):
info = self._GetDevInfo(self._GetShowData(minor))
match_l = self._MatchesLocal(info)
match_r = self._MatchesNet(info)
res_r = self._AssembleNet(minor,
(self._lhost, self._lport,
self._rhost, self._rport),
- "C")
+ constants.DRBD_NET_PROTOCOL,
+ hmac=constants.DRBD_HMAC_ALG,
+ secret=self._secret
+ )
if res_r:
if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
break
# 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._rhost, self._rport),
+ constants.DRBD_NET_PROTOCOL,
+ hmac=constants.DRBD_HMAC_ALG,
+ secret=self._secret) and
self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
break
"""
self.Attach()
if self.minor is not None:
- logger.Info("Already assembled")
+ logging.info("Already assembled")
return True
result = super(DRBD8, self).Assemble()
if not result:
return result
- minor = self._FindUnusedMinor()
+ # TODO: maybe completely tear-down the minor (drbdsetup ... down)
+ # before attaching our own?
+ minor = self._aminor
need_localdev_teardown = False
if self._children and self._children[0] and self._children[1]:
result = self._AssembleLocal(minor, self._children[0].dev_path,
result = self._AssembleNet(minor,
(self._lhost, self._lport,
self._rhost, self._rport),
- "C")
+ constants.DRBD_NET_PROTOCOL,
+ hmac=constants.DRBD_HMAC_ALG,
+ secret=self._secret)
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)
"""
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
"""
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
"""
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):
"""
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
raise ValueError("Invalid configuration data %s" % str(unique_id))
self.driver = unique_id[0]
self.dev_path = unique_id[1]
+ self.Attach()
def Assemble(self):
"""Assemble the device.
def Remove(self):
"""Remove the file backing the block device.
- Returns:
- boolean indicating wheter removal of file was successful or not.
+ @rtype: boolean
+ @return: True if the removal was successful
"""
if not os.path.exists(self.dev_path):
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):
Check if this file already exists.
- Returns:
- boolean indicating if file exists or not.
+ @rtype: boolean
+ @return: True if file exists
"""
- if os.path.exists(self.dev_path):
- return True
- return False
+ self.attached = os.path.exists(self.dev_path)
+ return self.attached
@classmethod
def Create(cls, unique_id, children, size):
"""Create a new file.
- Args:
- children:
- size: integer size of file in MiB
+ @param size: the size of file in MiB
- Returns:
- A ganeti.bdev.FileStorage object.
+ @rtype: L{bdev.FileStorage}
+ @return: an instance of FileStorage
"""
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
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
+ return device
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))