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
