repair_command takes two strings as input cmd, and inp.
This patch generalizes RunRestrictedCmd to RunCmd. RunCmd
can now pass a string as stdin input to the command that
will rull.

Signed-off-by: Bhimanavajjula Aditya <[email protected]>
---
 lib/backend.py                     | 41 ++++++++++++-----
 lib/pathutils.py                   |  4 ++
 lib/rpc_defs.py                    |  4 ++
 lib/server/noded.py                | 18 +++++++-
 test/py/ganeti.backend_unittest.py | 90 +++++++++++++++++++-------------------
 5 files changed, 99 insertions(+), 58 deletions(-)

diff --git a/lib/backend.py b/lib/backend.py
index cc96689..c12bde3 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -5473,18 +5473,25 @@ def _PrepareRestrictedCmd(path, cmd,
   return _verify_cmd(path, cmd)
 
 
-def RunRestrictedCmd(cmd,
-                     _lock_timeout=_RCMD_LOCK_TIMEOUT,
-                     _lock_file=pathutils.RESTRICTED_COMMANDS_LOCK_FILE,
-                     _path=pathutils.RESTRICTED_COMMANDS_DIR,
-                     _sleep_fn=time.sleep,
-                     _prepare_fn=_PrepareRestrictedCmd,
-                     _runcmd_fn=utils.RunCmd,
-                     _enabled=constants.ENABLE_RESTRICTED_COMMANDS):
-  """Executes a restricted command after performing strict tests.
+def RunConstrainedCmd(cmd,
+                      lock_file,
+                      path,
+                      inp=None,
+                      _lock_timeout=_RCMD_LOCK_TIMEOUT,
+                      _sleep_fn=time.sleep,
+                      _prepare_fn=_PrepareRestrictedCmd,
+                      _runcmd_fn=utils.RunCmd,
+                      _enabled=constants.ENABLE_RESTRICTED_COMMANDS):
+  """Executes a command after performing strict tests.
 
   @type cmd: string
   @param cmd: Command name
+  @type lock_file: string
+  @param lock_file: path to the lock file
+  @type path: string
+  @param path: path to the directory in which the command is present
+  @type inp: string
+  @param inp: Input to be passed to the command
   @rtype: string
   @return: Command output
   @raise RPCFail: In case of an error
@@ -5499,14 +5506,24 @@ def RunRestrictedCmd(cmd,
   try:
     cmdresult = None
     try:
-      lock = utils.FileLock.Open(_lock_file)
+      lock = utils.FileLock.Open(lock_file)
       lock.Exclusive(blocking=True, timeout=_lock_timeout)
 
-      (status, value) = _prepare_fn(_path, cmd)
+      (status, value) = _prepare_fn(path, cmd)
 
       if status:
+        if inp:
+          input_fd = tempfile.TemporaryFile()
+          input_fd.write(inp)
+          input_fd.flush()
+          input_fd.seek(0)
+        else:
+          input_fd = None
         cmdresult = _runcmd_fn([value], env={}, reset_env=True,
-                               postfork_fn=lambda _: lock.Unlock())
+                               postfork_fn=lambda _: lock.Unlock(),
+                               input_fd=input_fd)
+        if input_fd:
+          input_fd.close()
       else:
         logging.error(value)
     except Exception: # pylint: disable=W0703
diff --git a/lib/pathutils.py b/lib/pathutils.py
index d9c3440..81ebb1f 100644
--- a/lib/pathutils.py
+++ b/lib/pathutils.py
@@ -122,6 +122,7 @@ VNC_PASSWORD_FILE = CONF_DIR + "/vnc-cluster-password"
 HOOKS_BASE_DIR = CONF_DIR + "/hooks"
 FILE_STORAGE_PATHS_FILE = CONF_DIR + "/file-storage-paths"
 RESTRICTED_COMMANDS_DIR = CONF_DIR + "/restricted-commands"
+REPAIR_COMMANDS_DIR = CONF_DIR + "/node-repair-commands"
 
 #: Node daemon certificate path
 NODED_CERT_FILE = DATA_DIR + "/server.pem"
@@ -133,6 +134,9 @@ NODED_CERT_MODE = 0440
 #: Locked in exclusive mode while noded verifies a remote command
 RESTRICTED_COMMANDS_LOCK_FILE = LOCK_DIR + "/ganeti-restricted-commands.lock"
 
+#: Locked in exclusive mode while noded verifies a remote command
+REPAIR_COMMANDS_LOCK_FILE = LOCK_DIR + "/ganeti-repair-commands.lock"
+
 #: Lock file for watcher, locked in shared mode by watcher; lock in exclusive
 # mode to block watcher (see L{cli._RunWhileDaemonsStoppedHelper.Call}
 WATCHER_LOCK_FILE = LOCK_DIR + "/ganeti-watcher.lock"
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index e386ec2..c958eda 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -591,6 +591,10 @@ _MISC_CALLS = [
   ("restricted_command", MULTI, None, constants.RPC_TMO_SLOW, [
     ("cmd", None, "Command name"),
     ], None, None, "Runs restricted command"),
+  ("repair_command", SINGLE, None, constants.RPC_TMO_SLOW, [
+    ("cmd", None, "Command name"),
+    ("inp", None, "Input to be passed as stdin"),
+    ], None, None, "Runs repair command"),
   ("run_oob", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("oob_program", None, None),
     ("command", None, None),
diff --git a/lib/server/noded.py b/lib/server/noded.py
index bd876b3..83a36b4 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -1023,7 +1023,23 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """
     (cmd, ) = params
 
-    return backend.RunRestrictedCmd(cmd)
+    return backend.RunConstrainedCmd(
+      cmd,
+      lock_file=pathutils.RESTRICTED_COMMANDS_LOCK_FILE,
+      path=pathutils.RESTRICTED_COMMANDS_DIR)
+
+  @staticmethod
+  def perspective_repair_command(params):
+    """ Run a repair command.
+
+    """
+    (cmd, inp, ) = params
+
+    return backend.RunConstrainedCmd(
+      cmd,
+      lock_file=pathutils.REPAIR_COMMANDS_LOCK_FILE,
+      path=pathutils.REPAIR_COMMANDS_DIR,
+      inp=inp)
 
   @staticmethod
   def perspective_write_ssconf_files(params):
diff --git a/test/py/ganeti.backend_unittest.py 
b/test/py/ganeti.backend_unittest.py
index 5453c30..a8b26d4 100755
--- a/test/py/ganeti.backend_unittest.py
+++ b/test/py/ganeti.backend_unittest.py
@@ -376,7 +376,7 @@ def _GenericRestrictedCmdError(cmd):
   return "Executing command '%s' failed" % cmd
 
 
-class TestRunRestrictedCmd(unittest.TestCase):
+class TestRunConstrainedCmd(unittest.TestCase):
   def setUp(self):
     self.tmpdir = tempfile.mkdtemp()
 
@@ -388,10 +388,10 @@ class TestRunRestrictedCmd(unittest.TestCase):
     sleep_fn = testutils.CallCounter(_SleepForRestrictedCmd)
     self.assertFalse(os.path.exists(lockfile))
     self.assertRaises(backend.RPCFail,
-                      backend.RunRestrictedCmd, "test",
+                      backend.RunConstrainedCmd, "test",
                       _lock_timeout=NotImplemented,
-                      _lock_file=lockfile,
-                      _path=NotImplemented,
+                      lock_file=lockfile,
+                      path=NotImplemented,
                       _sleep_fn=sleep_fn,
                       _prepare_fn=NotImplemented,
                       _runcmd_fn=NotImplemented,
@@ -404,14 +404,14 @@ class TestRunRestrictedCmd(unittest.TestCase):
 
     result = False
     try:
-      backend.RunRestrictedCmd("test22717",
-                               _lock_timeout=0.1,
-                               _lock_file=lockfile,
-                               _path=NotImplemented,
-                               _sleep_fn=sleep_fn,
-                               _prepare_fn=NotImplemented,
-                               _runcmd_fn=NotImplemented,
-                               _enabled=True)
+      backend.RunConstrainedCmd("test22717",
+                                _lock_timeout=0.1,
+                                lock_file=lockfile,
+                                path=NotImplemented,
+                                _sleep_fn=sleep_fn,
+                                _prepare_fn=NotImplemented,
+                                _runcmd_fn=NotImplemented,
+                                _enabled=True)
     except backend.RPCFail, err:
       assert str(err) == _GenericRestrictedCmdError("test22717"), \
              "Did not fail with generic error message"
@@ -443,11 +443,11 @@ class TestRunRestrictedCmd(unittest.TestCase):
     prepare_fn = testutils.CallCounter(self._PrepareRaisingException)
 
     try:
-      backend.RunRestrictedCmd("test23122",
-                               _lock_timeout=1.0, _lock_file=lockfile,
-                               _path=NotImplemented, _runcmd_fn=NotImplemented,
-                               _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
-                               _enabled=True)
+      backend.RunConstrainedCmd("test23122",
+                                _lock_timeout=1.0, lock_file=lockfile,
+                                path=NotImplemented, _runcmd_fn=NotImplemented,
+                                _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
+                                _enabled=True)
     except backend.RPCFail, err:
       self.assertEqual(str(err), _GenericRestrictedCmdError("test23122"))
     else:
@@ -468,11 +468,11 @@ class TestRunRestrictedCmd(unittest.TestCase):
     prepare_fn = testutils.CallCounter(self._PrepareFails)
 
     try:
-      backend.RunRestrictedCmd("test29327",
-                               _lock_timeout=1.0, _lock_file=lockfile,
-                               _path=NotImplemented, _runcmd_fn=NotImplemented,
-                               _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
-                               _enabled=True)
+      backend.RunConstrainedCmd("test29327",
+                                _lock_timeout=1.0, lock_file=lockfile,
+                                path=NotImplemented, _runcmd_fn=NotImplemented,
+                                _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
+                                _enabled=True)
     except backend.RPCFail, err:
       self.assertEqual(str(err), _GenericRestrictedCmdError("test29327"))
     else:
@@ -485,11 +485,11 @@ class TestRunRestrictedCmd(unittest.TestCase):
   def _SuccessfulPrepare(path, cmd):
     return (True, utils.PathJoin(path, cmd))
 
-  def testRunCmdFails(self):
+  def testRunConstrainedCmdFails(self):
     lockfile = utils.PathJoin(self.tmpdir, "lock")
 
     def fn(args, env=NotImplemented, reset_env=NotImplemented,
-           postfork_fn=NotImplemented):
+           postfork_fn=NotImplemented, input_fd=NotImplemented):
       self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test3079")])
       self.assertEqual(env, {})
       self.assertTrue(reset_env)
@@ -519,11 +519,11 @@ class TestRunRestrictedCmd(unittest.TestCase):
     runcmd_fn = testutils.CallCounter(fn)
 
     try:
-      backend.RunRestrictedCmd("test3079",
-                               _lock_timeout=1.0, _lock_file=lockfile,
-                               _path=self.tmpdir, _runcmd_fn=runcmd_fn,
-                               _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
-                               _enabled=True)
+      backend.RunConstrainedCmd("test3079",
+                                _lock_timeout=1.0, lock_file=lockfile,
+                                path=self.tmpdir, _runcmd_fn=runcmd_fn,
+                                _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
+                                _enabled=True)
     except backend.RPCFail, err:
       self.assertTrue(str(err).startswith("Restricted command 'test3079'"
                                           " failed:"))
@@ -536,11 +536,11 @@ class TestRunRestrictedCmd(unittest.TestCase):
     self.assertEqual(prepare_fn.Count(), 1)
     self.assertEqual(runcmd_fn.Count(), 1)
 
-  def testRunCmdSucceeds(self):
+  def testRunConstrainedCmdSucceeds(self):
     lockfile = utils.PathJoin(self.tmpdir, "lock")
 
     def fn(args, env=NotImplemented, reset_env=NotImplemented,
-           postfork_fn=NotImplemented):
+           postfork_fn=NotImplemented, input_fd=NotImplemented):
       self.assertEqual(args, [utils.PathJoin(self.tmpdir, "test5667")])
       self.assertEqual(env, {})
       self.assertTrue(reset_env)
@@ -557,12 +557,12 @@ class TestRunRestrictedCmd(unittest.TestCase):
     prepare_fn = testutils.CallCounter(self._SuccessfulPrepare)
     runcmd_fn = testutils.CallCounter(fn)
 
-    result = backend.RunRestrictedCmd("test5667",
-                                      _lock_timeout=1.0, _lock_file=lockfile,
-                                      _path=self.tmpdir, _runcmd_fn=runcmd_fn,
-                                      _sleep_fn=sleep_fn,
-                                      _prepare_fn=prepare_fn,
-                                      _enabled=True)
+    result = backend.RunConstrainedCmd("test5667",
+                                       _lock_timeout=1.0, lock_file=lockfile,
+                                       path=self.tmpdir, _runcmd_fn=runcmd_fn,
+                                       _sleep_fn=sleep_fn,
+                                       _prepare_fn=prepare_fn,
+                                       _enabled=True)
     self.assertEqual(result, "stdout14463")
 
     self.assertEqual(sleep_fn.Count(), 0)
@@ -571,14 +571,14 @@ class TestRunRestrictedCmd(unittest.TestCase):
 
   def testCommandsDisabled(self):
     try:
-      backend.RunRestrictedCmd("test",
-                               _lock_timeout=NotImplemented,
-                               _lock_file=NotImplemented,
-                               _path=NotImplemented,
-                               _sleep_fn=NotImplemented,
-                               _prepare_fn=NotImplemented,
-                               _runcmd_fn=NotImplemented,
-                               _enabled=False)
+      backend.RunConstrainedCmd("test",
+                                _lock_timeout=NotImplemented,
+                                lock_file=NotImplemented,
+                                path=NotImplemented,
+                                _sleep_fn=NotImplemented,
+                                _prepare_fn=NotImplemented,
+                                _runcmd_fn=NotImplemented,
+                                _enabled=False)
     except backend.RPCFail, err:
       self.assertEqual(str(err),
                        "Restricted commands disabled at configure time")
-- 
2.1.4

Reply via email to