예제 #1
0
    async def test_stop_stops_apps_in_top_down_order(self,
                                                     three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            mock_parent = MagicMock()

            # Mock all of the apps that have been configured for this pipeline.
            for key, app in pipeline._installed_apps.items():
                app_mock = MagicMock(app.__class__)
                app_mock.name = app.name

                setattr(mock_parent, app.name, app_mock)

                pipeline._installed_apps[key] = app_mock

            await pipeline.stop()

            for app in pipeline.apps.values():
                assert app.stop.called
                assert app.stop.call_count == 1

            call_order = [
                call[0].rstrip("stop").rstrip(".")
                for call in mock_parent.method_calls[-len(pipeline.apps):]
            ]
            assert call_order == list(pipeline.apps.keys())
예제 #2
0
    async def test_send_stop(self, three_level_stop_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_stop_app_chain)

            message = await pipeline.send(admin.TestRequestMessage("Test"))
            assert message.TestReqID == "Test s3"
예제 #3
0
 def test_load_apps_falls_back_to_settings(self):
     with connection_manager() as conn:
         pipeline = BasePipeline(connection_name=conn.name)
         assert len(pipeline.apps) == len(pipeline.settings.PIPELINE_APPS)
         assert all(f"{app.__class__.__module__}.{app.__class__.__name__}"
                    in pipeline.settings.PIPELINE_APPS
                    for app in pipeline.apps.values())
예제 #4
0
 def test_prep_processing_pipeline_direction_unknown_raises_exception(
         self, three_level_app_chain):
     with pytest.raises(ValidationError):
         with connection_manager() as conn:
             pipeline = BasePipeline(connection_name=conn.name,
                                     installed_apps=three_level_app_chain)
             pipeline._setup_message_handling("inbound")
예제 #5
0
async def main():
    logging.basicConfig(
        level=settings.LOGGING_LEVEL,
        format="%(asctime)s - %(threadName)s - %(module)s - %(levelname)s - %(message)s",
    )

    args = parser.parse_args()
    exit_code = os.EX_OK

    with connection_manager(args.connection) as conn:
        fix_pipeline = BasePipeline(
            connection_name=conn.name, new_session=args.new_session
        )

        try:
            # Graceful shutdown on termination signals.
            # See: https://docs.python.org/3.7/library/asyncio-eventloop.html#set-signal-handlers-for-sigint-and-sigterm
            loop = asyncio.get_running_loop()
            for sig_name in {"SIGINT", "SIGTERM"}:
                loop.add_signal_handler(
                    getattr(signal, sig_name),
                    lambda: asyncio.create_task(
                        graceful_shutdown(fix_pipeline, sig_name=sig_name)
                    ),
                )

            await fix_pipeline.start()

        except ImproperlyConfigured as e:
            # User needs to fix config issue before restart is attempted. Set os.EX_OK so that system process
            # monitors like Supervisor do not attempt a restart immediately.
            await graceful_shutdown(fix_pipeline, error=e)

        except KeyboardInterrupt:
            logger.info("Received keyboard interrupt! Initiating shutdown...")
            await graceful_shutdown(fix_pipeline)

        except Exception as e:
            await graceful_shutdown(fix_pipeline, error=e)
            exit_code = os.EX_UNAVAILABLE  # Abnormal termination

        finally:
            # Report tasks that are still running after shutdown.
            tasks = [
                task
                for task in asyncio.all_tasks()
                if task is not asyncio.current_task() and not task.cancelled()
            ]

            if tasks:
                task_output = "\n".join(str(task) for task in tasks)
                logger.warning(
                    f"There are still {len(tasks)} tasks running that have not been cancelled! Cancelling them now...\n"
                    f"{task_output}."
                )

                for task in tasks:
                    task.cancel()

            sys.exit(exit_code)
예제 #6
0
    async def test_start_starts_apps_in_reverse_order(self,
                                                      three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            mock_parent = MagicMock()

            # Mock all of the apps that have been configured for this pipeline.
            for key, app in pipeline._installed_apps.items():
                app_mock = MagicMock(app.__class__)
                app_mock.name = app.name

                setattr(mock_parent, app.name, app_mock)

                pipeline._installed_apps[key] = app_mock

            pipeline.stopped_event.set(
            )  # Stop pipeline immediately after start has been executed
            await pipeline.start()

            for app in pipeline.apps.values():
                assert app.start.called
                assert app.start.call_count == 1

            call_order = [
                call[0].rstrip(".start")
                for call in mock_parent.method_calls[-len(pipeline.apps):]
            ]
            assert call_order == list(reversed(pipeline.apps.keys()))

            await pipeline.stop()
예제 #7
0
    async def test_stop_allows_only_one_stop_process_to_run_concurrently(
            self, three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            # Mock all of the apps that have been configured for this pipeline.
            for key, app in pipeline._installed_apps.items():
                app_mock = MagicMock(app.__class__)
                app_mock.stop = get_mock_async()

                pipeline._installed_apps[key] = app_mock

            asyncio.create_task(pipeline.stop())
            asyncio.create_task(pipeline.stop())
            asyncio.create_task(pipeline.stop())
            await pipeline.stop()

            for app in pipeline.apps.values():
                assert app.stop.called
                assert app.stop.call_count == 1

            # Wait for separate tasks to complete
            tasks = asyncio.all_tasks()
            await asyncio.wait(tasks, timeout=0.1)
예제 #8
0
def base_pipeline():
    """
    Basic mock pipeline that can be used to instantiate new apps in tests.

    :return: A pipeline mock with a client session initialized.
    """
    with connection_manager() as conn:
        pipeline = MagicMock(BasePipeline)
        pipeline.settings = ConnectionSettings(conn.name)

        client_session = ClientSessionApp(pipeline, new_session=True)
        client_session.sender = pipeline.settings.SENDER
        client_session.target = pipeline.settings.TARGET

        pipeline.apps = {ClientSessionApp.name: client_session}

        # Mock a future message that will allow us to await pipeline.send and pipeline.receive.
        # Only useful in situations where we are not interested in the actual message result :(
        mock_future_message = MagicMock(return_value=Future())
        mock_future_message.return_value.set_result({})

        pipeline.send = mock_future_message
        pipeline.receive = MagicMock(return_value=mock_coroutine())

        # Simulate the pipeline shutting down
        pipeline.stop = MagicMock(return_value=mock_coroutine())

        yield pipeline

        try:
            os.remove(client_session._sid_path)
        except FileNotFoundError:
            # File does not exist - skip deletion
            pass
예제 #9
0
    def test_group_templates_getter_initializes_with_empty_template_group(
            self):
        settings.CONNECTIONS["another_session"] = {}

        with connection_manager(name="another_session"):
            assert GroupTemplateMixin().group_templates == {}

        del settings.CONNECTIONS["another_session"]
예제 #10
0
    def test_load_apps_raises_exception_if_no_apps_installed(self):
        with pytest.raises(ImproperlyConfigured):
            with connection_manager() as conn:
                orig_apps = settings.CONNECTIONS[conn.name]["PIPELINE_APPS"]

                settings.CONNECTIONS[conn.name]["PIPELINE_APPS"] = []
                _ = BasePipeline(connection_name=conn.name)

                settings.CONNECTIONS[conn.name]["PIPELINE_APPS"] = orig_apps
예제 #11
0
    async def test_send_stop(self, three_level_stop_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_stop_app_chain)

            # Simulate all apps active
            pipeline._active_apps = OrderedDict(pipeline.apps.items())

            message = await pipeline.send(admin.TestRequestMessage("Test"))
            assert message.TestReqID == "Test s3"
예제 #12
0
async def main():
    logging.basicConfig(
        level=settings.LOGGING_LEVEL,
        format=
        "%(asctime)s - %(threadName)s - %(module)s - %(levelname)s - %(message)s",
    )

    args = parser.parse_args()

    with connection_manager(args.connection) as conn:
        fix_pipeline = BasePipeline(connection_name=conn.name,
                                    new_session=args.new_session)

        try:
            # Graceful shutdown on termination signals.
            # See: https://docs.python.org/3.7/library/asyncio-eventloop.html#set-signal-handlers-for-sigint-and-sigterm
            loop = asyncio.get_running_loop()
            for sig_name in {"SIGINT", "SIGTERM"}:
                loop.add_signal_handler(
                    getattr(signal, sig_name),
                    lambda: asyncio.ensure_future(
                        graceful_shutdown(sig_name, fix_pipeline)),
                )

            await fix_pipeline.start()

        except asyncio.TimeoutError as e:
            logger.error(e)
            sys.exit(os.EX_UNAVAILABLE)

        except KeyboardInterrupt:
            logger.info("Received keyboard interrupt! Initiating shutdown...")
            sys.exit(os.EX_OK)

        except futures.CancelledError as e:
            logger.error(f"Cancelled: connection terminated abnormally! ({e})")
            sys.exit(os.EX_UNAVAILABLE)

        except ImproperlyConfigured as e:
            logger.error(e)
            sys.exit(
                os.EX_OK
            )  # User needs to fix config issue before restart is attempted

        except Exception as e:
            logger.exception(e)
            sys.exit(os.EX_UNAVAILABLE)

        finally:
            try:
                await fix_pipeline.stop()
            except futures.CancelledError as e:
                logger.error(
                    f"Cancelled: connection terminated abnormally! ({e})")
                sys.exit(os.EX_UNAVAILABLE)
예제 #13
0
    def test_prep_processing_pipeline_inbound_order(self,
                                                    three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            func, app_chain = pipeline._setup_message_handling(
                pipeline.INBOUND_PROCESSING)
            assert func == "on_receive"
            assert next(app_chain).name == "below"
            assert next(app_chain).name == "middle"
            assert next(app_chain).name == "top"
예제 #14
0
    def test_pre_processing_pipeline_outbound_order(self,
                                                    three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            # Simulate all apps active
            pipeline._active_apps = OrderedDict(pipeline.apps.items())

            func, app_chain = pipeline._setup_message_handling(
                pipeline.OUTBOUND_PROCESSING)

            assert func == "on_send"
            assert next(app_chain).name == "top"
            assert next(app_chain).name == "middle"
            assert next(app_chain).name == "below"
예제 #15
0
    async def test_stop_no_op_if_already_stopped(self, three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            # Mock all of the apps that have been configured for this pipeline.
            for key, app in pipeline._installed_apps.items():
                app_mock = MagicMock(app.__class__)

                pipeline._installed_apps[key] = app_mock

            await pipeline.stop()
            await pipeline.stop()

            for app in pipeline.apps.values():
                assert app.stop.called
                assert app.stop.call_count == 1
예제 #16
0
    async def test_stop_cancels_all_tasks_on_stop_timeout(
            self, three_level_app_chain):
        with pytest.raises(futures.CancelledError):
            with connection_manager() as conn:
                pipeline = BasePipeline(connection_name=conn.name,
                                        installed_apps=three_level_app_chain)

                settings.STOP_TIMEOUT = 0.1
                # Mock all of the apps that have been configured for this pipeline.
                for key, app in pipeline._installed_apps.items():
                    app_mock = MagicMock(app.__class__)
                    app_mock.stop = get_slow_mock_async(settings.STOP_TIMEOUT +
                                                        0.1)

                    pipeline._installed_apps[key] = app_mock

                await pipeline.stop()
예제 #17
0
    async def test_start_raises_exception_on_initialize_timeout(
            self, three_level_app_chain, create_mock_coro):

        mock_, _ = create_mock_coro(
            runtime=settings.INIT_TIMEOUT + 0.1,
            to_patch="wtfix.apps.base.BaseApp.initialize",
        )

        with pytest.raises(asyncio.exceptions.TimeoutError):
            with connection_manager() as conn:
                pipeline = BasePipeline(connection_name=conn.name,
                                        installed_apps=three_level_app_chain)

                settings.INIT_TIMEOUT = 0.1
                await pipeline.start()

            await pipeline.stop()

            assert mock_.call_count == 1
예제 #18
0
    async def test_initialize_initializes_each_app_exactly_once(
            self, three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

            # Mock all of the apps that have been configured for this pipeline.
            for key, app in pipeline._installed_apps.items():
                app_mock = MagicMock(app.__class__)
                app_mock.name = app.name

                pipeline._installed_apps[key] = app_mock

            await pipeline.initialize()

            for app in pipeline.apps.values():
                assert app.initialize.called
                assert app.initialize.call_count == 1

            await pipeline.stop()
예제 #19
0
    async def test_start_raises_exception_on_initialize_timeout(
            self, three_level_app_chain):
        with pytest.raises(asyncio.TimeoutError):
            with connection_manager() as conn:
                pipeline = BasePipeline(connection_name=conn.name,
                                        installed_apps=three_level_app_chain)

                settings.INIT_TIMEOUT = 0.1
                # Mock all of the apps that have been configured for this pipeline.
                for key, app in pipeline._installed_apps.items():
                    app_mock = MagicMock(app.__class__)
                    app_mock.initialize = get_slow_mock_async(
                        settings.INIT_TIMEOUT + 0.1)
                    app_mock.stop = get_mock_async()

                    pipeline._installed_apps[key] = app_mock

                await pipeline.start()

            await pipeline.stop()
예제 #20
0
파일: conftest.py 프로젝트: jcass77/WTFIX
def base_pipeline():
    """
    Basic mock pipeline that can be used to instantiate new apps in tests.

    :return: A pipeline mock with a client session initialized.
    """
    with connection_manager() as conn:
        pipeline = MagicMock(BasePipeline)
        pipeline.settings = ConnectionSettings(conn.name)

        client_session = ClientSessionApp(pipeline, new_session=True)
        client_session.sender = pipeline.settings.SENDER
        client_session.target = pipeline.settings.TARGET

        pipeline.apps = {ClientSessionApp.name: client_session}

        yield pipeline

        try:
            os.remove(client_session._sid_path)
        except FileNotFoundError:
            # File does not exist - skip deletion
            pass
예제 #21
0
    def test_load_apps_installs_apps_in_pipeline(self, three_level_app_chain):
        with connection_manager() as conn:
            pipeline = BasePipeline(connection_name=conn.name,
                                    installed_apps=three_level_app_chain)

        assert len(pipeline.apps) == 3