예제 #1
0
    def test_spectre_404s_when_destination_is_missing(self):
        response = get_through_spectre('/not_cacheable')
        assert response.status_code == 200

        response = get_through_spectre('/not_cacheable', extra_headers={'X-Smartstack-Destination': None})
        assert response.status_code == 404
        assert 'Not found: GET /not_cacheable' in response.text
예제 #2
0
    def test_default_vary_headers(self):
        assert get_through_spectre('/long_ttl/vary').headers['Spectre-Cache-Status'] == 'miss'
        assert assert_is_in_spectre_cache('/long_ttl/vary')

        # namespace vary_headers do not contain X-Mode
        assert assert_is_in_spectre_cache('/long_ttl/vary', {'x-mode': 'ro'})
        assert get_through_spectre('/long_ttl/vary', {'accept-encoding': 'text'}).headers['Spectre-Cache-Status'] == 'miss'
예제 #3
0
    def test_purge_cassandra_by_id(self):
        get_through_spectre('/bulk_requester?ids=1')
        assert_is_in_spectre_cache('/bulk_requester?ids=1')

        # Purge id 1
        purge_resource({'namespace': 'backend.main', 'cache_name': 'bulk_requester_does_not_cache_missing_ids', 'id': '1'})
        get_resp_3 = get_through_spectre('/bulk_requester?ids=1')
        # resp 3 was no longer cached.
        assert get_resp_3.headers['Spectre-Cache-Status'] == 'miss'
예제 #4
0
    def test_response_headers_passed_back(self):
        response = get_through_spectre('/not_cacheable')
        assert response.headers['Some-Header'] == 'abc'
        assert response.headers['Spectre-Cache-Status'] == 'non-cacheable-uri (backend.main)'

        response = get_through_spectre('/timestamp/response_header')
        assert response.headers['Spectre-Cache-Status'] == 'miss'
        assert response.headers['Some-Header'] == 'abc'

        response = assert_is_in_spectre_cache('/timestamp/response_header')
        assert response.headers['Some-Header'] == 'abc'
예제 #5
0
    def test_different_params_same_id(self):
        base_path = '/bulk_requester'

        resp1 = get_through_spectre("{}?ids={}&data=false".format(base_path, '5%2C6'))
        assert resp1.headers['Spectre-Cache-Status'] == 'miss'
        resp2 = get_through_spectre("{}?ids={}&data=true".format(base_path, '5'))
        assert resp2.headers['Spectre-Cache-Status'] == 'miss'

        assert_is_in_spectre_cache("{}?data=false&ids={}".format(base_path, '5'))
        assert_is_in_spectre_cache("{}?data=false&ids={}".format(base_path, '6'))
        assert_is_in_spectre_cache("{}?ids={}&data=false".format(base_path, '6'))
예제 #6
0
    def test_non_application_json(self):
        extra_header = {'test-content-type': 'text'}
        base_path = '/bulk_requester'
        resp = get_through_spectre("{}?ids={}".format(base_path, '4%2C5'), extra_headers=extra_header)
        assert resp.text == 'this is text'
        assert resp.status_code == 200
        assert resp.headers['Spectre-Cache-Status'] == "unable to process response; content-type is text"

        # Single id isn't cached either
        resp = get_through_spectre("{}?ids={}".format(base_path, '4'), extra_headers=extra_header)
        assert resp.text == 'this is text'
        assert resp.status_code == 200
        assert resp.headers['Spectre-Cache-Status'] == "unable to process response; content-type is text"
예제 #7
0
    def test_spectre_status_response_header(self):
        response = get_through_spectre('/not_cacheable')
        assert response.headers['Spectre-Cache-Status'] == 'non-cacheable-uri (backend.main)'

        response = get_through_spectre('/timestamp/spectre_status_header')
        assert response.headers['Spectre-Cache-Status'] == 'miss'

        assert_is_in_spectre_cache('/timestamp/spectre_status_header')

        response = get_through_spectre('/timestamp/spectre_status_header', extra_headers={'Pragma': 'no-cache'})
        assert response.headers['Spectre-Cache-Status'] == 'no-cache-header'

        response = post_through_spectre('/timestamp/spectre_status_header')
        assert response.headers['Spectre-Cache-Status'] == 'non-cacheable-method'
예제 #8
0
    def test_caching_json_with_null(self):
        response1 = get_through_spectre('/timestamp/cached')
        time.sleep(0.2)
        response2 = get_through_spectre('/timestamp/cached')

        body1 = response1.json()
        body2 = response2.json()

        # keys with null values should be persisted
        assert 'null_value' in body1
        assert 'null_value' in body2

        assert body1['null_value'] is None
        assert body2['null_value'] is None
예제 #9
0
    def test_caching_json_with_null(self):
        base_path = '/bulk_requester_2'
        resp_1 = get_through_spectre("{}/{}/v1?k1=v2".format(base_path, '1,2,3'))
        resp_2 = get_through_spectre("{}/{}/v1?k1=v2".format(base_path, '2'))
        _, bulk_body = resp_1.headers, resp_1.json()
        _, body = resp_2.headers, resp_2.json()

        # keys with null values should be persisted
        assert 'null_value' in bulk_body[0]
        assert 'null_value' in bulk_body[1]
        assert 'null_value' in bulk_body[2]
        assert 'null_value' in body[0]

        assert bulk_body[1]['null_value'] is None
        assert body[0]['null_value'] is None
예제 #10
0
    def test_uncacheable_headers_not_passed_back(self):
        """Spectre doesn't store uncacheable headers in the cache, but it always
        passes back all response headers in the uncached (or uncacheable) case.
        """
        response = get_through_spectre('/not_cacheable')
        assert response.headers['Spectre-Cache-Status'] == 'non-cacheable-uri (backend.main)'
        assert 'Uncacheable-Header' in response.headers

        response = get_through_spectre('/timestamp/uncacheable_header')
        assert response.headers['Spectre-Cache-Status'] == 'miss'
        assert 'Uncacheable-Header' in response.headers

        # This is the cached case, where the uncacheable header isn't expected
        # in the response.
        response = assert_is_in_spectre_cache('/timestamp/uncacheable_header')
        assert 'Uncacheable-Header' not in response.headers
예제 #11
0
    def test_different_vary_headers(self):
        # same vary header
        val1 = get_through_spectre('/timestamp/cached', {'accept-encoding': 'testzip, deflate, custom'})
        assert val1.headers['Spectre-Cache-Status'] == 'miss'
        assert_is_in_spectre_cache('/timestamp/cached', {'accept-encoding': 'testzip, deflate, custom'})

        # different vary headers --> val3 is a miss
        val3 = get_through_spectre('/timestamp/cached', {'accept-encoding': 'none'})
        assert val3.headers['Spectre-Cache-Status'] == 'miss'

        # contains x-mode --> miss
        val4 = get_through_spectre('/timestamp/cached', {'X-Mode': 'ro'})
        assert val4.headers['Spectre-Cache-Status'] == 'miss'

        # Host is not a Vary header --> hit
        assert_is_in_spectre_cache('/timestamp/cached', {'X-Mode': 'ro', 'Host': 'localhost'})
예제 #12
0
def test_no_cache_header_metrics(log_file):
    response = get_through_spectre(
        '/timestamp/',
        extra_headers={'Pragma': 'spectre-no-cache'},
    )
    assert response.status_code == 200

    metrics = _load_metrics(log_file)

    # Since we send the no-cache header we don't have a `spectre.fetch_body_and_headers`
    # or `spectre.hit_rate`. We still update the cache though, so we have the
    # `spectre.store_body_and_headers`
    _assert_store_metric(metrics[0], 'timestamp')

    # Finally we emit the `spectre.no_cache_header`
    assert metrics[1].dimensions == {
        'metric_name': 'spectre.no_cache_header',
        'habitat': 'uswest1a',
        'service_name': 'spectre',
        'namespace': 'backend.main',
        'instance_name': 'itest',
        'reason': 'no-cache-header',
        'cache_name': 'timestamp',
    }

    # This assert is mostly there to make sure there are no more metrics than what I expect.
    # The reason why it's not before the other asserts is because the error message doesn't
    # show you what metrics are actually in the list, so it's very annoying to figure out
    # what's missing. You'd need to comment out this check and then verify which of the
    # other asserts is failing.
    assert len(metrics) == 2
예제 #13
0
def test_cache_miss(log_file):

    response = get_through_spectre(
        '/timestamp/',
    )
    assert response.status_code == 200
    assert response.headers['Spectre-Cache-Status'] == 'miss'

    metrics = _load_metrics(log_file)

    # First 2 metrics are `spectre.fetch_body_and_headers` and `spectre.hit_rate`
    _assert_fetch_hit_rate(metrics[0:2], 'timestamp')

    # Then since it's a miss we have a `spectre.store_body_and_headers`
    _assert_store_metric(metrics[2], 'timestamp')

    # Finally the `spectre.request_timing`
    _assert_request_timing_metrics(metrics[3:7], 'timestamp')

    # This assert is mostly there to make sure there are no more metrics than what I expect.
    # The reason why it's not before the other asserts is because the error message doesn't
    # show you what metrics are actually in the list, so it's very annoying to figure out
    # what's missing. You'd need to comment out this check and then verify which of the
    # other asserts is failing.
    assert len(metrics) == 7
예제 #14
0
    def test_dont_emit_span_if_no_headers(self, clean_log_files):
        response = get_through_spectre(
            '/not_cacheable',
        )
        assert response.status_code == 200

        self._assert_no_span_in_logs()
예제 #15
0
 def test_hop_by_hop_headers(self):
     response = get_through_spectre('/timestamp/get')
     assert response.status_code == 200
     # Our test backend sends 'Connection: close'. Connection being a
     # hop-by-hop header, spectre should not forward it and we expect to see
     # openresty's "keep-alive" instead.
     # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
     assert response.headers['Connection'] == 'keep-alive'
예제 #16
0
    def test_caching_works_with_id_extraction(self):
        response = get_through_spectre('/biz?foo=bar&business_id=1234')
        assert response.headers['Spectre-Cache-Status'] == 'miss'

        # ensure extracting the id is not messing up the caching logic
        assert_is_in_spectre_cache('/biz?foo=bar&business_id=1234')

        # check that invalidation is actually supported
        purge_resource({
            'namespace': 'backend.main',
            'cache_name': 'url_with_id_extraction',
            'id': '1234',
        })

        # now this should be a cache miss
        response = get_through_spectre('/biz?foo=bar&business_id=1234')
        assert response.headers['Spectre-Cache-Status'] == 'miss'
예제 #17
0
    def test_http_timeouts_return_504(self):
        start = time.time()
        response = get_through_spectre('/timestamp/cached?sleep=1500')
        duration = time.time() - start

        assert response.text == 'Error requesting /timestamp/cached?sleep=1500: timeout'
        assert response.status_code == 504
        # Duration should be >= 1.0 seconds since that's the HTTP_TIMEOUT_MS
        # value we set in start.sh
        assert duration >= 1.0
예제 #18
0
    def test_with_invalid_id_when_cache_missing_ids_is_true(self):
        path = '/bulk_requester_2/{ids}/v1?k1=v1'

        # 0 < ids < 1000 are valid
        response = get_through_spectre(path.format(ids='10,5000,11'))
        assert len(response.json()) == 2
        assert response.headers['Spectre-Cache-Status'] == 'miss'

        # "5000" is an invalid id; but by default cache_missing_ids is set to true, so the
        # empty response would've been cached from the above request
        hit_response = assert_is_in_spectre_cache(path.format(ids='5000'))
        assert hit_response.json() == []
예제 #19
0
    def test_dont_drop_underscored_headers(self):
        response = get_through_spectre(
            '/business?foo=bar&business_id=1234',
            extra_headers={
                'Test-Header': 'val1',
                'Header_with_underscores': 'val2',
            },
        )

        headers = response.json()['received_headers']
        assert 'test-header' in headers
        assert 'header_with_underscores' in headers
예제 #20
0
    def test_with_invalid_id_when_cache_missing_ids_is_true(self):
        base_path = '/bulk_requester_2'

        # 0 < ids < 1000 are valid
        resp1 = get_through_spectre("{}/{}/v1?k1=v2".format(base_path, '10,5000,11'))
        headers1, body1 = resp1.headers, resp1.json()
        assert len(body1) == 2
        assert headers1['Spectre-Cache-Status'] == 'miss'

        # "5000" is an invalid id; but by default cache_missing_ids is set to true, so the
        # empty response would've been cached from the above request
        resp2 = assert_is_in_spectre_cache("{}/{}/v1?k1=v2".format(base_path, '5000'))
        resp2.json() == []
예제 #21
0
    def test_purge_cassandra_by_id_backward_compatible(self):
        # Delete after PERF-2453 is done
        get_through_spectre('/bulk_requester?ids=1')
        assert_is_in_spectre_cache('/bulk_requester?ids=1')

        # Purge id 1
        res = requests.request(
            'PURGE',
            util.SPECTRE_BASE_URL +
            '?cache_name=bulk_requester_does_not_cache_missing_ids&id=1',
            headers=util.HAPROXY_ADDED_HEADERS)
        assert res.status_code == 200

        get_resp_3 = get_through_spectre('/bulk_requester?ids=1')
        # resp 3 was no longer cached.
        assert get_resp_3.headers['Spectre-Cache-Status'] == 'miss'

        res = requests.request(
            'PURGE',
            util.SPECTRE_BASE_URL +
            '?cache_name=bulk_requester_does_not_cache_missing_ids',
            headers=util.HAPROXY_ADDED_HEADERS)
        assert res.status_code == 200
예제 #22
0
    def test_bulk_ids_in_path(self):
        base_path = '/bulk_requester_2'

        resp_1 = get_through_spectre("{}/{}/v1?k1=v2".format(base_path, '1,2,3'))
        # Bulk was a miss
        bulk_headers, bulk_body = resp_1.headers, resp_1.json()
        assert bulk_headers['Spectre-Cache-Status'] == 'miss'

        resp_2 = assert_is_in_spectre_cache("{}/{}/v1?k1=v2".format(base_path, '2'))
        _, body = resp_2.headers, resp_2.json()

        # Check for correctness
        assert len(body) == 1
        assert bulk_body[1] == body[0]
예제 #23
0
    def test_cassandra_timeouts_dont_slow_down_spectre(self):
        # Run the test twice to make sure the slow write from the first request
        # doesn't affect the next request.
        for i in range(2):
            start = time.time()
            response = get_through_spectre('/timestamp/get')
            duration = time.time() - start

            # Cassandra timeouts are treated as a miss.
            assert response.headers['Spectre-Cache-Status'] == 'miss'

            # The timeouts are set at 500ms in itests.
            # Duration should be very close to that value. If it's much higher it
            # means the timeouts are not working, if it's lower it means the
            # iptable rule is not working.
            assert duration - 0.5 < 0.01
예제 #24
0
    def make_request(self, ids, cache=True, extra_headers=None, assert_hit=False):
        base_path = '/bulk_requester'
        ids = [str(i) for i in ids]
        ids = "%2C".join(ids)
        if not cache:
            cache_headers = {'Cache-Control': 'no-cache'}
            if extra_headers is None:
                extra_headers = {}
            extra_headers.update(cache_headers)

        if assert_hit:
            resp = assert_is_in_spectre_cache("{}?ids={}".format(base_path, ids), extra_headers)
        else:
            resp = get_through_spectre("{}?ids={}".format(base_path, ids), extra_headers)

        return resp.headers, resp.json()
예제 #25
0
    def test_can_skip_cassandra_check(self):
        response = get_through_spectre('/status')
        assert response.status_code == 200
        assert response.text == 'Backend is alive\n'

        response = get_from_spectre('/status')
        assert response.status_code == 200
        status = json.loads(response.text)
        assert status['cassandra_status'] == 'skipped'
        assert status['smartstack_configs'] == 'present'
        assert status['spectre_configs'] == 'present'
        assert status['proxied_services'] == {
            'backend.main': {
                'host': '10.5.0.3',
                'port': 9080,
            },
        }
예제 #26
0
 def _call_with_zipkin(
     self,
     trace_id,
     span_id,
     parent_span_id,
     sampled='1',
     url='/not_cacheable',
 ):
     """Calls Spectre-fronted service with specified Zipkin HTTP headers."""
     zipkin_headers = {
         'X-B3-TraceId': trace_id,
         'X-B3-SpanId': span_id,
         'X-B3-ParentSpanId': parent_span_id,
         'X-B3-Flags': '0',
         'X-B3-Sampled': sampled,
     }
     response = get_through_spectre(
         url,
         extra_headers=zipkin_headers,
     )
     return response
예제 #27
0
def test_bulk_endpoint_miss(log_file):
    response = get_through_spectre(
        '/bulk_requester_2/10,11/v1?foo=bar',
    )
    assert response.status_code == 200
    assert response.headers['Spectre-Cache-Status'] == 'miss'

    metrics = _load_metrics(log_file)

    # We have `spectre.fetch_body_and_headers` and `spectre.hit_rate` twice
    # since we have 2 ids in the url.
    _assert_fetch_hit_rate(metrics[0:2], 'bulk_requester_default')
    _assert_fetch_hit_rate(metrics[2:4], 'bulk_requester_default')

    # Then we have 2 `spectre.store_body_and_headers`
    _assert_store_metric(metrics[4], 'bulk_requester_default')
    _assert_store_metric(metrics[5], 'bulk_requester_default')

    # Then the `spectre.request_timing`
    _assert_request_timing_metrics(metrics[6:10], 'bulk_requester_default')

    # Finally we have `spectre.bulk_hit_rate`
    assert metrics[10].dimensions == {
        'metric_name': 'spectre.bulk_hit_rate',
        'habitat': 'uswest1a',
        'service_name': 'spectre',
        'namespace': 'backend.main',
        'instance_name': 'itest',
        'cache_name': 'bulk_requester_default',
        'cache_status': 'miss',
    }

    # This assert is mostly there to make sure there are no more metrics than what I expect.
    # The reason why it's not before the other asserts is because the error message doesn't
    # show you what metrics are actually in the list, so it's very annoying to figure out
    # what's missing. You'd need to comment out this check and then verify which of the
    # other asserts is failing.
    assert len(metrics) == 11
예제 #28
0
    def test_bulk_request_error_codes(self):
        resp = get_through_spectre('/bulk_requester_2/1/v1?error_status=502')

        assert resp.status_code == 502
        assert resp.headers[
            'Spectre-Cache-Status'] == 'non-cacheable-response: status code is 502'
예제 #29
0
    def test_doesnt_validate_json_on_non_200(self):
        resp = get_through_spectre("/not_authorized?ids=1")

        assert resp.status_code == 403
        assert resp.headers[
            'Spectre-Cache-Status'] == 'non-cacheable-response: status code is 403'
예제 #30
0
 def test_bulk_endpoint_regex_can_work(self, url, cache_status):
     assert get_through_spectre(
         url).headers['Spectre-Cache-Status'] == cache_status