예제 #1
0
async def test_waitable_monitor():
    """Test if `WaitableMonitor.wait_for_event()` respects event ordering."""

    monitor = EventMonitor()
    monitor.start()

    events = []

    async def wait_for_events():
        events.append(await monitor.wait_for_event(lambda e: e == 1))
        events.append(await monitor.wait_for_event(lambda e: e == 2))
        events.append(await monitor.wait_for_event(lambda e: e == 3))

    await monitor.add_event(0)
    await monitor.add_event(1)
    await monitor.add_event(2)
    await monitor.add_event(0)

    task = asyncio.create_task(wait_for_events())
    await asyncio.sleep(0.1)
    assert events == [1, 2]

    await monitor.add_event(3)
    await asyncio.sleep(0.1)
    assert events == [1, 2, 3]

    assert task.done()
    await monitor.stop()
예제 #2
0
async def test_waitable_monitor_timeout_error():
    """Test if `WaitableMonitor.wait_for_event()` raises `TimeoutError` on timeout."""

    monitor = EventMonitor()
    monitor.start()

    with pytest.raises(asyncio.TimeoutError):
        await monitor.wait_for_event(lambda e: e == 1, timeout=0.1)

    await monitor.stop()
예제 #3
0
async def test_stopped_raises_on_add_event():
    """Test whether `add_event()` invoked after stopping the monitor raises error."""

    monitor = EventMonitor()

    monitor.start()
    await monitor.stop()

    with pytest.raises(RuntimeError):
        await monitor.add_event(1)
예제 #4
0
async def test_add_assertion_while_checking():
    """Test if adding an assertion while iterating over existing assertions works.

    See https://github.com/golemfactory/goth/issues/464
    """

    monitor = EventMonitor()

    async def long_running_assertion_1(stream: Events):
        async for _ in stream:
            await asyncio.sleep(0.05)

    async def long_running_assertion_2(stream: Events):
        async for _ in stream:
            await asyncio.sleep(0.05)

    monitor.add_assertion(long_running_assertion_1)
    monitor.add_assertion(long_running_assertion_2)
    monitor.start()

    await monitor.add_event(1)
    await asyncio.sleep(0)
    # Add a new assertion while long_running_assertions are still being checked
    monitor.add_assertion(assert_all_positive)

    await monitor.stop()
예제 #5
0
async def test_waitable_monitor_timeout_success():
    """Test if `WaitableMonitor.wait_for_event()` return success before timeout."""

    monitor = EventMonitor()
    monitor.start()

    async def worker_task():
        await asyncio.sleep(0.1)
        await monitor.add_event(1)

    asyncio.create_task(worker_task())

    await monitor.wait_for_event(lambda e: e == 1, timeout=1.0)
    await monitor.stop()
예제 #6
0
 def __init__(self, monitor: Optional[EventMonitor[APIEvent]] = None):
     self._monitor = monitor or EventMonitor()
     if not self._monitor.is_running():
         self._monitor.start()
     self._pending_requests = {}
     self._num_requests = 0
     self._logger = logging.getLogger(__name__)
예제 #7
0
async def test_assertions():
    """Test a dummy set of assertions against a list of int's."""

    monitor: EventMonitor[int] = EventMonitor()
    monitor.add_assertions([
        assert_all_positive,
        assert_increasing,
        assert_eventually_five,
        assert_fancy_property,
    ])
    monitor.start()

    for n in [1, 3, 4, 6, 3, 8, 9, 10]:
        await monitor.add_event(n)

    # Need this sleep to make sure the assertions consume the events
    await asyncio.sleep(0.1)

    failed = {a.name.rsplit(".", 1)[-1] for a in monitor.failed}
    assert failed == {"assert_increasing"}

    satisfied = {a.name.rsplit(".", 1)[-1] for a in monitor.satisfied}
    assert satisfied == {"assert_fancy_property"}

    # Certain assertions can only accept/fail after the monitor is stopped
    await monitor.stop()

    failed = {a.name.rsplit(".", 1)[-1] for a in monitor.failed}
    assert failed == {"assert_increasing", "assert_eventually_five"}

    satisfied = {a.name.rsplit(".", 1)[-1] for a in monitor.satisfied}
    assert satisfied == {"assert_all_positive", "assert_fancy_property"}
예제 #8
0
async def test_not_started_raises_on_add_event():
    """Test whether `add_event()` invoked before starting the monitor raises error."""

    monitor = EventMonitor()

    with pytest.raises(RuntimeError):
        await monitor.add_event(1)
예제 #9
0
    def __init__(
        self,
        node_names: Mapping[str, str],
        ports: Mapping[str, dict],
        assertions_module: Optional[str] = None,
    ):
        self._node_names = node_names
        self._ports = ports
        self._logger = logging.getLogger(__name__)
        self._proxy_thread = threading.Thread(target=self._run_mitmproxy,
                                              name="ProxyThread",
                                              daemon=True)
        self._server_ready = threading.Event()
        self._mitmproxy_runner = None

        self.monitor = EventMonitor("rest", self._logger)
        if assertions_module:
            self.monitor.load_assertions(assertions_module)
예제 #10
0
async def test_check_assertions(caplog):
    """Test the `Runner.check_assertion_errors()` method."""

    runner = Runner(
        base_log_dir=Path(tempfile.mkdtemp()),
        compose_config=Mock(),
    )

    async def assertion(events):
        async for _ in events:
            break
        async for _ in events:
            raise AssertionError("Just failing")

    idle_monitor = EventMonitor()
    idle_monitor.start()
    busy_monitor = EventMonitor()
    busy_monitor.add_assertion(assertion)
    busy_monitor.start()

    await asyncio.sleep(0.1)
    runner.check_assertion_errors(idle_monitor, busy_monitor)

    await busy_monitor.add_event(1)
    await asyncio.sleep(0.1)
    runner.check_assertion_errors(idle_monitor, busy_monitor)

    await busy_monitor.add_event(2)
    await asyncio.sleep(0.1)
    # Assertion failure should be logged at this point
    assert any(record.levelname == "ERROR" for record in caplog.records)
    # And `check_assertion_errors()` should raise an exception
    with pytest.raises(TemporalAssertionError):
        runner.check_assertion_errors(idle_monitor, busy_monitor)

    await busy_monitor.stop()
    await idle_monitor.stop()
    with pytest.raises(TemporalAssertionError):
        runner.check_assertion_errors(idle_monitor, busy_monitor)
예제 #11
0
async def test_assertion_results_reported(caplog):
    """Test that assertion success and failure are logged.

    This used to be a problem for assertions that do not succeed or fail
    immediately after consuming an event. For example if an assertion
    contains `asyncio.wait_for()` then it may raise an exception some time
    after it consumed any event. After the failure, the monitor will not
    feed new events to the assertion. But it should report the failure
    (exactly once).
    """

    monitor = EventMonitor()

    async def never_accept(events):
        async for _ in events:
            pass

    async def await_impossible(events):
        await asyncio.wait_for(never_accept(events), timeout=0.1)

    async def await_inevitable(events):
        try:
            await asyncio.wait_for(never_accept(events), timeout=0.1)
        except asyncio.TimeoutError:
            return "I'm fine!"

    monitor.add_assertion(await_impossible)
    monitor.add_assertion(await_inevitable)
    monitor.start()

    await monitor.add_event(1)
    # At this point the assertions are still alive
    assert not monitor.done

    await asyncio.sleep(0.3)
    # The assertions should be done now
    assert monitor.failed
    assert monitor.satisfied

    # Stopping the monitor should trigger logging assertion success and
    # failure messages
    await monitor.stop()

    assert any(record.levelname == "ERROR" and "failed" in record.message
               for record in caplog.records)
    assert any(record.levelname == "INFO" and "I'm fine!" in record.message
               for record in caplog.records)
예제 #12
0
class Proxy:
    """Proxy using mitmproxy to generate events out of http calls."""

    monitor: EventMonitor[APIEvent]
    _proxy_thread: threading.Thread
    _logger: logging.Logger
    _mitmproxy_runner: Optional[dump.DumpMaster]
    _node_names: Mapping[str, str]
    _server_ready: threading.Event
    """Mapping of IP addresses to node names"""

    _ports: Mapping[str, dict]
    """Mapping of IP addresses to their port mappings"""
    def __init__(
        self,
        node_names: Mapping[str, str],
        ports: Mapping[str, dict],
        assertions_module: Optional[str] = None,
    ):
        self._node_names = node_names
        self._ports = ports
        self._logger = logging.getLogger(__name__)
        self._proxy_thread = threading.Thread(target=self._run_mitmproxy,
                                              name="ProxyThread",
                                              daemon=True)
        self._server_ready = threading.Event()
        self._mitmproxy_runner = None

        self.monitor = EventMonitor("rest", self._logger)
        if assertions_module:
            self.monitor.load_assertions(assertions_module)

    def start(self):
        """Start the proxy thread."""
        self.monitor.start()
        self._proxy_thread.start()
        self._server_ready.wait()

    async def stop(self):
        """Stop the proxy thread and the monitor."""
        if self._mitmproxy_runner:
            self._mitmproxy_runner.shutdown()
        self._proxy_thread.join()
        self._logger.info("The mitmproxy thread has finished")
        await self.monitor.stop()

    def _run_mitmproxy(self):
        """Run by `self.proxy_thread`."""

        # This class is nested since it needs to refer to the `monitor` attribute
        # of the enclosing instance of `Proxy`.
        class MITMProxyRunner(dump.DumpMaster):
            def __init__(inner_self, opts: options.Options) -> None:
                super().__init__(opts)
                inner_self.addons.add(
                    RouterAddon(self._node_names, self._ports))
                inner_self.addons.add(MonitorAddon(self.monitor))

            def start(inner_self):
                super().start()
                self._mitmproxy_runner = inner_self
                self._logger.info("Embedded mitmproxy started")
                self._server_ready.set()

        try:
            loop = asyncio.new_event_loop()
            # Monkey patch the loop to set its `add_signal_handler` method to no-op.
            # The original method would raise error since the loop will run in
            # a non-main thread and hence cannot have signal handlers installed.
            loop.add_signal_handler = lambda *args_: None
            asyncio.set_event_loop(loop)

            self._logger.info("Starting embedded mitmproxy...")

            args = f"-q --mode reverse:http://127.0.0.1 --listen-port {MITM_PROXY_PORT}"
            _main.run(MITMProxyRunner, cmdline.mitmdump, args.split())

        except Exception:
            self._logger.exception("Exception in mitmproxy thread")

        self._logger.info("Embedded mitmproxy exited")
예제 #13
0
async def test_demand_resubscription(log_dir: Path, goth_config_path: Path,
                                     monkeypatch) -> None:
    """Test that checks that a demand is re-submitted after its previous submission expires."""

    configure_logging(log_dir)

    # Override the default test configuration to create only one provider node
    nodes = [
        {
            "name": "requestor",
            "type": "Requestor"
        },
        {
            "name": "provider-1",
            "type": "VM-Wasm-Provider",
            "use-proxy": True
        },
    ]
    goth_config = load_yaml(goth_config_path, [("nodes", nodes)])

    vm_package = await vm.repo(
        image_hash="9a3b5d67b0b27746283cb5f287c13eab1beaa12d92a9f536b747c7ae",
        min_mem_gib=0.5,
        min_storage_gib=2.0,
    )

    runner = Runner(base_log_dir=log_dir,
                    compose_config=goth_config.compose_config)

    async with runner(goth_config.containers):

        requestor = runner.get_probes(probe_type=RequestorProbe)[0]
        env = dict(os.environ)
        env.update(requestor.get_agent_env_vars())

        # Setup the environment for the requestor
        for key, val in env.items():
            monkeypatch.setenv(key, val)

        monitor = EventMonitor()
        monitor.add_assertion(assert_demand_resubscribed)
        monitor.start()

        # The requestor

        enable_default_logger()

        async def worker(work_ctx, tasks):
            async for task in tasks:
                script = work_ctx.new_script()
                script.run("/bin/sleep", "5")
                yield script
                task.accept_result()

        async with Golem(
                budget=10.0,
                event_consumer=monitor.add_event_sync,
        ) as golem:

            task: Task  # mypy needs this for some reason
            async for task in golem.execute_tasks(
                    worker,
                [Task(data=n) for n in range(20)],
                    vm_package,
                    max_workers=1,
                    timeout=timedelta(seconds=30),
            ):
                logger.info("Task %d computed", task.data)

        await monitor.stop()
        for a in monitor.failed:
            raise a.result()