Migrate tools/* from constants to pathutils
[ganeti-local] / test / ganeti.hooks_unittest.py
index 2f5f8b0..f7384bd 100755 (executable)
@@ -34,14 +34,25 @@ from ganeti import mcpu
 from ganeti import backend
 from ganeti import constants
 from ganeti import cmdlib
+from ganeti import rpc
+from ganeti import compat
+from ganeti import pathutils
 from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP
 
 from mocks import FakeConfig, FakeProc, FakeContext
 
+import testutils
+
+
 class FakeLU(cmdlib.LogicalUnit):
   HPATH = "test"
+
   def BuildHooksEnv(self):
-    return {}, ["localhost"], ["localhost"]
+    return {}
+
+  def BuildHooksNodes(self):
+    return ["localhost"], ["localhost"]
+
 
 class TestHooksRunner(unittest.TestCase):
   """Testing case for HooksRunner"""
@@ -181,11 +192,24 @@ class TestHooksRunner(unittest.TestCase):
       os.symlink("/usr/bin/env", fname)
       self.torm.append((fname, False))
       env_snt = {"PHASE": phase}
-      env_exp = "PHASE=%s\n" % phase
+      env_exp = "PHASE=%s" % phase
       self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
                            [(self._rname(fname), HKR_SUCCESS, env_exp)])
 
 
+def FakeHooksRpcSuccess(node_list, hpath, phase, env):
+  """Fake call_hooks_runner function.
+
+  @rtype: dict of node -> L{rpc.RpcResult} with a successful script result
+  @return: script execution from all nodes
+
+  """
+  rr = rpc.RpcResult
+  return dict([(node, rr((True, [("utest", constants.HKR_SUCCESS, "ok")]),
+                         node=node, call='FakeScriptOk'))
+               for node in node_list])
+
+
 class TestHooksMaster(unittest.TestCase):
   """Testing case for HooksMaster"""
 
@@ -197,49 +221,43 @@ class TestHooksMaster(unittest.TestCase):
   def _call_nodes_false(node_list, hpath, phase, env):
     """Fake call_hooks_runner function.
 
-    Returns:
-      - list of False values with the same len as the node_list argument
+    @rtype: dict of node -> L{rpc.RpcResult} with an rpc error
+    @return: rpc failure from all nodes
 
     """
-    return [False for node_name in node_list]
+    return dict([(node, rpc.RpcResult('error', failed=True,
+                  node=node, call='FakeError')) for node in node_list])
 
   @staticmethod
   def _call_script_fail(node_list, hpath, phase, env):
     """Fake call_hooks_runner function.
 
-    Returns:
-      - list of False values with the same len as the node_list argument
-
-    """
-    return dict([(node_name, [("unittest", constants.HKR_FAIL, "error")])
-                 for node_name in node_list])
-
-  @staticmethod
-  def _call_script_succeed(node_list, hpath, phase, env):
-    """Fake call_hooks_runner function.
-
-    Returns:
-      - list of False values with the same len as the node_list argument
+    @rtype: dict of node -> L{rpc.RpcResult} with a failed script result
+    @return: script execution failure from all nodes
 
     """
-    return dict([(node_name, [("unittest", constants.HKR_SUCCESS, "ok")])
-                 for node_name in node_list])
+    rr = rpc.RpcResult
+    return dict([(node, rr((True, [("utest", constants.HKR_FAIL, "err")]),
+                           node=node, call='FakeScriptFail'))
+                  for node in node_list])
 
   def setUp(self):
     self.op = opcodes.OpCode()
     self.context = FakeContext()
-    self.lu = FakeLU(None, self.op, self.context)
+    # WARNING: here we pass None as RpcRunner instance since we know
+    # our usage via HooksMaster will not use lu.rpc
+    self.lu = FakeLU(FakeProc(), self.op, self.context, None)
 
   def testTotalFalse(self):
     """Test complete rpc failure"""
-    hm = mcpu.HooksMaster(self._call_false, FakeProc(), self.lu)
+    hm = mcpu.HooksMaster.BuildFromLu(self._call_false, self.lu)
     self.failUnlessRaises(errors.HooksFailure,
                           hm.RunPhase, constants.HOOKS_PHASE_PRE)
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
   def testIndividualFalse(self):
     """Test individual node failure"""
-    hm = mcpu.HooksMaster(self._call_nodes_false, FakeProc(), self.lu)
+    hm = mcpu.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
     hm.RunPhase(constants.HOOKS_PHASE_PRE)
     #self.failUnlessRaises(errors.HooksFailure,
     #                      hm.RunPhase, constants.HOOKS_PHASE_PRE)
@@ -247,16 +265,235 @@ class TestHooksMaster(unittest.TestCase):
 
   def testScriptFalse(self):
     """Test individual rpc failure"""
-    hm = mcpu.HooksMaster(self._call_script_fail, FakeProc(), self.lu)
+    hm = mcpu.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
     self.failUnlessRaises(errors.HooksAbort,
                           hm.RunPhase, constants.HOOKS_PHASE_PRE)
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
   def testScriptSucceed(self):
     """Test individual rpc failure"""
-    hm = mcpu.HooksMaster(self._call_script_succeed, FakeProc(), self.lu)
+    hm = mcpu.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
     for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
       hm.RunPhase(phase)
 
+
+class FakeEnvLU(cmdlib.LogicalUnit):
+  HPATH = "env_test_lu"
+  HTYPE = constants.HTYPE_GROUP
+
+  def __init__(self, *args):
+    cmdlib.LogicalUnit.__init__(self, *args)
+    self.hook_env = None
+
+  def BuildHooksEnv(self):
+    assert self.hook_env is not None
+    return self.hook_env
+
+  def BuildHooksNodes(self):
+    return (["localhost"], ["localhost"])
+
+
+class FakeNoHooksLU(cmdlib.NoHooksLU):
+  pass
+
+
+class TestHooksRunnerEnv(unittest.TestCase):
+  def setUp(self):
+    self._rpcs = []
+
+    self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
+    self.lu = FakeEnvLU(FakeProc(), self.op, FakeContext(), None)
+
+  def _HooksRpc(self, *args):
+    self._rpcs.append(args)
+    return FakeHooksRpcSuccess(*args)
+
+  def _CheckEnv(self, env, phase, hpath):
+    self.assertTrue(env["PATH"].startswith("/sbin"))
+    self.assertEqual(env["GANETI_HOOKS_PHASE"], phase)
+    self.assertEqual(env["GANETI_HOOKS_PATH"], hpath)
+    self.assertEqual(env["GANETI_OP_CODE"], self.op.OP_ID)
+    self.assertEqual(env["GANETI_HOOKS_VERSION"], str(constants.HOOKS_VERSION))
+    self.assertEqual(env["GANETI_DATA_DIR"], pathutils.DATA_DIR)
+    if "GANETI_OBJECT_TYPE" in env:
+      self.assertEqual(env["GANETI_OBJECT_TYPE"], constants.HTYPE_GROUP)
+    else:
+      self.assertTrue(self.lu.HTYPE is None)
+
+  def testEmptyEnv(self):
+    # Check pre-phase hook
+    self.lu.hook_env = {}
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunPhase(constants.HOOKS_PHASE_PRE)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(hpath, self.lu.HPATH)
+    self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
+    self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
+
+    # Check post-phase hook
+    self.lu.hook_env = {}
+    hm.RunPhase(constants.HOOKS_PHASE_POST)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(hpath, self.lu.HPATH)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
+
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testEnv(self):
+    # Check pre-phase hook
+    self.lu.hook_env = {
+      "FOO": "pre-foo-value",
+      }
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunPhase(constants.HOOKS_PHASE_PRE)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(hpath, self.lu.HPATH)
+    self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
+    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
+    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
+    self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
+
+    # Check post-phase hook
+    self.lu.hook_env = {
+      "FOO": "post-value",
+      "BAR": 123,
+      }
+    hm.RunPhase(constants.HOOKS_PHASE_POST)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(hpath, self.lu.HPATH)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
+    self.assertEqual(env["GANETI_POST_FOO"], "post-value")
+    self.assertEqual(env["GANETI_POST_BAR"], "123")
+    self.assertFalse("GANETI_BAR" in env)
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
+
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+    # Check configuration update hook
+    hm.RunConfigUpdate()
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST,
+                   constants.HOOKS_NAME_CFGUPDATE)
+    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
+    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testConflict(self):
+    for name in ["DATA_DIR", "OP_CODE"]:
+      self.lu.hook_env = { name: "value" }
+
+      # Test using a clean HooksMaster instance
+      hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+
+      for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
+        self.assertRaises(AssertionError, hm.RunPhase, phase)
+        self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testNoNodes(self):
+    self.lu.hook_env = {}
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testSpecificNodes(self):
+    self.lu.hook_env = {}
+
+    nodes = [
+      "node1.example.com",
+      "node93782.example.net",
+      ]
+
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+
+    for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
+      hm.RunPhase(phase, nodes=nodes)
+
+      (node_list, hpath, rpc_phase, env) = self._rpcs.pop(0)
+      self.assertEqual(set(node_list), set(nodes))
+      self.assertEqual(hpath, self.lu.HPATH)
+      self.assertEqual(rpc_phase, phase)
+      self._CheckEnv(env, phase, self.lu.HPATH)
+
+      self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testRunConfigUpdateNoPre(self):
+    self.lu.hook_env = {
+      "FOO": "value",
+      }
+
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunConfigUpdate()
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self.assertEqual(env["GANETI_FOO"], "value")
+    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST,
+                   constants.HOOKS_NAME_CFGUPDATE)
+
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testNoPreBeforePost(self):
+    self.lu.hook_env = {
+      "FOO": "value",
+      }
+
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunPhase(constants.HOOKS_PHASE_POST)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(hpath, self.lu.HPATH)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self.assertEqual(env["GANETI_FOO"], "value")
+    self.assertEqual(env["GANETI_POST_FOO"], "value")
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
+
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+  def testNoHooksLU(self):
+    self.lu = FakeNoHooksLU(FakeProc(), self.op, FakeContext(), None)
+    self.assertRaises(AssertionError, self.lu.BuildHooksEnv)
+    self.assertRaises(AssertionError, self.lu.BuildHooksNodes)
+
+    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    self.assertEqual(hm.pre_env, {})
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+    hm.RunPhase(constants.HOOKS_PHASE_PRE)
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+    hm.RunPhase(constants.HOOKS_PHASE_POST)
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+    hm.RunConfigUpdate()
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
+    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
+    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
+    self._CheckEnv(env, constants.HOOKS_PHASE_POST,
+                   constants.HOOKS_NAME_CFGUPDATE)
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+    assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
+
+
 if __name__ == '__main__':
-  unittest.main()
+  testutils.GanetiTestProgram()