예제 #1
0
def test_generic_exception():
    headers = {"Content-Type": "application/cloudevents+json"}
    data = json.dumps({
        "specversion": "1.0",
        "source": "s",
        "type": "t",
        "id": "1234-1234-1234",
        "data": "",
    })
    with pytest.raises(cloud_exceptions.GenericException) as e:
        from_http({}, None)
    e.errisinstance(cloud_exceptions.MissingRequiredFields)

    with pytest.raises(cloud_exceptions.GenericException) as e:
        from_http({}, 123)
    e.errisinstance(cloud_exceptions.InvalidStructuredJSON)

    with pytest.raises(cloud_exceptions.GenericException) as e:
        from_http(headers, data, data_unmarshaller=lambda x: 1 / 0)
    e.errisinstance(cloud_exceptions.DataUnmarshallerError)

    with pytest.raises(cloud_exceptions.GenericException) as e:
        event = from_http(headers, data)
        to_binary(event, data_marshaller=lambda x: 1 / 0)
    e.errisinstance(cloud_exceptions.DataMarshallerError)
예제 #2
0
def test_none_data_cloudevent(specversion):
    event = CloudEvent({
        "source": "<my-url>",
        "type": "issue.example",
        "specversion": specversion,
    })
    to_binary(event)
    to_structured(event)
예제 #3
0
    async def test_predict_ce_avro_binary(self, http_server_client):
        schema = avro.schema.parse(test_avsc_schema)
        msg = {"name": "foo", "favorite_number": 1, "favorite_color": "pink"}

        writer = avro.io.DatumWriter(schema)
        bytes_writer = io.BytesIO()
        encoder = avro.io.BinaryEncoder(bytes_writer)
        writer.write(msg, encoder)
        data = bytes_writer.getvalue()

        event = dummy_cloud_event(data, set_contenttype=True)
        # Creates the HTTP request representation of the CloudEvent in binary content mode
        headers, body = to_binary(event)
        resp = await http_server_client.fetch('/v1/models/TestModel:predict',
                                              method="POST",
                                              headers=headers,
                                              body=body)

        assert resp.code == 200
        assert resp.headers['content-type'] == "application/json"
        assert resp.headers['ce-specversion'] == "1.0"
        assert resp.headers["ce-id"] != "36077800-0c23-4f38-a0b4-01f4369f670a"
        assert resp.headers['ce-source'] == "io.kserve.kfserver.TestModel"
        assert resp.headers['ce-type'] == "io.kserve.inference.response"
        assert resp.headers['ce-time'] > "2021-01-28T21:04:43.144141+00:00"
        assert resp.body == b'{"predictions": [["foo", 1, "pink"]]}'
예제 #4
0
def test_create_binary_image():
    # Create image and turn image into bytes
    attributes = {
        "type": "com.example.string",
        "source": "https://example.com/event-producer",
    }

    # Create CloudEvent
    event = CloudEvent(attributes, image_bytes)

    # Create http headers/body content
    headers, body = to_binary(event)

    # Unmarshall CloudEvent and re-create image
    reconstruct_event = from_http(headers,
                                  body,
                                  data_unmarshaller=lambda x: io.BytesIO(x))

    # reconstruct_event.data is an io.BytesIO object due to data_unmarshaller
    restore_image = Image.open(reconstruct_event.data)
    assert restore_image.size == image_expected_shape

    # # Test cloudevent extension from http fields and data
    assert isinstance(body, bytes)
    assert body == image_bytes
예제 #5
0
def create_response_cloudevent(model_name: str,
                               body: Union[Dict, CloudEvent],
                               response: Dict,
                               binary_event=False) -> tuple:
    ce_attributes = {}

    if os.getenv("CE_MERGE", "false").lower() == "true":
        if binary_event:
            ce_attributes = body._attributes
            if "datacontenttype" in ce_attributes:  # Optional field so must check
                del ce_attributes["datacontenttype"]
        else:
            ce_attributes = body
            del ce_attributes["data"]

        # Remove these fields so we generate new ones
        del ce_attributes["id"]
        del ce_attributes["time"]

    ce_attributes["type"] = os.getenv("CE_TYPE",
                                      "io.kserve.inference.response")
    ce_attributes["source"] = os.getenv("CE_SOURCE",
                                        f"io.kserve.kfserver.{model_name}")

    event = CloudEvent(ce_attributes, response)

    if binary_event:
        event_headers, event_body = to_binary(event)
    else:
        event_headers, event_body = to_structured(event)

    return event_headers, event_body
예제 #6
0
def test_http_data_marshaller_exception(binary_headers, structured_data):
    # binary
    event = from_http(binary_headers, None)
    with pytest.raises(cloud_exceptions.DataMarshallerError) as e:
        to_binary(event, data_marshaller=lambda x: 1 / 0)
    assert ("Failed to marshall data with error: "
            "ZeroDivisionError('division by zero')" in str(e.value))

    # structured
    headers = {"Content-Type": "application/cloudevents+json"}

    event = from_http(headers, structured_data)
    with pytest.raises(cloud_exceptions.DataMarshallerError) as e:
        to_structured(event, data_marshaller=lambda x: 1 / 0)
    assert ("Failed to marshall data with error: "
            "ZeroDivisionError('division by zero')" in str(e.value))
예제 #7
0
    async def test_predict_merge_binary_ce_attributes(self,
                                                      http_server_client):
        with mock.patch.dict(os.environ, {"CE_MERGE": "true"}):
            event = dummy_cloud_event({"instances": [[1, 2]]},
                                      set_contenttype=True,
                                      add_extension=True)
            headers, body = to_binary(event)

            resp = await http_server_client.fetch(
                '/v1/models/TestModel:predict',
                method="POST",
                headers=headers,
                body=body)

            assert resp.code == 200
            assert resp.headers['content-type'] == "application/json"
            assert resp.headers['ce-specversion'] == "1.0"
            assert resp.headers[
                "ce-id"] != "36077800-0c23-4f38-a0b4-01f4369f670a"
            # Added by add_extension=True in dummy_cloud_event
            assert resp.headers['ce-custom-extension'] == 'custom-value'
            assert resp.headers['ce-source'] == "io.kserve.kfserver.TestModel"
            assert resp.headers['ce-type'] == "io.kserve.inference.response"
            assert resp.headers['ce-time'] > "2021-01-28T21:04:43.144141+00:00"
            assert resp.body == b'{"predictions": [[1, 2]]}'
예제 #8
0
 async def test_predict_ce_bytes_bad_format_exception(
         self, http_server_client):
     event = dummy_cloud_event(b'{', set_contenttype=True)
     headers, body = to_binary(event)
     with pytest.raises(
             HTTPClientError,
             match=r".*HTTP 400: Unrecognized request format: "
             r"Expecting property name enclosed in double quotes.*"):
         await http_server_client.fetch('/v1/models/TestModel:predict',
                                        method="POST",
                                        headers=headers,
                                        body=body)
예제 #9
0
    async def post(self, name: str):
        if has_binary_headers(self.request.headers):
            try:
                #Use default unmarshaller if contenttype is set in header
                if "ce-contenttype" in self.request.headers:
                    body = from_http(self.request.headers, self.request.body)
                else:
                    body = from_http(self.request.headers, self.request.body,
                                     lambda x: x)
            except (ce.MissingRequiredFields, ce.InvalidRequiredFields,
                    ce.InvalidStructuredJSON, ce.InvalidHeadersFormat,
                    ce.DataMarshallerError, ce.DataUnmarshallerError) as e:
                raise tornado.web.HTTPError(
                    status_code=HTTPStatus.BAD_REQUEST,
                    reason="Cloud Event Exceptions: %s" % e)
        else:
            try:
                body = json.loads(self.request.body)
            except json.decoder.JSONDecodeError as e:
                raise tornado.web.HTTPError(
                    status_code=HTTPStatus.BAD_REQUEST,
                    reason="Unrecognized request format: %s" % e)

        model = self.get_model(name)
        request = model.preprocess(body)
        request = self.validate(request)
        response = (await
                    model.predict(request)) if inspect.iscoroutinefunction(
                        model.predict) else model.predict(request)
        response = model.postprocess(response)

        if has_binary_headers(self.request.headers):
            event = CloudEvent(body._attributes, response)
            if is_binary(self.request.headers):
                eventheader, eventbody = to_binary(event)
            elif is_structured(self.request.headers):
                eventheader, eventbody = to_structured(event)
            for k, v in eventheader.items():
                if k != "ce-time":
                    self.set_header(k, v)
                else:  #utc now() timestamp
                    self.set_header(
                        'ce-time',
                        datetime.utcnow().replace(tzinfo=pytz.utc).strftime(
                            '%Y-%m-%dT%H:%M:%S.%f%z'))

            if isinstance(eventbody, (bytes, bytearray)):
                response = eventbody
            else:
                response = eventbody.data

        self.write(response)
예제 #10
0
def test_binary_request(client):
    # This data defines a binary cloudevent
    attributes = {
        "type": "com.example.sampletype1",
        "source": "https://example.com/event-producer",
    }
    data = {"message": "Hello World!"}

    event = CloudEvent(attributes, data)
    headers, body = to_binary(event)

    r = client.post("/", headers=headers, data=body)
    assert r.status_code == 204
예제 #11
0
파일: test.py 프로젝트: lance/parliament
def test_post_cloud_event(client):
    """
    Test that we can successfully receive a CloudEvent
    """
    attributes = {
        "type": "com.test.parliament",
        "source": "https://test.com/parliament",
    }
    data = {"message": "Hello World!"}
    event = CloudEvent(attributes, data)
    headers, body = to_binary(event)

    rv = client.post("/", data=body, headers=headers)
    assert rv.data == b"Hello World!"
예제 #12
0
 async def test_predict_ce_bytes_bad_hex_format_exception(
         self, http_server_client):
     event = dummy_cloud_event(b'0\x80\x80\x06World!\x00\x00',
                               set_contenttype=True)
     headers, body = to_binary(event)
     with pytest.raises(
             HTTPClientError,
             match=r".*HTTP 400: Unrecognized request format: "
             r"'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte.*"
     ):
         await http_server_client.fetch('/v1/models/TestModel:predict',
                                        method="POST",
                                        headers=headers,
                                        body=body)
예제 #13
0
def send_binary_cloud_event(url):
    # This data defines a binary cloudevent
    attributes = {
        "type": "com.example.sampletype1",
        "source": "https://example.com/event-producer",
    }
    data = {"message": "Hello World!"}

    event = CloudEvent(attributes, data)
    headers, body = to_binary(event)

    # send and print event
    requests.post(url, headers=headers, data=body)
    print(f"Sent {event['id']} from {event['source']} with {event.data}")
예제 #14
0
def test_uppercase_headers_with_none_data_binary():
    headers = {
        "Ce-Id": "my-id",
        "Ce-Source": "<event-source>",
        "Ce-Type": "cloudevent.event.type",
        "Ce-Specversion": "1.0",
    }
    event = from_http(headers, None)

    for key in headers:
        assert event[key.lower()[3:]] == headers[key]
    assert event.data == None

    _, new_data = to_binary(event)
    assert new_data == None
예제 #15
0
def send_binary_cloud_event(url: str):
    # Create cloudevent
    attributes = {
        "type": "com.example.string",
        "source": "https://example.com/event-producer",
    }

    event = CloudEvent(attributes, image_bytes)

    # Create cloudevent HTTP headers and content
    headers, body = to_binary(event)

    # Send cloudevent
    requests.post(url, headers=headers, data=body)
    print(f"Sent {event['id']} of type {event['type']}")
예제 #16
0
    async def test_predict_ce_bytes_bad_format_exception(
            self, http_server_client):
        event = dummy_cloud_event(b'{', set_contenttype=True)
        headers, body = to_binary(event)

        with pytest.raises(HTTPClientError) as err:
            _ = await http_server_client.fetch('/v1/models/TestModel:predict',
                                               method="POST",
                                               headers=headers,
                                               body=body)
        assert err.value.code == 400
        error_regex = re.compile(
            "Failed to decode or parse binary json cloudevent: "
            "Expecting property name enclosed in double quotes.*")
        response = json.loads(err.value.response.body)
        assert error_regex.match(response["error"]) is not None
예제 #17
0
def test_server_binary(client):
    # Create cloudevent
    attributes = {
        "type": "com.example.string",
        "source": "https://example.com/event-producer",
    }

    event = CloudEvent(attributes, image_bytes)

    # Create cloudevent HTTP headers and content
    headers, body = to_binary(event)

    # Send cloudevent
    r = client.post("/", headers=headers, data=body)
    assert r.status_code == 200
    assert r.data.decode() == f"Found image of size {image_expected_shape}"
예제 #18
0
def test_binary_to_request(specversion):
    attributes = {
        "specversion": specversion,
        "type": "word.found.name",
        "id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
        "source": "pytest",
    }
    data = {"message": "Hello World!"}
    event = CloudEvent(attributes, data)
    headers, body_bytes = to_binary(event)
    body = json.loads(body_bytes)

    for key in data:
        assert body[key] == data[key]
    for key in attributes:
        assert attributes[key] == headers["ce-" + key]
예제 #19
0
    async def post(self, name: str):
        if has_binary_headers(self.request.headers):
            try:
                # Use default unmarshaller if contenttype is set in header
                if "ce-contenttype" in self.request.headers:
                    body = from_http(self.request.headers, self.request.body)
                else:
                    body = from_http(self.request.headers, self.request.body,
                                     lambda x: x)
            except (ce.MissingRequiredFields, ce.InvalidRequiredFields,
                    ce.InvalidStructuredJSON, ce.InvalidHeadersFormat,
                    ce.DataMarshallerError, ce.DataUnmarshallerError) as e:
                raise tornado.web.HTTPError(
                    status_code=HTTPStatus.BAD_REQUEST,
                    reason="Cloud Event Exceptions: %s" % e)
        else:
            try:
                body = json.loads(self.request.body)
            except json.decoder.JSONDecodeError as e:
                raise tornado.web.HTTPError(
                    status_code=HTTPStatus.BAD_REQUEST,
                    reason="Unrecognized request format: %s" % e)
        # call model locally or remote model workers
        model = self.get_model(name)
        if not isinstance(model, RayServeHandle):
            response = await model(body)
        else:
            model_handle = model
            response = await model_handle.remote(body)
        # process response from the model
        if has_binary_headers(self.request.headers):
            event = CloudEvent(body._attributes, response)
            if is_binary(self.request.headers):
                eventheader, eventbody = to_binary(event)
            elif is_structured(self.request.headers):
                eventheader, eventbody = to_structured(event)
            for k, v in eventheader.items():
                if k != "ce-time":
                    self.set_header(k, v)
                else:  # utc now() timestamp
                    self.set_header(
                        'ce-time',
                        datetime.utcnow().replace(tzinfo=pytz.utc).strftime(
                            '%Y-%m-%dT%H:%M:%S.%f%z'))
            response = eventbody

        self.write(response)
예제 #20
0
    async def test_predict_ce_binary_bytes(self, http_server_client):
        event = dummy_cloud_event(b'{"instances":[[1,2]]}',
                                  set_contenttype=True)
        headers, body = to_binary(event)
        resp = await http_server_client.fetch('/v1/models/TestModel:predict',
                                              method="POST",
                                              headers=headers,
                                              body=body)

        assert resp.code == 200
        assert resp.headers['content-type'] == "application/json"
        assert resp.headers['ce-specversion'] == "1.0"
        assert resp.headers["ce-id"] != "36077800-0c23-4f38-a0b4-01f4369f670a"
        assert resp.headers['ce-source'] == "io.kserve.kfserver.TestModel"
        assert resp.headers['ce-type'] == "io.kserve.inference.response"
        assert resp.headers['ce-time'] > "2021-01-28T21:04:43.144141+00:00"
        assert resp.body == b'{"predictions": [[1, 2]]}'
예제 #21
0
    async def test_predict_ce_bytes_bad_hex_format_exception(
            self, http_server_client):
        event = dummy_cloud_event(b'0\x80\x80\x06World!\x00\x00',
                                  set_contenttype=True)
        headers, body = to_binary(event)

        with pytest.raises(HTTPClientError) as err:
            _ = await http_server_client.fetch('/v1/models/TestModel:predict',
                                               method="POST",
                                               headers=headers,
                                               body=body)
        assert err.value.code == 400
        error_regex = re.compile(
            "Failed to decode or parse binary json cloudevent: "
            "'utf-8' codec can't decode byte 0x80 in position 1: invalid start byte.*"
        )
        response = json.loads(err.value.response.body)
        assert error_regex.match(response["error"]) is not None
예제 #22
0
def test_known_empty_edge_cases(binary_headers, test_data):
    expect_data = test_data
    if test_data in ["", b""]:
        expect_data = None
    elif test_data == ():
        # json.dumps(()) outputs '[]' hence list not tuple check
        expect_data = []

    # Remove ce- prefix
    headers = {key[3:]: value for key, value in binary_headers.items()}

    # binary
    event = from_http(*to_binary(CloudEvent(headers, test_data)))
    assert event.data == expect_data

    # structured
    event = from_http(*to_structured(CloudEvent(headers, test_data)))
    assert event.data == expect_data
예제 #23
0
    async def test_predict_ce_binary_bytes(self, http_server_client):
        event = dummy_cloud_event(b'{"instances":[[1,2]]}',
                                  set_contenttype=True)
        headers, body = to_binary(event)
        resp = await http_server_client.fetch('/v1/models/TestModel:predict',
                                              method="POST",
                                              headers=headers,
                                              body=body)

        assert resp.code == 200
        assert resp.body == b'{"predictions": [[1, 2]]}'
        assert resp.headers[
            'content-type'] == "application/x-www-form-urlencoded"
        assert resp.headers['ce-specversion'] == "1.0"
        assert resp.headers['ce-id'] == "36077800-0c23-4f38-a0b4-01f4369f670a"
        assert resp.headers[
            'ce-source'] == "https://example.com/event-producer"
        assert resp.headers['ce-type'] == "com.example.sampletype1"
        assert resp.headers[
            'ce-datacontenttype'] == "application/x-www-form-urlencoded"
        assert resp.headers['ce-time'] > "2021-01-28T21:04:43.144141+00:00"
예제 #24
0
def test_roundtrip_non_json_event(converter, specversion):
    input_data = io.BytesIO()
    for _ in range(100):
        for j in range(20):
            assert 1 == input_data.write(j.to_bytes(1, byteorder="big"))
    compressed_data = bz2.compress(input_data.getvalue())
    attrs = {"source": "test", "type": "t"}

    event = CloudEvent(attrs, compressed_data)

    if converter == converters.TypeStructured:
        headers, data = to_structured(event, data_marshaller=lambda x: x)
    elif converter == converters.TypeBinary:
        headers, data = to_binary(event, data_marshaller=lambda x: x)

    headers["binary-payload"] = "true"  # Decoding hint for server
    _, r = app.test_client.post("/event", headers=headers, data=data)

    assert r.status_code == 200
    for key in attrs:
        assert r.headers[key] == attrs[key]
    assert compressed_data == r.body, r.body
예제 #25
0
def test_wrong_specversion_to_request():
    event = CloudEvent({"source": "s", "type": "t"}, None)
    with pytest.raises(cloud_exceptions.InvalidRequiredFields) as e:
        event["specversion"] = "0.2"
        to_binary(event)
    assert "Unsupported specversion: 0.2" in str(e.value)
예제 #26
0
def test_to_binary_extensions(specversion):
    event = CloudEvent(test_attributes, test_data)
    headers, body = to_binary(event)

    assert "ce-ext1" in headers
    assert headers.get("ce-ext1") == test_attributes["ext1"]
예제 #27
0
def test_binary_event_0_3(client, cloudevent_0_3):
    headers, data = to_binary(cloudevent_0_3)
    resp = client.post("/", headers=headers, data=data)

    assert resp.status_code == 200
    assert resp.data == b"OK"
def test_to_binary_http_deprecated(event):
    with pytest.deprecated_call():
        assert to_binary(event) == to_binary_http(event)