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
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",
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
The status was already read in Attach, so we just return it.
+ @rtype: tuple
+ @return: (sync_percent, estimated_time, is_degraded, ldisk)
+
"""
return None, None, self._degraded, self._degraded
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()
_MAX_MINORS = 255
_PARSE_SHOW = None
+ # timeout constants
+ _NET_RECONFIG_TIMEOUT = 60
+
def __init__(self, unique_id, children):
if children and children.count(None) > 0:
children = []
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
# sure its shutdown
return cls._ShutdownNet(minor)
+ # Workaround for a race condition. When DRBD is doing its dance to
+ # establish a connection with its peer, it also sends the
+ # synchronization speed over the wire. In some cases setting the
+ # sync speed only after setting up both sides can race with DRBD
+ # connecting, hence we set it here before telling DRBD anything
+ # about its peer.
+ cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
+
args = ["drbdsetup", cls._DevPath(minor), "net",
"%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
"-A", "discard-zero-changes",
raise errors.BlockDeviceError("Can't detach from local storage")
self._children = []
- def SetSyncSpeed(self, kbytes):
+ @classmethod
+ def _SetMinorSyncSpeed(cls, minor, kbytes):
"""Set the speed of the DRBD syncer.
+ This is the low-level implementation.
+
+ @type minor: int
+ @param minor: the drbd minor whose settings we change
+ @type kbytes: int
+ @param kbytes: the speed in kbytes/second
+ @rtype: boolean
+ @return: the success of the operation
+
"""
- children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
- if self.minor is None:
- logging.info("Instance not attached to a device")
- return False
- result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
- kbytes])
+ result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
+ "-r", "%d" % kbytes, "--create-device"])
if result.failed:
logging.error("Can't change syncer rate: %s - %s",
result.fail_reason, result.output)
- return not result.failed and children_result
+ return not result.failed
+
+ def SetSyncSpeed(self, kbytes):
+ """Set the speed of the DRBD syncer.
+
+ @type kbytes: int
+ @param kbytes: the speed in kbytes/second
+ @rtype: boolean
+ @return: the success of the operation
+
+ """
+ if self.minor is None:
+ logging.info("Not attached during SetSyncSpeed")
+ return False
+ children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
+ return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
def GetProcStatus(self):
"""Return device data from /proc.
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")
logging.error(msg)
raise errors.BlockDeviceError(msg)
+ def DisconnectNet(self):
+ """Removes network configuration.
+
+ This method shutdowns the network side of the device.
+
+ The method will wait up to a hardcoded timeout for the device to
+ go into standalone after the 'disconnect' command before
+ re-configuring it, as sometimes it takes a while for the
+ disconnect to actually propagate and thus we might issue a 'net'
+ command while the device is still connected. If the device will
+ still be attached to the network and we time out, we raise an
+ exception.
+
+ """
+ if self.minor is None:
+ raise errors.BlockDeviceError("DRBD disk not attached in re-attach net")
+
+ if None in (self._lhost, self._lport, self._rhost, self._rport):
+ raise errors.BlockDeviceError("DRBD disk missing network info in"
+ " DisconnectNet()")
+
+ ever_disconnected = self._ShutdownNet(self.minor)
+ timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
+ sleep_time = 0.100 # we start the retry time at 100 miliseconds
+ while time.time() < timeout_limit:
+ status = self.GetProcStatus()
+ if status.is_standalone:
+ break
+ # retry the disconnect, it seems possible that due to a
+ # well-time disconnect on the peer, my disconnect command might
+ # be ingored and forgotten
+ ever_disconnected = self._ShutdownNet(self.minor) or ever_disconnected
+ time.sleep(sleep_time)
+ sleep_time = min(2, sleep_time * 1.5)
+
+ if not status.is_standalone:
+ if ever_disconnected:
+ msg = ("Device did not react to the"
+ " 'disconnect' command in a timely manner")
+ else:
+ msg = ("Can't shutdown network, even after multiple retries")
+ raise errors.BlockDeviceError(msg)
+
+ reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
+ if reconfig_time > 15: # hardcoded alert limit
+ logging.debug("DRBD8.DisconnectNet: detach took %.3f seconds",
+ reconfig_time)
+
+ def AttachNet(self, multimaster):
+ """Reconnects the network.
+
+ This method connects the network side of the device with a
+ specified multi-master flag. The device needs to be 'Standalone'
+ but have valid network configuration data.
+
+ Args:
+ - multimaster: init the network in dual-primary mode
+
+ """
+ if self.minor is None:
+ raise errors.BlockDeviceError("DRBD disk not attached in AttachNet")
+
+ if None in (self._lhost, self._lport, self._rhost, self._rport):
+ raise errors.BlockDeviceError("DRBD disk missing network info in"
+ " AttachNet()")
+
+ status = self.GetProcStatus()
+
+ if not status.is_standalone:
+ raise errors.BlockDeviceError("Device is not standalone in AttachNet")
+
+ return self._AssembleNet(self.minor,
+ (self._lhost, self._lport,
+ self._rhost, self._rport),
+ "C", dual_pri=multimaster)
+
def Attach(self):
"""Find a DRBD device which matches our config and attach to it.
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
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
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):
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:
device = DEV_MAP[dev_type](unique_id, children)
if not device.attached:
return None
- return device
+ return device
def AttachOrAssemble(dev_type, unique_id, children):