Config modifications are now done using
wconfd RPCs. However, offline cluster
updates are still allowed.

Added wrappers CommitTemporaryIps, and VerifyConfigAndLog
to perform read only config operations with lock.

Add a mock for Update function, and use
the mock config object in two config tests.

Remove config tests on Update

Signed-off-by: BSRK Aditya <[email protected]>
---
 lib/config/__init__.py            |   76 ++++++++++++++++++++-----------------
 test/py/ganeti.config_unittest.py |   58 +---------------------------
 test/py/testutils/config_mock.py  |   26 +++++++++++++
 3 files changed, 69 insertions(+), 91 deletions(-)

diff --git a/lib/config/__init__.py b/lib/config/__init__.py
index cc6efe7..61e5c20 100644
--- a/lib/config/__init__.py
+++ b/lib/config/__init__.py
@@ -668,6 +668,11 @@ class ConfigWriter(object):
     """
     self._wconfd.ReserveMAC(self._GetWConfdContext(), mac)
 
+  @ConfigSync(shared=1)
+  def CommitTemporaryIps(self, _ec_id):
+    """A simple wrapper around L{_UnlockedCommitTemporaryIps}"""
+    self._UnlockedCommitTemporaryIps(_ec_id)
+
   def _UnlockedCommitTemporaryIps(self, _ec_id):
     """Commit all reserved IP address to their respective pools
 
@@ -1092,6 +1097,11 @@ class ConfigWriter(object):
 
     return result
 
+  @ConfigSync(shared=1)
+  def VerifyConfigAndLog(self, feedback_fn=None):
+    """A simple wrapper around L{_UnlockedVerifyConfigAndLog}"""
+    return self._UnlockedVerifyConfigAndLog(feedback_fn=feedback_fn)
+
   def _UnlockedVerifyConfigAndLog(self, feedback_fn=None):
     """Verify the configuration and log any errors.
 
@@ -3102,7 +3112,6 @@ class ConfigWriter(object):
     """
     return DetachedConfig(self._ConfigData())
 
-  @ConfigSync()
   def Update(self, target, feedback_fn, ec_id=None):
     """Notify function to be called after updates.
 
@@ -3118,60 +3127,57 @@ class ConfigWriter(object):
     @param feedback_fn: Callable feedback function
 
     """
-    if self._ConfigData() is None:
-      raise errors.ProgrammerError("Configuration file not read,"
-                                   " cannot save.")
 
-    def check_serial(target, current):
-      if current is None:
-        raise errors.ConfigurationError("Configuration object unknown")
-      elif current.serial_no != target.serial_no:
-        raise errors.ConfigurationError("Configuration object updated since"
-                                        " it has been read: %d != %d",
-                                        current.serial_no, target.serial_no)
-
-    def replace_in(target, tdict):
-      check_serial(target, tdict.get(target.uuid))
-      tdict[target.uuid] = target
-
-    update_serial = False
+    update_function = None
     if isinstance(target, objects.Cluster):
-      check_serial(target, self._ConfigData().cluster)
-      self._ConfigData().cluster = target
+      if self._offline:
+        self.UpdateOfflineCluster(target, feedback_fn)
+        return
+      else:
+        update_function = self._wconfd.UpdateCluster
     elif isinstance(target, objects.Node):
-      replace_in(target, self._ConfigData().nodes)
-      update_serial = True
+      update_function = self._wconfd.UpdateNode
     elif isinstance(target, objects.Instance):
-      replace_in(target, self._ConfigData().instances)
+      update_function = self._wconfd.UpdateInstance
     elif isinstance(target, objects.NodeGroup):
-      replace_in(target, self._ConfigData().nodegroups)
+      update_function = self._wconfd.UpdateNodeGroup
     elif isinstance(target, objects.Network):
-      replace_in(target, self._ConfigData().networks)
+      update_function = self._wconfd.UpdateNetwork
     elif isinstance(target, objects.Disk):
-      replace_in(target, self._ConfigData().disks)
+      update_function = self._wconfd.UpdateDisk
     else:
       raise errors.ProgrammerError("Invalid object type (%s) passed to"
                                    " ConfigWriter.Update" % type(target))
-    target.serial_no += 1
-    target.mtime = now = time.time()
 
-    if update_serial:
-      # for node updates, we need to increase the cluster serial too
-      self._ConfigData().cluster.serial_no += 1
-      self._ConfigData().cluster.mtime = now
+    def WithRetry():
+      result = update_function(target.ToDict())
+      self.OutDate()
 
-    if isinstance(target, objects.Disk):
-      self._UnlockedReleaseDRBDMinors(target.uuid)
+      if result is None:
+        raise utils.RetryAgain()
+      else:
+        return float(result)
+    mtime = utils.Retry(WithRetry, 0.1, 30)
+    self.OutDate()
+    target.serial_no += 1
+    target.mtime = mtime
 
     if ec_id is not None:
       # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
       # FIXME: After RemoveInstance is moved to WConfd, use its internal
       # functions from TempRes module.
-      self._UnlockedCommitTemporaryIps(ec_id)
+      self.CommitTemporaryIps(ec_id)
 
     # Just verify the configuration with our feedback function.
     # It will get written automatically by the decorator.
-    self._UnlockedVerifyConfigAndLog(feedback_fn=feedback_fn)
+    self.VerifyConfigAndLog(feedback_fn=feedback_fn)
+
+  @ConfigSync()
+  def UpdateOfflineCluster(self, target, feedback_fn):
+    self._ConfigData().cluster = target
+    target.serial_no += 1
+    target.mtime = time.time()
+    self.VerifyConfigAndLog(feedback_fn=feedback_fn)
 
   def _UnlockedDropECReservations(self, _ec_id):
     """Drop per-execution-context reservations
diff --git a/test/py/ganeti.config_unittest.py 
b/test/py/ganeti.config_unittest.py
index e56c723..2940a0a 100755
--- a/test/py/ganeti.config_unittest.py
+++ b/test/py/ganeti.config_unittest.py
@@ -303,60 +303,6 @@ class TestConfigRunner(unittest.TestCase):
       node2.uuid: ["myxenvg/disk0", "myxenvg/meta0"],
       })
 
-  def testUpdateCluster(self):
-    """Test updates on the cluster object"""
-    cfg = self._get_object()
-    # construct a fake cluster object
-    fake_cl = objects.Cluster()
-    # fail if we didn't read the config
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
-
-    cl = cfg.GetClusterInfo()
-    # first pass, must not fail
-    cfg.Update(cl, None)
-    # second pass, also must not fail (after the config has been written)
-    cfg.Update(cl, None)
-    # but the fake_cl update should still fail
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
-
-  def testUpdateNode(self):
-    """Test updates on one node object"""
-    cfg = self._get_object()
-    # construct a fake node
-    fake_node = objects.Node()
-    # fail if we didn't read the config
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
-                          None)
-
-    node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
-    # first pass, must not fail
-    cfg.Update(node, None)
-    # second pass, also must not fail (after the config has been written)
-    cfg.Update(node, None)
-    # but the fake_node update should still fail
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
-                          None)
-
-  def testUpdateInstance(self):
-    """Test updates on one instance object"""
-    cfg = self._get_object_mock()
-    # construct a fake instance
-    inst = self._create_instance(cfg)
-    fake_instance = objects.Instance()
-    # fail if we didn't read the config
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
-                          None)
-
-    cfg.AddInstance(inst, "my-job")
-    instance = cfg.GetInstanceInfo(cfg.GetInstanceList()[0])
-    # first pass, must not fail
-    cfg.Update(instance, None)
-    # second pass, also must not fail (after the config has been written)
-    cfg.Update(instance, None)
-    # but the fake_instance update should still fail
-    self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
-                          None)
-
   def testUpgradeSave(self):
     """Test that any modification done during upgrading is saved back"""
     cfg = self._get_object()
@@ -430,7 +376,7 @@ class TestConfigRunner(unittest.TestCase):
         constants.ND_CPU_SPEED: 1.0,
         }
 
-    cfg = self._get_object()
+    cfg = self._get_object_mock()
     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
     node.ndparams = my_ndparams
     cfg.Update(node, None)
@@ -458,7 +404,7 @@ class TestConfigRunner(unittest.TestCase):
       constants.ND_SSH_PORT: 222,
       constants.ND_CPU_SPEED: 1.0,
       }
-    cfg = self._get_object()
+    cfg = self._get_object_mock()
     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
     node.ndparams = node_ndparams
     cfg.Update(node, None)
diff --git a/test/py/testutils/config_mock.py b/test/py/testutils/config_mock.py
index e994bda..7c06fbc 100644
--- a/test/py/testutils/config_mock.py
+++ b/test/py/testutils/config_mock.py
@@ -892,3 +892,29 @@ class ConfigMock(config.ConfigWriter):
 
   def AllocatePort(self):
     return 1
+
+  def Update(self, target, feedback_fn, ec_id=None):
+    def replace_in(target, tdict):
+      tdict[target.uuid] = target
+
+    update_serial = False
+    if isinstance(target, objects.Cluster):
+      self._ConfigData().cluster = target
+    elif isinstance(target, objects.Node):
+      replace_in(target, self._ConfigData().nodes)
+      update_serial = True
+    elif isinstance(target, objects.Instance):
+      replace_in(target, self._ConfigData().instances)
+    elif isinstance(target, objects.NodeGroup):
+      replace_in(target, self._ConfigData().nodegroups)
+    elif isinstance(target, objects.Network):
+      replace_in(target, self._ConfigData().networks)
+    elif isinstance(target, objects.Disk):
+      replace_in(target, self._ConfigData().disks)
+
+    target.serial_no += 1
+    target.mtime = now = time.time()
+
+    if update_serial:
+      self._ConfigData().cluster.serial_no += 1 # pylint: disable=E1103
+      self._ConfigData().cluster.mtime = now
-- 
1.7.10.4

Reply via email to