def test_run_unknown_user(self, mock_getpwnam): mock_getpwnam.side_effect = KeyError("foo") self.manager.config.script_users = "foo" self.store.add_graph(123, "filename", "foo") factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": u"UnknownUserError: Unknown user 'foo'", "script-hash": b"", "values": []}, }, "type": "custom-graph"}]) mock_getpwnam.assert_called_with("foo") return result.addCallback(check)
def test_run_dissallowed_user(self): uid = os.getuid() info = pwd.getpwuid(uid) username = info.pw_name self.manager.config.script_users = "foo" self.store.add_graph(123, "filename", username) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": (u"ProhibitedUserError: Custom graph cannot " u"be run as user %s") % (username,), "script-hash": b"", "values": []}, }, "type": "custom-graph"}]) return result.addCallback(check)
def test_run_timeout(self): filename = self.makeFile("some content") self.store.add_graph(123, filename, None) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] protocol = spawn[0] protocol.makeConnection(DummyProcess()) self.assertEqual(spawn[1], filename) self.manager.reactor.advance(110) protocol.processEnded(Failure(ProcessDone(0))) def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": u"Process exceeded the 10 seconds limit", "script-hash": b"9893532233caff98cd083a116b013c0b", "values": []}, }, "type": "custom-graph"}]) return result.addCallback(check)
def test_run_user(self, mock_getpwnam): filename = self.makeFile("some content") self.store.add_graph(123, filename, "bar") factory = StubProcessFactory() self.graph_manager.process_factory = factory class pwnam(object): pw_uid = 1234 pw_gid = 5678 pw_dir = self.makeFile() mock_getpwnam.return_value = pwnam result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.assertEqual(spawn[1], filename) self.assertEqual(spawn[2], (filename,)) self.assertEqual(spawn[3], {}) self.assertEqual(spawn[4], "/") self.assertEqual(spawn[5], 1234) self.assertEqual(spawn[6], 5678) mock_getpwnam.assert_called_with("bar") self._exit_process_protocol(spawn[0], b"spam") return result
def test_run_unknown_user_with_unicode(self): """ Using a non-existent user containing unicode characters fails with the appropriate error message. """ username = u"non-existent-f\N{LATIN SMALL LETTER E WITH ACUTE}e" self.manager.config.script_users = "ALL" filename = self.makeFile("some content") self.store.add_graph(123, filename, username) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": u"UnknownUserError: Unknown user '%s'" % username, "script-hash": b"9893532233caff98cd083a116b013c0b", "values": []}}, "type": "custom-graph"}]) return result.addCallback(check)
def test_unknown_error(self): """ When a completely unknown error comes back from the process protocol, the operation fails and the formatted failure is included in the response message. """ factory = StubProcessFactory() self.manager.add(ScriptExecutionPlugin(process_factory=factory)) result = self._send_script(sys.executable, "print 'hi'") self._verify_script(factory.spawns[0][1], sys.executable, "print 'hi'") self.assertMessages( self.broker_service.message_store.get_pending_messages(), []) failure = Failure(RuntimeError("Oh noes!")) factory.spawns[0][0].result_deferred.errback(failure) def got_result(r): self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{ "type": "operation-result", "operation-id": 123, "status": FAILED, "result-text": str(failure) }]) result.addCallback(got_result) return result
def test_run_with_script_removed(self): """ If a script is removed while a data point is being retrieved, the data point is discarded and no data is sent at all. """ uid = os.getuid() info = pwd.getpwuid(uid) username = info.pw_name self.manager.dispatch_message( {"type": "custom-graph-add", "interpreter": "/bin/sh", "code": "echo 1.0", "username": username, "graph-id": 123}) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.manager.dispatch_message( {"type": "custom-graph-remove", "graph-id": 123}) self._exit_process_protocol(spawn[0], b"1.0") def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"api": b"3.2", "data": {}, "timestamp": 0, "type": "custom-graph"}]) return result.addCallback(check)
def _run_script(self, username, uid, gid, path): expected_uid = uid if uid != os.getuid() else None expected_gid = gid if gid != os.getgid() else None factory = StubProcessFactory() self.plugin.process_factory = factory # ignore the call to chown! patch_chown = mock.patch("os.chown") mock_chown = patch_chown.start() result = self.plugin.run_script("/bin/sh", "echo hi", user=username) self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.assertEqual(spawn[4], path) self.assertEqual(spawn[5], expected_uid) self.assertEqual(spawn[6], expected_gid) protocol = spawn[0] protocol.childDataReceived(1, b"foobar") for fd in (0, 1, 2): protocol.childConnectionLost(fd) protocol.processEnded(Failure(ProcessDone(0))) def check(result): mock_chown.assert_called_with() self.assertEqual(result, "foobar") def cleanup(result): patch_chown.stop() return result return result.addErrback(check).addBoth(cleanup)
def test_run_no_output_error(self): filename = self.makeFile("some_content") self.store.add_graph(123, filename, None) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.assertEqual(spawn[1], filename) self._exit_process_protocol(spawn[0], b"") def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": (u"NoOutputError: Script did not output " u"any value"), "values": [], "script-hash": b"baab6c16d9143523b7865d46896e4596", }, }, "type": "custom-graph"}]) return result.addCallback(check)
def test_success(self): """ When a C{execute-script} message is received from the server, the specified script will be run and an operation-result will be sent back to the server. """ # Let's use a stub process factory, because otherwise we don't have # access to the deferred. factory = StubProcessFactory() self.manager.add(ScriptExecutionPlugin(process_factory=factory)) result = self._send_script(sys.executable, "print 'hi'") self._verify_script(factory.spawns[0][1], sys.executable, "print 'hi'") self.assertMessages( self.broker_service.message_store.get_pending_messages(), []) # Now let's simulate the completion of the process factory.spawns[0][0].childDataReceived(1, b"hi!\n") factory.spawns[0][0].processEnded(Failure(ProcessDone(0))) def got_result(r): self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{ "type": "operation-result", "operation-id": 123, "status": SUCCEEDED, "result-text": u"hi!\n" }]) result.addCallback(got_result) return result
def test_timeout(self): """ If a L{ProcessTimeLimitReachedError} is fired back, the operation-result should have a failed status. """ factory = StubProcessFactory() self.manager.add(ScriptExecutionPlugin(process_factory=factory)) result = self._send_script(sys.executable, "bar", time_limit=30) self._verify_script(factory.spawns[0][1], sys.executable, "bar") protocol = factory.spawns[0][0] protocol.makeConnection(DummyProcess()) protocol.childDataReceived(2, b"ONOEZ") self.manager.reactor.advance(31) protocol.processEnded(Failure(ProcessDone(0))) def got_result(r): self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{ "type": "operation-result", "operation-id": 123, "status": FAILED, "result-text": u"ONOEZ", "result-code": 102 }]) result.addCallback(got_result) return result
def test_multiple_errors(self): filename1 = self.makeFile("some_content") self.store.add_graph(123, filename1, None) filename2 = self.makeFile("some_content") self.store.add_graph(124, filename2, None) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 2) spawn = factory.spawns[0] self._exit_process_protocol(spawn[0], b"foo") spawn = factory.spawns[1] self._exit_process_protocol(spawn[0], b"") def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": { 123: { "error": (u"InvalidFormatError: Failed to convert " u"to number: 'foo'"), "script-hash": b"baab6c16d9143523b7865d46896e4596", "values": []}, 124: { "error": (u"NoOutputError: Script did not output " u"any value"), "script-hash": b"baab6c16d9143523b7865d46896e4596", "values": []}, }, "type": "custom-graph"}]) return result.addCallback(check)
def setUp(self): super(ShutdownManagerTest, self).setUp() self.broker_service.message_store.set_accepted_types( ["shutdown", "operation-result"]) self.broker_service.pinger.start() self.process_factory = StubProcessFactory() self.plugin = ShutdownManager(process_factory=self.process_factory) self.manager.add(self.plugin)
def test_time_limit_canceled_after_success(self): """ The timeout call is cancelled after the script terminates. """ factory = StubProcessFactory() self.plugin.process_factory = factory self.plugin.run_script("/bin/sh", "", time_limit=500) protocol = factory.spawns[0][0] transport = DummyProcess() protocol.makeConnection(transport) protocol.childDataReceived(1, b"hi\n") protocol.processEnded(Failure(ProcessDone(0))) self.manager.reactor.advance(501) self.assertEqual(transport.signals, [])
def test_run_without_graph(self): """ If no graph is available, C{CustomGraphPlugin.run} doesn't even call C{call_if_accepted} on the broker and return immediately an empty list of results. """ self.graph_manager.registry.broker.call_if_accepted = ( lambda *args: 1 / 0) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) return result.addCallback(self.assertEqual, [])
def test_run_removed_file(self): """ If run is called on a script file that has been removed, it doesn't try to run it, but report it with an empty hash value. """ self.store.add_graph(123, "/nonexistent", None) factory = StubProcessFactory() self.graph_manager.process_factory = factory self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"data": {123: {"error": u"", "script-hash": b"", "values": []}}, "type": "custom-graph"}])
def test_run_with_script_updated(self): """ If a script is updated while a data point is being retrieved, the data point is discarded and no value is sent, but the new script is mentioned. """ uid = os.getuid() info = pwd.getpwuid(uid) username = info.pw_name self.manager.dispatch_message( {"type": "custom-graph-add", "interpreter": "/bin/sh", "code": "echo 1.0", "username": username, "graph-id": 123}) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.manager.dispatch_message( {"type": "custom-graph-add", "interpreter": "/bin/sh", "code": "echo 2.0", "username": username, "graph-id": 123}) self._exit_process_protocol(spawn[0], b"1.0") def check(ignore): self.graph_manager.exchange() self.assertMessages( self.broker_service.message_store.get_pending_messages(), [{"api": b"3.2", "data": {123: {"error": u"", "script-hash": b"991e15a81929c79fe1d243b2afd99c62", "values": []}}, "timestamp": 0, "type": "custom-graph"}]) return result.addCallback(check)
def test_user_with_attachments(self): uid = os.getuid() info = pwd.getpwuid(uid) username = info.pw_name gid = info.pw_gid patch_chown = mock.patch("os.chown") mock_chown = patch_chown.start() factory = StubProcessFactory() self.plugin.process_factory = factory result = self.plugin.run_script("/bin/sh", "echo hi", user=username, attachments={u"file 1": "some data"}) self.assertEqual(len(factory.spawns), 1) spawn = factory.spawns[0] self.assertIn("LANDSCAPE_ATTACHMENTS", spawn[3]) attachment_dir = spawn[3]["LANDSCAPE_ATTACHMENTS"] self.assertEqual(stat.S_IMODE(os.stat(attachment_dir).st_mode), 0o700) filename = os.path.join(attachment_dir, "file 1") self.assertEqual(stat.S_IMODE(os.stat(filename).st_mode), 0o600) protocol = spawn[0] protocol.childDataReceived(1, b"foobar") for fd in (0, 1, 2): protocol.childConnectionLost(fd) protocol.processEnded(Failure(ProcessDone(0))) def check(data): self.assertEqual(data, "foobar") self.assertFalse(os.path.exists(attachment_dir)) mock_chown.assert_has_calls( [mock.call(mock.ANY, uid, gid) for x in range(3)]) def cleanup(result): patch_chown.stop() return result return result.addCallback(check).addBoth(cleanup)
def test_limit_time_accumulates_data(self): """ Data from processes that time out should still be accumulated and available from the exception object that is raised. """ factory = StubProcessFactory() self.plugin.process_factory = factory result = self.plugin.run_script("/bin/sh", "", time_limit=500) protocol = factory.spawns[0][0] protocol.makeConnection(DummyProcess()) protocol.childDataReceived(1, b"hi\n") self.manager.reactor.advance(501) protocol.processEnded(Failure(ProcessDone(0))) def got_error(f): self.assertTrue(f.check(ProcessTimeLimitReachedError)) self.assertEqual(f.value.data, "hi\n") result.addErrback(got_error) return result
def test_cancel_doesnt_blow_after_success(self): """ When the process ends successfully and is immediately followed by the timeout, the output should still be in the failure and nothing bad will happen! [regression test: killing of the already-dead process would blow up.] """ factory = StubProcessFactory() self.plugin.process_factory = factory result = self.plugin.run_script("/bin/sh", "", time_limit=500) protocol = factory.spawns[0][0] protocol.makeConnection(DummyProcess()) protocol.childDataReceived(1, b"hi") protocol.processEnded(Failure(ProcessDone(0))) self.manager.reactor.advance(501) def got_result(output): self.assertEqual(output, "hi") result.addCallback(got_result) return result
def test_limit_size(self): """Data returned from the command is limited.""" factory = StubProcessFactory() self.plugin.process_factory = factory self.plugin.size_limit = 100 result = self.plugin.run_script("/bin/sh", "") # Ultimately we assert that the resulting output is limited to # 100 bytes and indicates its truncation. result.addCallback(self.assertEqual, ("x" * 79) + "\n**OUTPUT TRUNCATED**") protocol = factory.spawns[0][0] # Push 200 bytes of output, so we trigger truncation. protocol.childDataReceived(1, b"x" * 200) for fd in (0, 1, 2): protocol.childConnectionLost(fd) protocol.processEnded(Failure(ProcessDone(0))) return result
def test_command_output_ends_with_truncation(self): """After truncation, no further output is recorded.""" factory = StubProcessFactory() self.plugin.process_factory = factory self.manager.config.script_output_limit = 1 result = self.plugin.run_script("/bin/sh", "") # Ultimately we assert that the resulting output is limited to # 1024 bytes and indicates its truncation. result.addCallback(self.assertEqual, ("x" * (1024 - 21)) + "\n**OUTPUT TRUNCATED**") protocol = factory.spawns[0][0] # Push 1024 bytes of output, so we trigger truncation. protocol.childDataReceived(1, b"x" * 1024) # Push 1024 bytes more protocol.childDataReceived(1, b"x" * 1024) for fd in (0, 1, 2): protocol.childConnectionLost(fd) protocol.processEnded(Failure(ProcessDone(0))) return result
def test_run_not_accepted_types(self): """ If "custom-graph" is not an accepted message-type anymore, C{CustomGraphPlugin.run} shouldn't even run the graph scripts. """ self.broker_service.message_store.set_accepted_types([]) uid = os.getuid() info = pwd.getpwuid(uid) username = info.pw_name self.manager.dispatch_message( {"type": "custom-graph-add", "interpreter": "/bin/sh", "code": "echo 1.0", "username": username, "graph-id": 123}) factory = StubProcessFactory() self.graph_manager.process_factory = factory result = self.graph_manager.run() self.assertEqual(len(factory.spawns), 0) return result.addCallback(self.assertIdentical, None)