Esempio n. 1
0
def test_get_options(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        status=404,
    )

    with pytest.raises(ServiceNotFoundError, match="No service named <ECHO>"):
        client.get_options("ECHO")
Esempio n. 2
0
def test_transform_document_pipe_no_service(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        status=404,
    )

    with pytest.raises(ServiceNotFoundError, match="No service named <ECHO>"):
        client.transform_document_pipe(request=TransformDocumentPipeRequest(
            document="Hello World",
            services=[PipeService(name="ECHO"),
                      PipeService(name="COUNT")],
        ))
Esempio n. 3
0
 def __init__(
     self,
     work: Optional[Worker],
     config: "ServiceConfig",
     options_type: Optional[Type[BaseModel]],
     client: Optional[Client] = None,
 ) -> None:
     self._work = work
     self.config = config
     if client is None:
         self.client = Client(ClientConfig(registrar_url=config.registrar_url))
     else:
         self.client = client
     self.options_type = options_type
     log.info("initialized abc worker.")
Esempio n. 4
0
def test_rest_transform_pipe(eureka_server):
    print("start pipe test")
    config = ClientConfig("http://127.0.0.1:8761/eureka")
    client = Client(config)
    response = client.transform_document_pipe(
        TransformDocumentPipeRequest(
            document="Hello World",
            services=[
                PipeService(name="TEST1"),
                PipeService(name="TEST2"),
                PipeService(name="TEST3"),
            ],
        ))
    assert response.document == "Hello World,TEST1,TEST2,TEST3"
    assert response.last_transformer == "TEST3"
Esempio n. 5
0
def test_transform_document_pipe_with_instance_address(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.POST,
        uri="http://localhost:50000/v1/document/transform-pipe",
        body=json.dumps({
            "document":
            "Hello World!!",
            "output": ["transforming document <simpletext.txt> ...", "lol"],
            "error": ["Unknown Exceptions"],
            "last_transformer":
            "COUNT",
        }),
        status=200,
    )

    response = client.transform_document_pipe(
        request=TransformDocumentPipeRequest(
            document="Hello World",
            services=[PipeService(name="ECHO"),
                      PipeService(name="COUNT")],
        ),
        instance_address="localhost:50000",
    )

    assert response.document == "Hello World!!"
    assert response.last_transformer == "COUNT"
    assert not response.output is None
    assert not response.error is None
    assert len(response.output) == 2
    assert len(response.error) == 1
Esempio n. 6
0
def test_get_options_with_instance_address(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:50000/v1/service/options",
        body=json.dumps({
            "properties": {
                "offset": {
                    "default": 0,
                    "title": "Offset",
                    "type": "string"
                }
            },
            "title": "Options",
            "type": "object",
        }),
        status=200,
    )

    response = client.get_options("ECHO", instance_address="localhost:50000")
    assert response == {
        "properties": {
            "offset": {
                "default": 0,
                "title": "Offset",
                "type": "string"
            }
        },
        "title": "Options",
        "type": "object",
    }
Esempio n. 7
0
def test_transform_document_pipe(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        body=EUREKA_GET_APPS_ECHO_RESPONSE,
        status=200,
    )

    httpretty.register_uri(
        method=httpretty.POST,
        uri="http://localhost:50000/v1/document/transform-pipe",
        body=json.dumps({
            "document":
            "Hello Worlds!",
            "output": ["transforming document <simpletext.txt> ...", "lol"],
            "error": ["Unknown Exceptions"],
            "last_transformer":
            "COUNT",
        }),
        status=200,
    )

    response = client.transform_document_pipe(
        request=TransformDocumentPipeRequest(
            document="Hello World",
            services=[PipeService(name="ECHO"),
                      PipeService(name="COUNT")],
        ))

    assert response.document == "Hello Worlds!"
    assert response.last_transformer == "COUNT"
    assert not response.output is None
    assert not response.error is None
    assert len(response.output) == 2
    assert len(response.error) == 1
Esempio n. 8
0
def test_transform_document_with_instance_address(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.POST,
        uri="http://localhost:50000/v1/document/transform",
        body=json.dumps({
            "document":
            "Hello Worlds!",
            "output": ["transforming document <simpletext.txt> ...", "lol"],
            "error": ["Unknown Exceptions"],
        }),
        status=200,
    )

    response = client.transform_document(
        TransformDocumentRequest(
            document="Hello world!",
            service_name="ECHO",
            file_name="simpletext.txt",
            options={
                "offset": 5,
                "debug": True
            },
        ),
        instance_address="localhost:50000",
    )

    assert not response is None
    assert response.document == "Hello Worlds!"
    assert not response.output is None
    assert len(response.output) == 2
    assert response.output == [
        "transforming document <simpletext.txt> ...", "lol"
    ]
    assert response.error == ["Unknown Exceptions"]
Esempio n. 9
0
def test_transform_document_pipe_with_instance_address_no_service(
        monkeypatch, client: Client) -> None:
    monkeypatch.setattr("morpho.client.requests.post", raise_connection_error)

    with pytest.raises(requests.exceptions.ConnectionError):
        client.transform_document(
            TransformDocumentRequest(
                document="Hello world!",
                service_name="ECHO",
                file_name="simpletext.txt",
                options={
                    "offset": 5,
                    "debug": True
                },
            ),
            instance_address="localhost:50000",
        )
Esempio n. 10
0
def test_transform_document_no_service(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        status=404,
    )

    with pytest.raises(ServiceNotFoundError, match="No service named <ECHO>"):
        client.transform_document(
            TransformDocumentRequest(
                document="Hello world!",
                service_name="ECHO",
                file_name="simpletext.txt",
                options={
                    "offset": 5,
                    "debug": True
                },
            ))
Esempio n. 11
0
def test_transform_document_with_instance_address_no_service(
        monkeypatch) -> None:
    config = ClientConfig(registrar_url="http://localhost:8761/eureka")
    client = Client(config)

    monkeypatch.setattr("morpho.client.requests.post", raise_connection_error)

    with pytest.raises(requests.exceptions.ConnectionError):
        client.transform_document(
            TransformDocumentRequest(
                document="Hello world!",
                service_name="ECHO",
                file_name="simpletext.txt",
                options={
                    "offset": 5,
                    "debug": True
                },
            ),
            instance_address="localhost:50000",
        )
Esempio n. 12
0
def test_list_services_with_instance_address(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:50000/v1/service/list",
        body=json.dumps({"services": [{
            "name": "ECHO"
        }]}),
        status=200,
    )

    response = client.list_services("ECHO", instance_address="localhost:50000")

    assert len(response.services) == 1
    service = response.services[0]
    assert service.name == "ECHO"
Esempio n. 13
0
def test_list_services(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        body=EUREKA_GET_APPS_ECHO_RESPONSE,
        status=200,
    )

    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:50000/v1/service/list",
        body=json.dumps({"services": [{
            "name": "ECHO"
        }]}),
        status=200,
    )

    response = client.list_services("ECHO")

    assert len(response.services) == 1
    service = response.services[0]
    assert service.name == "ECHO"
Esempio n. 14
0
 def __init__(
     self,
     work: Optional[Worker],
     config: ServiceConfig,
     options_type: Optional[Type[BaseModel]],
 ) -> None:
     # consumers are only invoked with ServiceConfig
     # so we need to cast to the RestGatewayServiceConfig
     super().__init__(
         work=work,
         config=config,
         options_type=options_type,
         client=Client(
             ClientConfig(
                 registrar_url=cast(RestGatewayServiceConfig, config).resolver_url
             )
         ),
     )
     # TODO: somehow remove one of the config instances
     # self.gateway_config = cast(config
     # TODO: discuss: set implicit the default type to Gateway
     self.config.type = DtaType.GATEWAY
Esempio n. 15
0
def test_get_options(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        body=EUREKA_GET_APPS_ECHO_RESPONSE,
        status=200,
    )

    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:50000/v1/service/options",
        body=json.dumps({
            "properties": {
                "offset": {
                    "default": 0,
                    "title": "Offset",
                    "type": "integer"
                }
            },
            "title": "Options",
            "type": "object",
        }),
        status=200,
    )

    response = client.get_options("ECHO")
    assert response == {
        "properties": {
            "offset": {
                "default": 0,
                "title": "Offset",
                "type": "integer"
            }
        },
        "title": "Options",
        "type": "object",
    }
Esempio n. 16
0
def test_transform_document(client: Client) -> None:
    httpretty.register_uri(
        method=httpretty.GET,
        uri="http://localhost:8761/eureka/apps/ECHO",
        body=EUREKA_GET_APPS_ECHO_RESPONSE,
        status=200,
    )

    httpretty.register_uri(
        method=httpretty.POST,
        uri="http://localhost:50000/v1/document/transform",
        body=json.dumps({
            "document":
            "Hello World!",
            "output": ["transforming document <simpletext.txt> ..."],
            "error": ["Unknown Exception"],
        }),
        status=200,
    )

    response = client.transform_document(
        TransformDocumentRequest(
            document="Hello world!",
            service_name="ECHO",
            file_name="simpletext.txt",
            options={
                "offset": 5,
                "debug": True
            },
        ))

    assert not response is None
    assert response.document == "Hello World!"
    assert not response.output is None
    assert len(response.output) == 1
    assert response.output == ["transforming document <simpletext.txt> ..."]
    assert response.error == ["Unknown Exception"]
Esempio n. 17
0
from morpho.rest.models import TransformDocumentRequest
from morpho.client import Client
from morpho.client import ClientConfig

morpho = Client(ClientConfig("http://localhost:8761/eureka/"))

request = TransformDocumentRequest(document="This is a Document!",
                                   service_name="Echo")

response = morpho.transform_document(request=request)
Esempio n. 18
0
class WorkConsumer(ABC):
    """An abstract class which must be implemented by each ``work`` consumer.

    Attributes:
        work (Callable[[str], str]): Worker function which will executed to get the
                                     transformed document.
        config (ServiceConfig): Configuration for the given Server.

    Note:
        The ``work`` callback should be called once on a implemented work consumer after the
        server received the request. After receiving the request the document should be already
        be correctly marshalled.
    """

    def __init__(
        self,
        work: Optional[Worker],
        config: "ServiceConfig",
        options_type: Optional[Type[BaseModel]],
        client: Optional[Client] = None,
    ) -> None:
        self._work = work
        self.config = config
        if client is None:
            self.client = Client(ClientConfig(registrar_url=config.registrar_url))
        else:
            self.client = client
        self.options_type = options_type
        log.info("initialized abc worker.")

    def _get_applications(self) -> Applications:
        try:
            return eureka_client.get_applications(self.config.registrar_url)
        # TODO: add custom eureka not found error
        except URLError:
            log.error("no eureka instance is running at: %s", self.config.registrar_url)
            exit(1)

    def health(self) -> Health:
        return self.config.health

    def list_services(self) -> ListServicesResponse:
        """Lists all services from the eureka server of the provided ``ServiceConfig``.
        
        Returns:
            List[ListServicesResponse]: List of services.
        """
        # return the service itself if the service is not registered at eureka
        if not self.config.should_register:
            return ListServicesResponse(
                services=[
                    ServiceInfo(name=self.config.name.upper(), options=self.options())
                ]
            )

        applications = self._get_applications()
        cached_applications: List[Tuple[str, str]] = []

        # check if we can find a available gateway
        for service in applications.applications:
            instance = service.instances[0]

            # skip self
            if instance.app == self.config.name.upper():
                continue
            instance_address = f"{instance.ipAddr}:{instance.port.port}"

            if instance.metadata:
                try:
                    dta_type = DtaType(instance.metadata.get("dtaType"))
                except ValueError:
                    dta_type = DtaType.UNKNOWN

                if dta_type == dta_type.GATEWAY:
                    log.info(
                        "found gateway send list request directly to <%s (%s)>",
                        instance.app,
                        instance_address,
                    )
                    # TODO: gateways could cache the whole list of services
                    return self.client.list_services(
                        instance.app, instance_address=instance_address
                    )

            # cache the list of instance_addresses
            cached_applications.append((instance.app, instance_address))

        services = [ServiceInfo(name=self.config.name.upper(), options=self.options())]
        # iterate over cached applications and create ListServicesResponse
        for cached_application in cached_applications:
            app_name, instance_address = cached_application
            # get options from service via rest call
            options = self.client.get_options(
                service_name=app_name, instance_address=instance_address
            )
            services.append(ServiceInfo(name=app_name, options=options))

        return ListServicesResponse(services=services)

    def options(self) -> Schema:
        """Lists available options of the service.

        Returns:
            Schema: A Schema representing the different available options. Returns an empty dictionary `{}` if no option is present.
        """
        if self.options_type is None:
            return {}
        return self.options_type.schema()

    def transform_document(
        self, request: TransformDocumentRequest,
    ) -> TransformDocumentResponse:
        document = None
        # TODO: create a decorator for capturing stdout and stderr
        # TODO: consider to move this into base class
        captured_stdout = io.StringIO()
        captured_stderr = io.StringIO()
        with redirect_stderr(captured_stderr):
            with redirect_stdout(captured_stdout):
                options = (
                    self.options_type(**request.options) if self.options_type else None
                )
                if self._work is None:
                    raise NoWorkerFunctionError("No worker function specified!")
                try:
                    if options is None:
                        document = self._work(request.document)
                    else:
                        document = self._work(request.document, options)
                except BaseException:  # pylint: disable=broad-except
                    traceback.print_exc()
        error = captured_stderr.getvalue().splitlines()
        output = captured_stdout.getvalue().splitlines()
        captured_stderr.close()
        captured_stdout.close()
        log.info("-- after transform document --")
        log.info("document: %s", document)
        log.info("error: %s", error)
        log.info("output: %s", output)
        # TODO: test on none return type (if an error occurs) so the error
        # will be still be transferred to the client
        return TransformDocumentResponse(document=document, output=output, error=error)

    def transform_document_pipe(
        self, request: TransformDocumentPipeRequest
    ) -> TransformDocumentPipeResponse:
        log.info("transform document pipe was called with: <%s>", request.document)
        pipe_service = request.services.pop(0)
        transform_response = self.transform_document(
            TransformDocumentRequest(
                document=request.document,
                service_name=pipe_service.name,
                file_name=request.file_name,
                options=pipe_service.options,
            )
        )
        # return a response if there are only 1 service left
        # this means that the pipe is either finished or will
        # be interupted because of an occured error
        if request.services == [] or transform_response.error:
            log.info(
                "reached end of pipe returning response: %s",
                transform_response.document,
            )
            return TransformDocumentPipeResponse(
                document=transform_response.document,
                output=transform_response.output,
                last_transformer=self.config.name,
                error=transform_response.error,
            )

        request.document = transform_response.document
        response = self.client.transform_document_pipe(request=request)

        # cancat outputs in the right order
        response.output = transform_response.output + response.output
        response.error = transform_response.error + response.error

        return response

    @abstractmethod
    def start(self) -> None:
        """starts the implemented server instance.

        Raises:
            NotImplementedError: Must be implemented on the derived class.

        Hint:
            Can be used to instantiate a new ``Thread`` for the listening server of the Server.
            Creating a new Thread with ``daemon=True`` helps the thread to destroy its self 
            which will then gracefully shutdown if the Server gets terminated
            (preventing zombie threads).

        Important:
            This function should not block.
        """
        raise NotImplementedError
Esempio n. 19
0
def test_get_options_with_instance_address_no_service(monkeypatch,
                                                      client: Client) -> None:
    monkeypatch.setattr("morpho.client.requests.get", raise_connection_error)

    with pytest.raises(requests.exceptions.ConnectionError):
        client.get_options("ECHO", instance_address="localhost:50000")
Esempio n. 20
0
def client():
    config = ClientConfig(registrar_url="http://localhost:8761/eureka")
    yield Client(config)