예제 #1
0
async def mock_scicrunch_service_api(fake_data_dir: Path,
                                     mock_env_devel_environment):
    assert mock_env_devel_environment["SCICRUNCH_API_KEY"] == os.environ.get(
        "SCICRUNCH_API_KEY")

    API_KEY = os.environ.get("SCICRUNCH_API_KEY")
    assert os.environ.get(
        "SCICRUNCH_API_BASE_URL") == "https://scicrunch.org/api/1"

    with aioresponses() as mock:
        # curl -X GET "https://scicrunch.org/api/1/resource/fields/autocomplete?field=Resource%20Name&value=octave" -H "accept: application/json
        mock.get(
            f"https://scicrunch.org/api/1/resource/fields/autocomplete?field=Resource%20Name&value=octave&key={API_KEY}",
            status=200,
            payload={
                "data": [
                    {
                        "rid": "SCR_000860",
                        "original_id": "nlx_155680",
                        "name": "cbiNifti: Matlab/Octave Nifti library",
                    },
                    {
                        "rid": "SCR_009637",
                        "original_id": "nlx_155924",
                        "name": "Pipeline System for Octave and Matlab",
                    },
                    {
                        "rid": "SCR_014398",
                        "original_id": "SCR_014398",
                        "name": "GNU Octave",
                    },
                ],
                "success":
                "true",
            },
        )
        # curl -X GET "https://scicrunch.org/api/1/resource/fields/view/SCR_018997" -H "accept: application/json"
        mock.get(
            f"https://scicrunch.org/api/1/resource/fields/view/SCR_018997?key={API_KEY}",
            status=200,
            payload=json.loads(
                (fake_data_dir /
                 "get_osparc_resource_payload.json").read_text()),
        )
        # curl -X GET "https://scicrunch.org/api/1/resource/versions/all/SCR_018997" -H "accept: application/json"
        mock.get(
            f"https://scicrunch.org/api/1/resource/versions/all/SCR_018997?key={API_KEY}",
            status=200,
            payload=json.loads(
                '{"data":[{"version":2,"status":"Curated","time":1598984801,"uid":34739,"username":"******","cid":null},{"version":1,"status":"Pending","time":1598898249,"uid":43,"username":"******","cid":30}],"success":true}'
            ),
        )

        yield mock
async def test_unauntheticated_request_to_scicrunch(client):

    with aioresponses() as scicrunch_service_api_mock:
        scicrunch_service_api_mock.get(
            re.compile(r"^https://scicrunch\.org/api/1/resource/fields/view/.*"),
            status=web_exceptions.HTTPUnauthorized.status_code,
        )

        with pytest.raises(web_exceptions.HTTPBadRequest):
            await client.post(
                "/v0/groups/sparc/classifiers/scicrunch-resources/SCR_018997",
                raise_for_status=True,
            )
예제 #3
0
async def _start_get_stop_services(
    client,
    push_services,
    user_id,
    project_id,
    api_version_prefix: str,
    save_state: Optional[bool],
    expected_save_state_call: bool,
    mocker,
):
    params = {}
    web_response = await client.post(
        f"/{api_version_prefix}/running_interactive_services", params=params)
    assert web_response.status == 400

    params = {
        "user_id": "None",
        "project_id": "None",
        "service_uuid": "sdlfkj4",
        "service_key": "None",
        "service_tag": "None",  # optional
        "service_basepath": "None",  # optional
    }
    web_response = await client.post(
        f"/{api_version_prefix}/running_interactive_services", params=params)
    data = await web_response.json()
    assert web_response.status == 400, data

    params["service_key"] = "simcore/services/comp/somfunkyname-nhsd"
    params["service_tag"] = "1.2.3"
    web_response = await client.post(
        f"/{api_version_prefix}/running_interactive_services", params=params)
    data = await web_response.json()
    assert web_response.status == 404, data

    created_services = await push_services(0, 2)
    assert len(created_services) == 2
    for created_service in created_services:
        service_description = created_service["service_description"]
        params["user_id"] = user_id
        params["project_id"] = project_id
        params["service_key"] = service_description["key"]
        params["service_tag"] = service_description["version"]
        service_port = created_service["internal_port"]
        service_entry_point = created_service["entry_point"]
        params["service_basepath"] = "/i/am/a/basepath"
        params["service_uuid"] = str(uuid.uuid4())
        # start the service
        web_response = await client.post(
            f"/{api_version_prefix}/running_interactive_services",
            params=params)
        assert web_response.status == 201
        assert web_response.content_type == "application/json"
        running_service_enveloped = await web_response.json()
        assert isinstance(running_service_enveloped["data"], dict)
        assert all(k in running_service_enveloped["data"] for k in [
            "service_uuid",
            "service_key",
            "service_version",
            "published_port",
            "entry_point",
            "service_host",
            "service_port",
            "service_basepath",
        ])
        assert (running_service_enveloped["data"]["service_uuid"] ==
                params["service_uuid"])
        assert running_service_enveloped["data"]["service_key"] == params[
            "service_key"]
        assert (running_service_enveloped["data"]["service_version"] ==
                params["service_tag"])
        assert running_service_enveloped["data"][
            "service_port"] == service_port
        service_published_port = running_service_enveloped["data"][
            "published_port"]
        assert not service_published_port
        assert service_entry_point == running_service_enveloped["data"][
            "entry_point"]
        service_host = running_service_enveloped["data"]["service_host"]
        assert service_host == f"test_{params['service_uuid']}"
        service_basepath = running_service_enveloped["data"][
            "service_basepath"]
        assert service_basepath == params["service_basepath"]

        # get the service
        web_response = await client.request(
            "GET",
            f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}",
        )
        assert web_response.status == 200
        text = await web_response.text()
        assert web_response.content_type == "application/json", text
        running_service_enveloped = await web_response.json()
        assert isinstance(running_service_enveloped["data"], dict)
        assert all(k in running_service_enveloped["data"] for k in [
            "service_uuid",
            "service_key",
            "service_version",
            "published_port",
            "entry_point",
        ])
        assert (running_service_enveloped["data"]["service_uuid"] ==
                params["service_uuid"])
        assert running_service_enveloped["data"]["service_key"] == params[
            "service_key"]
        assert (running_service_enveloped["data"]["service_version"] ==
                params["service_tag"])
        assert (running_service_enveloped["data"]["published_port"] ==
                service_published_port)
        assert running_service_enveloped["data"][
            "entry_point"] == service_entry_point
        assert running_service_enveloped["data"][
            "service_host"] == service_host
        assert running_service_enveloped["data"][
            "service_port"] == service_port
        assert running_service_enveloped["data"][
            "service_basepath"] == service_basepath

        # stop the service
        query_params = {}
        if save_state:
            query_params.update(
                {"save_state": "true" if save_state else "false"})

        mocked_save_state_cb = mocker.MagicMock(
            return_value=CallbackResult(status=200, payload={}))
        PASSTHROUGH_REQUESTS_PREFIXES = [
            "http://127.0.0.1",
            "http://localhost",
            "unix://",  # docker engine
            "ws://",  # websockets
        ]
        with aioresponses(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock:

            # POST /http://service_host:service_port service_basepath/state -------------------------------------------------
            mock.post(
                f"http://{service_host}:{service_port}{service_basepath}/state",
                status=200,
                callback=mocked_save_state_cb,
            )
            web_response = await client.delete(
                f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}",
                params=query_params,
            )
            if expected_save_state_call:
                mocked_save_state_cb.assert_called_once()

        text = await web_response.text()
        assert web_response.status == 204, text
        assert web_response.content_type == "application/json"
        data = await web_response.json()
        assert data is None
예제 #4
0
def mock_director_service(
    model_fake_factory,
    running_service_model_schema,
    registry_service_model_schema,
    user_id: int,
    project_id: str,
    project_nodes: List[Tuple[str, ...]],
):

    # helpers
    def fake_registry_service_model(**overrides):
        return model_fake_factory(registry_service_model_schema, **overrides)

    def fake_running_service_model(**overrides):
        return model_fake_factory(running_service_model_schema, **overrides)

    # fake director service "state" variables
    _fake_project_services = [
        fake_registry_service_model(key=s[0], version=s[1])
        for s in project_nodes
    ]

    _fake_running_services = []

    def _get_running():
        return [
            s for s in _fake_running_services
            if s["service_state"] not in "complete failed".split()
        ]

    #
    # Mocks director's service API  api/specs/director/openapi.yaml
    #
    with aioresponses() as mock:

        # GET /running_interactive_services -------------------------------------------------
        url_pattern = (
            r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services\?.*$"
        )

        def get_running_interactive_services_handler(url: URL, **kwargs):
            assert int(url.query.get("user_id", 0)) in (user_id, 0)
            assert url.query.get("project_id") in (project_id, None)

            return CallbackResult(payload={"data": _get_running()}, status=200)

        mock.get(
            re.compile(url_pattern),
            callback=get_running_interactive_services_handler,
            repeat=True,
        )

        # POST /running_interactive_services -------------------------------------------------

        url_pattern = (
            r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services\?.*$"
        )

        def start_service_handler(url: URL, **kwargs):
            assert int(url.query["user_id"]) == user_id
            assert url.query["project_id"] == project_id

            service_uuid = url.query["service_uuid"]

            if any(s["service_uuid"] == service_uuid for s in _get_running()):
                return CallbackResult(
                    payload={
                        "error":
                        f"A service with the same uuid {service_uuid} already running exists"
                    },
                    status=409,
                )

            service_key = url.query["service_key"]
            service_tag = url.query.get("service_tag")

            try:
                service = next(
                    s for s in _fake_project_services
                    if s["key"] == service_key and s["version"] == service_tag)
            except StopIteration:
                return CallbackResult(
                    payload={
                        "error":
                        f"Service {service_key}:{service_tag} not found in project"
                    },
                    status=404,
                )

            starting_service = fake_running_service_model(
                service_key=service["key"],
                service_version=service["version"],
                service_basepath=url.query.get(
                    "service_basepath", ""
                ),  # predefined basepath for the backend service otherwise uses root
                service_state="starting",  # let's assume it pulls very fast :-)
            )

            _fake_running_services.append(starting_service)
            return CallbackResult(payload={"data": starting_service},
                                  status=201)

        mock.post(re.compile(url_pattern),
                  callback=start_service_handler,
                  repeat=True)

        # DELETE /running_interactive_services/{service_uuid}-------------------------------------------------
        url_pattern = (
            r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services/.+$"
        )

        def stop_service_handler(url: URL, **kwargs):
            service_uuid = url.parts[-1]
            required_query_parameter = "save_state"
            if required_query_parameter not in kwargs["params"]:
                return CallbackResult(
                    payload={
                        "error":
                        f"stop_service endpoint was called without query parameter {required_query_parameter} {url}"
                    },
                    status=500,
                )

            try:
                service = next(s for s in _fake_running_services
                               if s["service_uuid"] == service_uuid)
                service["service_state"] = "complete"

            except StopIteration:
                return CallbackResult(payload={"error": "service not found"},
                                      status=404)

            return CallbackResult(status=204)

        mock.delete(re.compile(url_pattern),
                    callback=stop_service_handler,
                    repeat=True)

        # GET /services/{service_key}/{service_version}-------------------------------------------------
        url_pattern = r"^http://[a-z\-_]*director:[0-9]+/v0/services/.+/.+$"

        def get_service_by_key_version_handler(url: URL, **kwargs):
            service_key, service_version = url.parts[-2:]
            # TODO: validate against schema?
            # TODO: improve API: does it make sense to return multiple services
            services = [
                s for s in _fake_project_services
                if s["key"] == service_key and s["version"] == service_version
            ]
            if services:
                return CallbackResult(payload={"data": services}, status=200)

            return CallbackResult(
                payload={
                    "error":
                    f"Service {service_key}:{service_version} not found in project"
                },
                status=404,
            )

        mock.get(
            re.compile(url_pattern),
            callback=get_service_by_key_version_handler,
            repeat=True,
        )

        # GET /service_extras/{service_key}/{service_version}
        # get_service_extras = re.compile(
        #    r"^http://[a-z\-_]*director:[0-9]+/v0/service_extras/\w+/.+$"
        # )

        ## mock.get(get_service_extras, status=200, payload={"data": {}})
        yield mock