Check that device names are unique and valid
authorChristos Stavrakakis <cstavr@grnet.gr>
Thu, 4 Apr 2013 08:51:10 +0000 (11:51 +0300)
committerHelga Velroyen <helgav@google.com>
Wed, 17 Apr 2013 14:49:36 +0000 (16:49 +0200)
Extend the CheckArguments phase of LUInstanceCreate and CheckPrereq
phase of LUInstanceSetParams to also check if the name parameters of
disks and NICs are unique and valid.

Signed-off-by: Christos Stavrakakis <cstavr@grnet.gr>
Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>
Reviewed-by: Helga Velroyen <helgav@google.com>

lib/cmdlib.py
lib/utils/__init__.py
test/py/ganeti.utils_unittest.py

index 9e09583..1995b2b 100644 (file)
@@ -10186,6 +10186,11 @@ class LUInstanceCreate(LogicalUnit):
     # check nics' parameter names
     for nic in self.op.nics:
       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
+    # check that NIC's parameters names are unique and valid
+    utils.ValidateDeviceNames("NIC", self.op.nics)
+
+    # check that disk's names are unique and valid
+    utils.ValidateDeviceNames("disk", self.op.disks)
 
     cluster = self.cfg.GetClusterInfo()
     if not self.op.disk_template in cluster.enabled_disk_templates:
@@ -14009,9 +14014,14 @@ class LUInstanceSetParams(LogicalUnit):
                                  " (%d), cannot add more" % constants.MAX_NICS,
                                  errors.ECODE_STATE)
 
+    def _PrepareDiskMod(_, disk, params, __):
+      disk.name = params.get(constants.IDISK_NAME, None)
+
     # Verify disk changes (operating on a copy)
-    disks = instance.disks[:]
-    ApplyContainerMods("disk", disks, None, self.diskmod, None, None, None)
+    disks = copy.deepcopy(instance.disks)
+    ApplyContainerMods("disk", disks, None, self.diskmod, None, _PrepareDiskMod,
+                       None)
+    utils.ValidateDeviceNames("disk", disks)
     if len(disks) > constants.MAX_DISKS:
       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
                                  " more" % constants.MAX_DISKS,
@@ -14033,6 +14043,8 @@ class LUInstanceSetParams(LogicalUnit):
       nics = [nic.Copy() for nic in instance.nics]
       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
                          self._CreateNewNic, self._ApplyNicMods, None)
+      # Verify that NIC names are unique and valid
+      utils.ValidateDeviceNames("NIC", nics)
       self._new_nics = nics
       ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
     else:
index d3319c1..cda87f4 100644 (file)
@@ -797,3 +797,47 @@ class FieldSet(object):
 
     """
     return [val for val in items if not self.Matches(val)]
+
+
+def ValidateDeviceNames(kind, container):
+  """Validate instance device names.
+
+  Check that a device container contains only unique and valid names.
+
+  @type kind: string
+  @param kind: One-word item description
+  @type container: list
+  @param container: Container containing the devices
+
+  """
+
+  valid = []
+  for device in container:
+    if isinstance(device, dict):
+      if kind == "NIC":
+        name = device.get(constants.INIC_NAME, None)
+      elif kind == "disk":
+        name = device.get(constants.IDISK_NAME, None)
+      else:
+        raise errors.OpPrereqError("Invalid container kind '%s'" % kind,
+                                   errors.ECODE_INVAL)
+    else:
+      name = device.name
+      # Check that a device name is not the UUID of another device
+      valid.append(device.uuid)
+
+    try:
+      int(name)
+    except (ValueError, TypeError):
+      pass
+    else:
+      raise errors.OpPrereqError("Invalid name '%s'. Purely numeric %s names"
+                                 " are not allowed" % (name, kind),
+                                 errors.ECODE_INVAL)
+
+    if name is not None and name.lower() != constants.VALUE_NONE:
+      if name in valid:
+        raise errors.OpPrereqError("%s name '%s' already used" % (kind, name),
+                                   errors.ECODE_NOTUNIQUE)
+      else:
+        valid.append(name)
index 343e029..f57b713 100755 (executable)
@@ -369,5 +369,26 @@ class TestVerifyDictOptions(unittest.TestCase):
                       some_keys, self.defaults)
 
 
+class TestValidateDeviceNames(unittest.TestCase):
+  def testEmpty(self):
+    utils.ValidateDeviceNames("NIC", [])
+    utils.ValidateDeviceNames("disk", [])
+
+  def testNoName(self):
+    nics = [{}, {}]
+    utils.ValidateDeviceNames("NIC", nics)
+
+  def testInvalidName(self):
+    self.assertRaises(errors.OpPrereqError, utils.ValidateDeviceNames,
+                      "disk", [{constants.IDISK_NAME: "42"}])
+    self.assertRaises(errors.OpPrereqError, utils.ValidateDeviceNames,
+                      "NIC", [{constants.INIC_NAME: "42"}])
+
+  def testUsedName(self):
+    disks = [{constants.IDISK_NAME: "name1"}, {constants.IDISK_NAME: "name1"}]
+    self.assertRaises(errors.OpPrereqError, utils.ValidateDeviceNames,
+                      "disk", disks)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()