import time
import errno
import pyparsing as pyp
+import os
from ganeti import utils
from ganeti import logger
- md arrays are created or assembled and used
- drbd devices are attached to a local disk/remote peer and made primary
- The status of the device can be examined by `GetStatus()`, which
- returns a numerical value, depending on the position in the
- transition stack of the device.
-
A block device is identified by three items:
- the /dev path of the device (dynamic)
- a unique ID of the device (static)
after assembly we'll have our correct major/minor.
"""
- STATUS_UNKNOWN = 0
- STATUS_EXISTING = 1
- STATUS_STANDBY = 2
- STATUS_ONLINE = 3
-
- STATUS_MAP = {
- STATUS_UNKNOWN: "unknown",
- STATUS_EXISTING: "existing",
- STATUS_STANDBY: "ready for use",
- STATUS_ONLINE: "online",
- }
-
def __init__(self, unique_id, children):
self._children = children
self.dev_path = None
status = status and child.Assemble()
if not status:
break
- status = status and child.Open()
+
+ try:
+ child.Open()
+ except errors.BlockDeviceError:
+ for child in self._children:
+ child.Shutdown()
+ raise
if not status:
for child in self._children:
"""
raise NotImplementedError
- def GetStatus(self):
- """Return the status of the device.
-
- """
- raise NotImplementedError
-
def Open(self, force=False):
"""Make the device ready for use.
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.
This method will try to see if an existing and active LV exists
- which matches the our name. If so, its major/minor will be
+ which matches our name. If so, its major/minor will be
recorded.
"""
def Assemble(self):
"""Assemble the device.
- This is a no-op for the LV device type. Eventually, we could
- lvchange -ay here if we see that the LV is not active.
+ We alway run `lvchange -ay` on the LV to ensure it's active before
+ use, as there were cases when xenvg was not active after boot
+ (also possibly after disk issues).
"""
- return True
+ 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
def Shutdown(self):
"""Shutdown the device.
"""
return True
- def GetStatus(self):
- """Return the status of the device.
-
- Logical volumes will can be in all four states, although we don't
- deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
- should not be seen for our devices.
-
- """
- result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
- if result.failed:
- logger.Error("Can't display lv: %s" % result.fail_reason)
- return self.STATUS_UNKNOWN
- out = result.stdout.strip()
- # format: type/permissions/alloc/fixed_minor/state/open
- if len(out) != 6:
- return self.STATUS_UNKNOWN
- #writable = (out[1] == "w")
- active = (out[4] == "a")
- online = (out[5] == "o")
- if online:
- retval = self.STATUS_ONLINE
- elif active:
- retval = self.STATUS_STANDBY
- else:
- retval = self.STATUS_EXISTING
-
- return retval
-
def GetSyncStatus(self):
"""Returns the sync status of the device.
This is a no-op for the LV device type.
"""
- return True
+ pass
def Close(self):
"""Notifies that the device will no longer be used for I/O.
This is a no-op for the LV device type.
"""
- return True
+ pass
def Snapshot(self, size):
"""Create a snapshot copy of an lvm block device.
for dev in orig_devs:
self._children.remove(dev)
- def GetStatus(self):
- """Return the status of the device.
-
- """
- self.Attach()
- if self.minor is None:
- retval = self.STATUS_UNKNOWN
- else:
- retval = self.STATUS_ONLINE
- return retval
-
def _SetFromMinor(self, minor):
"""Set our parameters based on the given minor.
the 2.6.18's new array_state thing.
"""
- return True
+ pass
def Close(self):
"""Notifies that the device will no longer be used for I/O.
`Open()`.
"""
- return True
+ pass
class BaseDRBD(BlockDev):
"""
_VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
- r" \(api:(\d+)/proto:(\d+)\)")
+ r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
+
_DRBD_MAJOR = 147
_ST_UNCONFIGURED = "Unconfigured"
_ST_WFCONNECTION = "WFConnection"
def _GetVersion(cls):
"""Return the DRBD version.
- This will return a list [k_major, k_minor, k_point, api, proto].
+ 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()
if not version:
raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
first_line)
- return [int(val) for val in version.groups()]
+
+ 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):
def __init__(self, unique_id, children):
super(DRBDev, self).__init__(unique_id, children)
self.major = self._DRBD_MAJOR
- [kmaj, kmin, kfix, api, proto] = self._GetVersion()
- if kmaj != 0 and kmin != 7:
+ version = self._GetVersion()
+ if version['k_major'] != 0 and version['k_minor'] != 7:
raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
" requested ganeti usage: kernel is"
- " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
-
+ " %s.%s, ganeti wants 0.7" %
+ (version['k_major'], version['k_minor']))
if len(children) != 2:
raise ValueError("Invalid configuration data %s" % str(children))
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
cmd.append("--do-what-I-say")
result = utils.RunCmd(cmd)
if result.failed:
- logger.Error("Can't make drbd device primary: %s" % result.output)
- return False
- return True
+ msg = ("Can't make drbd device primary: %s" % result.output)
+ logger.Error(msg)
+ raise errors.BlockDeviceError(msg)
def Close(self):
"""Make the local state secondary.
raise errors.BlockDeviceError("Can't find device")
result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
if result.failed:
- logger.Error("Can't switch drbd device to secondary: %s" % result.output)
- raise errors.BlockDeviceError("Can't switch drbd device to secondary")
+ msg = ("Can't switch drbd device to"
+ " secondary: %s" % result.output)
+ logger.Error(msg)
+ raise errors.BlockDeviceError(msg)
def SetSyncSpeed(self, kbytes):
"""Set the speed of the DRBD syncer.
is_degraded = client_state != "Connected"
return sync_percent, est_time, is_degraded, False
- def GetStatus(self):
- """Compute the status of the DRBD device
-
- Note that DRBD devices don't have the STATUS_EXISTING state.
-
- """
- if self.minor is None and not self.Attach():
- return self.STATUS_UNKNOWN
-
- data = self._GetProcData()
- match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
- self.minor)
- for line in data:
- mresult = match.match(line)
- if mresult:
- break
- else:
- logger.Error("Can't find myself!")
- return self.STATUS_UNKNOWN
-
- state = mresult.group(2)
- if state == "Primary":
- result = self.STATUS_ONLINE
- else:
- result = self.STATUS_STANDBY
-
- return result
-
@staticmethod
def _ZeroDevice(device):
"""Zero a device.
children = []
super(DRBD8, self).__init__(unique_id, children)
self.major = self._DRBD_MAJOR
- [kmaj, kmin, kfix, api, proto] = self._GetVersion()
- if kmaj != 8:
+ version = self._GetVersion()
+ if version['k_major'] != 8 :
raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
" requested ganeti usage: kernel is"
- " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
+ " %s.%s, ganeti wants 8.x" %
+ (version['k_major'], version['k_minor']))
if len(children) not in (0, 2):
raise ValueError("Invalid configuration data %s" % str(children))
is_degraded = client_state != "Connected"
return sync_percent, est_time, is_degraded or ldisk, ldisk
- def GetStatus(self):
- """Compute the status of the DRBD device
-
- Note that DRBD devices don't have the STATUS_EXISTING state.
-
- """
- if self.minor is None and not self.Attach():
- return self.STATUS_UNKNOWN
-
- data = self._GetProcData()
- match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
- self.minor)
- for line in data:
- mresult = match.match(line)
- if mresult:
- break
- else:
- logger.Error("Can't find myself!")
- return self.STATUS_UNKNOWN
-
- state = mresult.group(2)
- if state == "Primary":
- result = self.STATUS_ONLINE
- else:
- result = self.STATUS_STANDBY
-
- return result
-
def Open(self, force=False):
"""Make the local state primary.
cmd.append("-o")
result = utils.RunCmd(cmd)
if result.failed:
- logger.Error("Can't make drbd device primary: %s" % result.output)
- return False
- return True
+ msg = ("Can't make drbd device primary: %s" % result.output)
+ logger.Error(msg)
+ raise errors.BlockDeviceError(msg)
def Close(self):
"""Make the local state secondary.
raise errors.BlockDeviceError("Can't find device")
result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
if result.failed:
- logger.Error("Can't switch drbd device to secondary: %s" % result.output)
- raise errors.BlockDeviceError("Can't switch drbd device to secondary")
+ msg = ("Can't switch drbd device to"
+ " secondary: %s" % result.output)
+ logger.Error(msg)
+ raise errors.BlockDeviceError(msg)
def Attach(self):
"""Find a DRBD device which matches our config and attach to it.
return cls(unique_id, children)
+class FileStorage(BlockDev):
+ """File device.
+
+ This class represents the a file storage backend device.
+
+ The unique_id for the file device is a (file_driver, file_path) tuple.
+
+ """
+ def __init__(self, unique_id, children):
+ """Initalizes a file device backend.
+
+ """
+ if children:
+ raise errors.BlockDeviceError("Invalid setup for file device")
+ super(FileStorage, self).__init__(unique_id, children)
+ if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+ raise ValueError("Invalid configuration data %s" % str(unique_id))
+ self.driver = unique_id[0]
+ self.dev_path = unique_id[1]
+
+ def Assemble(self):
+ """Assemble the device.
+
+ Checks whether the file device exists, raises BlockDeviceError otherwise.
+
+ """
+ if not os.path.exists(self.dev_path):
+ raise errors.BlockDeviceError("File device '%s' does not exist." %
+ self.dev_path)
+ return True
+
+ def Shutdown(self):
+ """Shutdown the device.
+
+ This is a no-op for the file type, as we don't deacivate
+ the file on shutdown.
+
+ """
+ return True
+
+ def Open(self, force=False):
+ """Make the device ready for I/O.
+
+ This is a no-op for the file type.
+
+ """
+ pass
+
+ def Close(self):
+ """Notifies that the device will no longer be used for I/O.
+
+ This is a no-op for the file type.
+
+ """
+ pass
+
+ def Remove(self):
+ """Remove the file backing the block device.
+
+ Returns:
+ boolean indicating wheter removal of file was successful or not.
+
+ """
+ if not os.path.exists(self.dev_path):
+ return True
+ try:
+ os.remove(self.dev_path)
+ return True
+ except OSError, err:
+ logger.Error("Can't remove file '%s': %s"
+ % (self.dev_path, err))
+ return False
+
+ def Attach(self):
+ """Attach to an existing file.
+
+ Check if this file already exists.
+
+ Returns:
+ boolean indicating if file exists or not.
+
+ """
+ if os.path.exists(self.dev_path):
+ return True
+ return False
+
+ @classmethod
+ def Create(cls, unique_id, children, size):
+ """Create a new file.
+
+ Args:
+ children:
+ size: integer size of file in MiB
+
+ Returns:
+ A ganeti.bdev.FileStorage object.
+
+ """
+ if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+ raise ValueError("Invalid configuration data %s" % str(unique_id))
+ dev_path = unique_id[1]
+ try:
+ f = open(dev_path, 'w')
+ except IOError, err:
+ raise BlockDeviceError("Could not create '%'" % err)
+ else:
+ f.truncate(size * 1024 * 1024)
+ f.close()
+
+ return FileStorage(unique_id, children)
+
+
DEV_MAP = {
constants.LD_LV: LogicalVolume,
constants.LD_MD_R1: MDRaid1,
constants.LD_DRBD7: DRBDev,
constants.LD_DRBD8: DRBD8,
+ constants.LD_FILE: FileStorage,
}