From: Apollon Oikonomopoulos <[email protected]>

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      |  137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/cmdlib.py    |  102 +++++++++++++++++++++++++---------------
 lib/constants.py |   14 +++--
 lib/objects.py   |   11 +++-
 4 files changed, 217 insertions(+), 47 deletions(-)

diff --git a/lib/bdev.py b/lib/bdev.py
index 3d68aea..728b51f 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -1937,10 +1937,147 @@ 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
+
+  def GetSyncStatus(self):
+    """Returns the sync status of the device.
+
+    If this device is a mirroring device, this function returns the
+    status of the mirror.
+
+    If the persistent block device name exists, it is assumed to be in
+    sync.
+
+    The status was already read in Attach, so we just return it.
+
+    @rtype: objects.BlockDevStatus
+
+    """
+
+    if self.attached:
+      ldisk_status = constants.LDS_OKAY
+    else:
+      ldisk_status = constants.LDS_FAULTY
+
+    return objects.BlockDevStatus(dev_path=self.dev_path,
+                                  major=self.major,
+                                  minor=self.minor,
+                                  sync_percent=None,
+                                  estimated_time=None,
+                                  is_degraded=False,
+                                  ldisk_status=ldisk_status)
+
+
+  def DisconnectNet(self):
+    pass
+
+  def AttachNet(self, multimaster):
+    pass
+
 
 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 fec6c75..9e49e8d 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -5963,6 +5963,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
@@ -6081,6 +6093,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:
@@ -6772,34 +6785,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])[pnode.name]
+        node_disks = node_disks.payload
+        logging.info("node_disks: %s", node_disks)
+        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)
 
@@ -6868,17 +6893,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 9311dcb..f5f548a 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -318,15 +318,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])
@@ -335,13 +336,14 @@ DTS_GROWABLE = frozenset([DT_PLAIN, DT_DRBD8, DT_FILE, 
DT_SHARED_FILE])
 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"
@@ -399,7 +401,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 0e1fbfe..392d49b 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -395,11 +395,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."""
@@ -418,6 +420,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):
@@ -445,7 +449,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

Reply via email to