Hi, Following patch 06, I changed the way bdev_list is called during disk adoption. Also, gone are all DRBD-related methods (GetSyncStatus(), AttachNet(), DisconnectNet()).
The revised patch follows. Apollon --- This patch introduces basic shared block storage support. It introduces a new storage backend, bdev.PersistentBlockDevice, to use as a backend for shared block storage. A new disk template, DT_BLOCK is introduced as well and added to DTS_EXT_MIRROR and DTS_MAY_ADOPT. This is very basic support and includes no storage manipulation (provisioning, resizing, renaming) which will have to be implemented through a "driver" framework. Signed-off-by: Apollon Oikonomopoulos <[email protected]> --- lib/bdev.py | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++- lib/cmdlib.py | 102 +++++++++++++++++++++++++++++++++------------------- lib/constants.py | 14 ++++--- lib/objects.py | 11 ++++-- 4 files changed, 184 insertions(+), 48 deletions(-) diff --git a/lib/bdev.py b/lib/bdev.py index c321508..2734af6 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -1,4 +1,4 @@ -# + # # Copyright (C) 2006, 2007 Google Inc. @@ -26,6 +26,7 @@ import time import errno import pyparsing as pyp import os +import stat import logging from ganeti import utils @@ -1954,10 +1955,112 @@ class FileStorage(BlockDev): return FileStorage(unique_id, children, size) +class PersistentBlockDevice(BlockDev): + """A block device with persistent node + + May be either directly attached, or exposed through DM (e.g. dm-multipath). + udev helpers are probably required to give persistent, human-friendly + names. + + For the time being, pathnames are required to lie under /dev. + + """ + def __init__(self, unique_id, children, size): + """Attaches to a static block device. + + The unique_id is a path under /dev. + + """ + super(PersistentBlockDevice, self).__init__(unique_id, children, size) + if not isinstance(unique_id, (tuple, list)): + raise ValueError("Invalid configuration data %s" % str(unique_id)) + self.dev_path = unique_id[1] + if not os.path.realpath(self.dev_path).startswith('/dev/'): + raise ValueError("Full path '%s' lies outside /dev" % + os.path.realpath(self.dev_path)) + self.major = self.minor = None + self.Attach() + + @classmethod + def Create(cls, unique_id, children, size): + """Create a new device + + This is a noop, we only return a PersistentBlockDevice instance + + """ + return PersistentBlockDevice(unique_id, children, size=0) + + def Remove(self): + """Remove a device + + This is a noop + + """ + pass + + def Rename(self, new_id): + """Rename this device. + + """ + raise NotImplementedError + + def Attach(self): + """Attach to an existing block device. + + + """ + self.attached = False + try: + st = os.stat(self.dev_path) + except OSError, err: + logging.error("Error stat()'ing %s: %s" % (self.dev_path, str(err))) + return False + + if not stat.S_ISBLK(st[stat.ST_MODE]): + logging.error("%s is not a block device", self.dev_path) + return False + + self.major = os.major(st.st_rdev) + self.minor = os.minor(st.st_rdev) + self.attached = True + + return True + + def Assemble(self): + """Assemble the device. + + """ + pass + + def Shutdown(self): + """Shutdown the device. + + """ + pass + + def Open(self, force=False): + """Make the device ready for I/O. + + """ + pass + + def Close(self): + """Notifies that the device will no longer be used for I/O. + + """ + pass + + def Grow(self, amount): + """Grow the logical volume. + + """ + raise NotImplementedError + DEV_MAP = { constants.LD_LV: LogicalVolume, constants.LD_DRBD8: DRBD8, + constants.LD_BLOCK: PersistentBlockDevice, } if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE: diff --git a/lib/cmdlib.py b/lib/cmdlib.py index f6d530d..153cfb5 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -6200,6 +6200,18 @@ def _GenerateDiskTemplate(lu, template_name, disk_index)), mode=disk["mode"]) disks.append(disk_dev) + elif template_name == constants.DT_BLOCK: + if len(secondary_nodes) != 0: + raise errors.ProgrammerError("Wrong template configuration") + + for idx, disk in enumerate(disk_info): + disk_index = idx + base_index + disk_dev = objects.Disk(dev_type=constants.LD_BLOCK, size=disk["size"], + logical_id=('dummy', disk["adopt"]), + iv_name="disk/%d" % disk_index, + mode=disk["mode"]) + disks.append(disk_dev) + else: raise errors.ProgrammerError("Invalid disk template '%s'" % template_name) return disks @@ -6318,6 +6330,7 @@ def _ComputeDiskSize(disk_template, disks): constants.DT_DRBD8: sum(d["size"] + 128 for d in disks), constants.DT_FILE: None, constants.DT_SHARED_FILE: 0, + constants.DT_BLOCK: 0, } if disk_template not in req_size_dict: @@ -7062,34 +7075,46 @@ class LUCreateInstance(LogicalUnit): _CheckNodesFreeDisk(self, nodenames, req_size) if self.adopt_disks: # instead, we must check the adoption data - all_lvs = set([i["adopt"] for i in self.disks]) - if len(all_lvs) != len(self.disks): - raise errors.OpPrereqError("Duplicate volume names given for adoption", + all_disks = set([i["adopt"] for i in self.disks]) + if len(all_disks) != len(self.disks): + raise errors.OpPrereqError("Duplicate disk names given for adoption", errors.ECODE_INVAL) - for lv_name in all_lvs: - try: - self.cfg.ReserveLV(lv_name, self.proc.GetECId()) - except errors.ReservationError: - raise errors.OpPrereqError("LV named %s used by another instance" % - lv_name, errors.ECODE_NOTUNIQUE) - - node_lvs = self.rpc.call_lv_list([pnode.name], - self.cfg.GetVGName())[pnode.name] - node_lvs.Raise("Cannot get LV information from node %s" % pnode.name) - node_lvs = node_lvs.payload - delta = all_lvs.difference(node_lvs.keys()) - if delta: - raise errors.OpPrereqError("Missing logical volume(s): %s" % - utils.CommaJoin(delta), - errors.ECODE_INVAL) - online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]] - if online_lvs: - raise errors.OpPrereqError("Online logical volumes found, cannot" - " adopt: %s" % utils.CommaJoin(online_lvs), - errors.ECODE_STATE) - # update the size of disk based on what is found - for dsk in self.disks: - dsk["size"] = int(float(node_lvs[dsk["adopt"]][0])) + if self.op.disk_template == constants.DT_PLAIN: + for lv_name in all_disks: + try: + self.cfg.ReserveLV(lv_name, self.proc.GetECId()) + except errors.ReservationError: + raise errors.OpPrereqError("LV named %s used by another instance" % + lv_name, errors.ECODE_NOTUNIQUE) + + node_lvs = self.rpc.call_lv_list([pnode.name], + self.cfg.GetVGName())[pnode.name] + node_lvs.Raise("Cannot get LV information from node %s" % pnode.name) + node_lvs = node_lvs.payload + delta = all_disks.difference(node_lvs.keys()) + if delta: + raise errors.OpPrereqError("Missing logical volume(s): %s" % + utils.CommaJoin(delta), + errors.ECODE_INVAL) + online_lvs = [lv for lv in all_disks if node_lvs[lv][2]] + if online_lvs: + raise errors.OpPrereqError("Online logical volumes found, cannot" + " adopt: %s" % utils.CommaJoin(online_lvs), + errors.ECODE_STATE) + # update the size of disk based on what is found + for dsk in self.disks: + dsk["size"] = int(float(node_lvs[dsk["adopt"]][0])) + elif self.op.disk_template == constants.DT_BLOCK: + node_disks = self.rpc.call_bdev_list([pnode.name], + list(all_disks))[pnode.name] + node_disks = node_disks.payload + delta = all_disks.difference(node_disks.keys()) + if delta: + raise errors.OpPrereqError("Missing block device(s): %s" % + utils.CommaJoin(delta), + errors.ECODE_INVAL) + for dsk in self.disks: + dsk["size"] = int(float(node_disks[dsk["adopt"]])) _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams) @@ -7161,17 +7186,18 @@ class LUCreateInstance(LogicalUnit): ) if self.adopt_disks: - # rename LVs to the newly-generated names; we need to construct - # 'fake' LV disks with the old data, plus the new unique_id - tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks] - rename_to = [] - for t_dsk, a_dsk in zip (tmp_disks, self.disks): - rename_to.append(t_dsk.logical_id) - t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"]) - self.cfg.SetDiskID(t_dsk, pnode_name) - result = self.rpc.call_blockdev_rename(pnode_name, - zip(tmp_disks, rename_to)) - result.Raise("Failed to rename adoped LVs") + if self.op.disk_template == constants.DT_PLAIN: + # rename LVs to the newly-generated names; we need to construct + # 'fake' LV disks with the old data, plus the new unique_id + tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks] + rename_to = [] + for t_dsk, a_dsk in zip (tmp_disks, self.disks): + rename_to.append(t_dsk.logical_id) + t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"]) + self.cfg.SetDiskID(t_dsk, pnode_name) + result = self.rpc.call_blockdev_rename(pnode_name, + zip(tmp_disks, rename_to)) + result.Raise("Failed to rename adoped LVs") else: feedback_fn("* creating instance disks...") try: diff --git a/lib/constants.py b/lib/constants.py index 1f0b3aa..d37096f 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -319,15 +319,16 @@ DT_PLAIN = "plain" DT_DRBD8 = "drbd" DT_FILE = "file" DT_SHARED_FILE = "sharedfile" +DT_BLOCK = "blockdev" # the set of network-mirrored disk templates DTS_NET_MIRROR = frozenset([DT_DRBD8]) -# the set of externally mirrored disk templates -DTS_EXT_MIRROR = frozenset([DT_SHARED_FILE]) +# the set of externally-mirrored disk templates (e.g. SAN, NAS) +DTS_EXT_MIRROR = frozenset([DT_SHARED_FILE, DT_BLOCK]) # the set of non-lvm-based disk templates -DTS_NOT_LVM = frozenset([DT_DISKLESS, DT_FILE, DT_SHARED_FILE]) +DTS_NOT_LVM = frozenset([DT_DISKLESS, DT_FILE, DT_SHARED_FILE, DT_BLOCK]) # the set of disk templates which can be grown DTS_GROWABLE = frozenset([DT_PLAIN, DT_DRBD8, DT_FILE, DT_SHARED_FILE]) @@ -339,13 +340,14 @@ DTS_MAY_ADOPT = frozenset([DT_PLAIN]) DTS_MIRRORED = frozenset.union(DTS_NET_MIRROR, DTS_EXT_MIRROR) # the set of disk templates that allow adoption -DTS_MAY_ADOPT = frozenset([DT_PLAIN]) +DTS_MAY_ADOPT = frozenset([DT_PLAIN, DT_BLOCK]) # logical disk types LD_LV = "lvm" LD_DRBD8 = "drbd8" LD_FILE = "file" -LDS_BLOCK = frozenset([LD_LV, LD_DRBD8]) +LD_BLOCK = "blockdev" +LDS_BLOCK = frozenset([LD_LV, LD_DRBD8, LD_BLOCK]) # drbd constants DRBD_HMAC_ALG = "md5" @@ -409,7 +411,7 @@ RIE_CERT_VALIDITY = 24 * 60 * 60 RIE_CONNECT_TIMEOUT = 60 DISK_TEMPLATES = frozenset([DT_DISKLESS, DT_PLAIN, DT_DRBD8, - DT_FILE, DT_SHARED_FILE]) + DT_FILE, DT_SHARED_FILE, DT_BLOCK]) FILE_DRIVER = frozenset([FD_LOOP, FD_BLKTAP]) diff --git a/lib/objects.py b/lib/objects.py index d6cf527..6d26bd7 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -396,11 +396,13 @@ class Disk(ConfigObject): def CreateOnSecondary(self): """Test if this device needs to be created on a secondary node.""" - return self.dev_type in (constants.LD_DRBD8, constants.LD_LV) + return self.dev_type in (constants.LD_DRBD8, constants.LD_LV, + constants.LD_BLOCK) def AssembleOnSecondary(self): """Test if this device needs to be assembled on a secondary node.""" - return self.dev_type in (constants.LD_DRBD8, constants.LD_LV) + return self.dev_type in (constants.LD_DRBD8, constants.LD_LV, + constants.LD_BLOCK) def OpenOnSecondary(self): """Test if this device needs to be opened on a secondary node.""" @@ -419,6 +421,8 @@ class Disk(ConfigObject): """ if self.dev_type == constants.LD_LV: return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) + elif self.dev_type == constants.LD_BLOCK: + return self.logical_id[1] return None def ChildrenNeeded(self): @@ -446,7 +450,8 @@ class Disk(ConfigObject): devices needs to (or can) be assembled. """ - if self.dev_type in [constants.LD_LV, constants.LD_FILE] + if self.dev_type in [constants.LD_LV, constants.LD_FILE, + constants.LD_BLOCK]: result = [node] elif self.dev_type in constants.LDS_DRBD: result = [self.logical_id[0], self.logical_id[1]] -- 1.7.1
