Revert product checker commits:
023cf5380f: "[7.x] Document 'UnsupportedProductError'"d381491482: "[7.x] Rename product error to 'UnsupportedProductError'"b56fbcb155: "Document the Elasticsearch product check"b63d005138: "Don't swallow unexpected errors during Elasticsearch verification"801a839093: "Verify we're connected to Elasticsearch before requests" Signed-off-by: Shephali Mittal <[email protected]>
This commit is contained in:
@@ -28,7 +28,7 @@ import pytest
|
||||
from mock import patch
|
||||
from multidict import CIMultiDict
|
||||
|
||||
from elasticsearch import AIOHttpConnection, AsyncElasticsearch, __versionstr__
|
||||
from elasticsearch import AIOHttpConnection, __versionstr__
|
||||
from elasticsearch.compat import reraise_exceptions
|
||||
from elasticsearch.exceptions import ConnectionError
|
||||
|
||||
@@ -410,9 +410,3 @@ class TestConnectionHttpbin:
|
||||
conn = AIOHttpConnection("not.a.host.name")
|
||||
with pytest.raises(ConnectionError):
|
||||
await conn.perform_request("GET", "/")
|
||||
|
||||
async def test_elasticsearch_connection_error(self):
|
||||
es = AsyncElasticsearch("http://not.a.host.name")
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
await es.search()
|
||||
|
||||
@@ -28,16 +28,7 @@ from mock import patch
|
||||
from elasticsearch import AsyncTransport
|
||||
from elasticsearch.connection import Connection
|
||||
from elasticsearch.connection_pool import DummyConnectionPool
|
||||
from elasticsearch.exceptions import (
|
||||
AuthenticationException,
|
||||
AuthorizationException,
|
||||
ConnectionError,
|
||||
ElasticsearchWarning,
|
||||
NotFoundError,
|
||||
TransportError,
|
||||
UnsupportedProductError,
|
||||
)
|
||||
from elasticsearch.transport import _ProductChecker
|
||||
from elasticsearch.exceptions import ConnectionError, TransportError
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
@@ -130,7 +121,6 @@ class TestTransport:
|
||||
|
||||
async def test_request_timeout_extracted_from_params_and_passed(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection, meta_header=False)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", params={"request_timeout": 42})
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -145,7 +135,6 @@ class TestTransport:
|
||||
t = AsyncTransport(
|
||||
[{}], opaque_id="app-1", connection_class=DummyConnection, meta_header=False
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/")
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -168,7 +157,6 @@ class TestTransport:
|
||||
|
||||
async def test_request_with_custom_user_agent_header(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection, meta_header=False)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request(
|
||||
"GET", "/", headers={"user-agent": "my-custom-value/1.2.3"}
|
||||
@@ -184,7 +172,6 @@ class TestTransport:
|
||||
t = AsyncTransport(
|
||||
[{}], send_get_body_as="source", connection_class=DummyConnection
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body={})
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -194,7 +181,6 @@ class TestTransport:
|
||||
t = AsyncTransport(
|
||||
[{}], send_get_body_as="POST", connection_class=DummyConnection
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body={})
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -202,7 +188,6 @@ class TestTransport:
|
||||
|
||||
async def test_client_meta_header(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body={})
|
||||
assert len(t.get_connection().calls) == 1
|
||||
@@ -216,7 +201,6 @@ class TestTransport:
|
||||
HTTP_CLIENT_META = ("dm", "1.2.3")
|
||||
|
||||
t = AsyncTransport([{}], connection_class=DummyConnectionWithMeta)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body={}, headers={"Custom": "header"})
|
||||
assert len(t.get_connection().calls) == 1
|
||||
@@ -229,7 +213,6 @@ class TestTransport:
|
||||
|
||||
async def test_client_meta_header_not_sent(self):
|
||||
t = AsyncTransport([{}], meta_header=False, connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body={})
|
||||
assert len(t.get_connection().calls) == 1
|
||||
@@ -238,7 +221,6 @@ class TestTransport:
|
||||
|
||||
async def test_body_gets_encoded_into_bytes(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body="你好")
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -251,7 +233,6 @@ class TestTransport:
|
||||
|
||||
async def test_body_bytes_get_passed_untouched(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
body = b"\xe4\xbd\xa0\xe5\xa5\xbd"
|
||||
await t.perform_request("GET", "/", body=body)
|
||||
@@ -260,7 +241,6 @@ class TestTransport:
|
||||
|
||||
async def test_body_surrogates_replaced_encoded_into_bytes(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t.perform_request("GET", "/", body="你好\uda6a")
|
||||
assert 1 == len(t.get_connection().calls)
|
||||
@@ -273,8 +253,6 @@ class TestTransport:
|
||||
|
||||
async def test_kwargs_passed_on_to_connections(self):
|
||||
t = AsyncTransport([{"host": "google.com"}], port=123)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t._async_call()
|
||||
assert 1 == len(t.connection_pool.connections)
|
||||
assert "http://google.com:123" == t.connection_pool.connections[0].host
|
||||
@@ -282,8 +260,6 @@ class TestTransport:
|
||||
async def test_kwargs_passed_on_to_connection_pool(self):
|
||||
dt = object()
|
||||
t = AsyncTransport([{}, {}], dead_timeout=dt)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t._async_call()
|
||||
assert dt is t.connection_pool.dead_timeout
|
||||
|
||||
@@ -293,15 +269,12 @@ class TestTransport:
|
||||
self.kwargs = kwargs
|
||||
|
||||
t = AsyncTransport([{}], connection_class=MyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t._async_call()
|
||||
assert 1 == len(t.connection_pool.connections)
|
||||
assert isinstance(t.connection_pool.connections[0], MyConnection)
|
||||
|
||||
def test_add_connection(self):
|
||||
t = AsyncTransport([{}], randomize_hosts=False)
|
||||
t._verified_elasticsearch = True
|
||||
t.add_connection({"host": "google.com", "port": 1234})
|
||||
|
||||
assert 2 == len(t.connection_pool.connections)
|
||||
@@ -312,7 +285,6 @@ class TestTransport:
|
||||
[{"exception": ConnectionError("abandon ship")}],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
connection_error = False
|
||||
try:
|
||||
@@ -328,7 +300,6 @@ class TestTransport:
|
||||
[{"exception": ConnectionError("abandon ship")}] * 2,
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
connection_error = False
|
||||
try:
|
||||
@@ -342,8 +313,6 @@ class TestTransport:
|
||||
async def test_resurrected_connection_will_be_marked_as_live_on_success(self):
|
||||
for method in ("GET", "HEAD"):
|
||||
t = AsyncTransport([{}, {}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t._async_call()
|
||||
con1 = t.connection_pool.get_connection()
|
||||
con2 = t.connection_pool.get_connection()
|
||||
@@ -369,7 +338,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniff_on_start=True,
|
||||
)
|
||||
|
||||
await t._async_call()
|
||||
await t.sniffing_task # Need to wait for the sniffing task to complete
|
||||
|
||||
@@ -383,7 +351,6 @@ class TestTransport:
|
||||
sniff_on_start=True,
|
||||
sniff_timeout=12,
|
||||
)
|
||||
|
||||
await t._async_call()
|
||||
await t.sniffing_task # Need to wait for the sniffing task to complete
|
||||
|
||||
@@ -426,8 +393,6 @@ class TestTransport:
|
||||
max_retries=0,
|
||||
randomize_hosts=False,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
await t._async_call()
|
||||
|
||||
connection_error = False
|
||||
@@ -452,7 +417,6 @@ class TestTransport:
|
||||
max_retries=3,
|
||||
randomize_hosts=False,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_init()
|
||||
|
||||
conn_err, conn_data = t.connection_pool.connections
|
||||
@@ -468,7 +432,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniffer_timeout=5,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_call()
|
||||
|
||||
for _ in range(4):
|
||||
@@ -492,9 +455,7 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniff_timeout=42,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_call()
|
||||
|
||||
await t.sniff_hosts()
|
||||
# Ensure we parsed out the fqdn and port from the fqdn/ip:port string.
|
||||
assert t.connection_pool.connection_opts[0][1] == {
|
||||
@@ -511,7 +472,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
cloud_id="cluster:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyQ0ZmE4ODIxZTc1NjM0MDMyYmVkMWNmMjIxMTBlMmY5NyQ0ZmE4ODIxZTc1NjM0MDMyYmVkMWNmMjIxMTBlMmY5Ng==",
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_call()
|
||||
|
||||
assert not t.sniff_on_connection_fail
|
||||
@@ -522,7 +482,6 @@ class TestTransport:
|
||||
|
||||
async def test_transport_close_closes_all_pool_connections(self):
|
||||
t = AsyncTransport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_call()
|
||||
|
||||
assert not any([conn.closed for conn in t.connection_pool.connections])
|
||||
@@ -530,7 +489,6 @@ class TestTransport:
|
||||
assert all([conn.closed for conn in t.connection_pool.connections])
|
||||
|
||||
t = AsyncTransport([{}, {}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
await t._async_call()
|
||||
|
||||
assert not any([conn.closed for conn in t.connection_pool.connections])
|
||||
@@ -547,7 +505,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniff_on_start=True,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
# If our initial sniffing attempt comes back
|
||||
# empty then we raise an error.
|
||||
@@ -565,7 +522,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniff_on_start=True,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
# Start the timer right before the first task
|
||||
# and have a bunch of tasks come in immediately.
|
||||
@@ -600,7 +556,6 @@ class TestTransport:
|
||||
connection_class=DummyConnection,
|
||||
sniff_on_start=True,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
# Start making _async_calls() before we cancel
|
||||
tasks = []
|
||||
@@ -619,286 +574,3 @@ class TestTransport:
|
||||
|
||||
# A lot quicker than 10 seconds defined in 'delay'
|
||||
assert duration < 1
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["headers", "data"],
|
||||
[
|
||||
(
|
||||
{},
|
||||
'{"version":{"number":"6.99.0"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
(
|
||||
{},
|
||||
'{"version":{"number":"7.13.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
(
|
||||
{"X-elastic-product": "Elasticsearch"},
|
||||
'{"version":{"number":"7.14.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_verify_elasticsearch(self, headers, data):
|
||||
t = AsyncTransport(
|
||||
[{"data": data, "headers": headers}], connection_class=DummyConnection
|
||||
)
|
||||
await t.perform_request("GET", "/_search")
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
_ = [call[1]["headers"].pop("x-elastic-client-meta") for call in calls]
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
("GET", "/"),
|
||||
{
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
},
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
(
|
||||
("GET", "/_search", None, None),
|
||||
{
|
||||
"headers": {},
|
||||
"ignore": (),
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception_cls", [AuthorizationException, AuthenticationException]
|
||||
)
|
||||
async def test_verify_elasticsearch_skips_on_auth_errors(self, exception_cls):
|
||||
t = AsyncTransport(
|
||||
[{"exception": exception_cls(exception_cls.status_code)}],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
with pytest.warns(ElasticsearchWarning) as warns:
|
||||
with pytest.raises(exception_cls):
|
||||
await t.perform_request(
|
||||
"GET", "/_search", headers={"Authorization": "testme"}
|
||||
)
|
||||
|
||||
# Assert that a warning was raised due to security privileges
|
||||
assert [str(w.message) for w in warns] == [
|
||||
"The client is unable to verify that the server is "
|
||||
"Elasticsearch due security privileges on the server side"
|
||||
]
|
||||
|
||||
# Assert that the cluster is "verified"
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
# See that the headers were passed along to the "info" request made
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
_ = [call[1]["headers"].pop("x-elastic-client-meta") for call in calls]
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
("GET", "/"),
|
||||
{
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"authorization": "testme",
|
||||
},
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
(
|
||||
("GET", "/_search", None, None),
|
||||
{
|
||||
"headers": {
|
||||
"Authorization": "testme",
|
||||
},
|
||||
"ignore": (),
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
async def test_multiple_requests_verify_elasticsearch_success(self, event_loop):
|
||||
t = AsyncTransport(
|
||||
[
|
||||
{
|
||||
"data": '{"version":{"number":"7.13.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
"delay": 1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
async def request_task():
|
||||
try:
|
||||
results.append(await t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(event_loop.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
tasks = []
|
||||
start_time = event_loop.time()
|
||||
for _ in range(10):
|
||||
tasks.append(event_loop.create_task(request_task()))
|
||||
await asyncio.gather(*tasks)
|
||||
end_time = event_loop.time()
|
||||
|
||||
# Exactly 10 results completed
|
||||
assert len(results) == 10
|
||||
|
||||
# No errors in the results
|
||||
assert all(isinstance(result, dict) for result in results)
|
||||
|
||||
# Assert that this took longer than 2 seconds but less than 2.1 seconds
|
||||
duration = end_time - start_time
|
||||
assert 2 <= duration <= 2.1
|
||||
|
||||
# Assert that every result came after ~2 seconds, no fast completions.
|
||||
assert all(
|
||||
2 <= completed_time - start_time <= 2.1 for completed_time in completed_at
|
||||
)
|
||||
|
||||
# Assert that the cluster is "verified"
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
# See that the first request is always 'GET /' for ES check
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert calls[0][0] == ("GET", "/")
|
||||
|
||||
# The rest of the requests are 'GET /_search' afterwards
|
||||
assert all(call[0][:2] == ("GET", "/_search") for call in calls[1:])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["build_flavor", "tagline", "product_error", "error_message"],
|
||||
[
|
||||
(
|
||||
"default",
|
||||
"BAD TAGLINE",
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
"The client noticed that the server is not Elasticsearch and we do not support this unknown product",
|
||||
),
|
||||
(
|
||||
"BAD BUILD FLAVOR",
|
||||
"BAD TAGLINE",
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
"The client noticed that the server is not Elasticsearch and we do not support this unknown product",
|
||||
),
|
||||
(
|
||||
"BAD BUILD FLAVOR",
|
||||
"You Know, for Search",
|
||||
_ProductChecker.UNSUPPORTED_DISTRIBUTION,
|
||||
"The client noticed that the server is not a supported distribution of Elasticsearch",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_multiple_requests_verify_elasticsearch_product_error(
|
||||
self, event_loop, build_flavor, tagline, product_error, error_message
|
||||
):
|
||||
t = AsyncTransport(
|
||||
[
|
||||
{
|
||||
"data": '{"version":{"number":"7.13.0","build_flavor":"%s"},"tagline":"%s"}'
|
||||
% (build_flavor, tagline),
|
||||
"delay": 1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
async def request_task():
|
||||
try:
|
||||
results.append(await t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(event_loop.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
tasks = []
|
||||
start_time = event_loop.time()
|
||||
for _ in range(10):
|
||||
tasks.append(event_loop.create_task(request_task()))
|
||||
await asyncio.gather(*tasks)
|
||||
end_time = event_loop.time()
|
||||
|
||||
# Exactly 10 results completed
|
||||
assert len(results) == 10
|
||||
|
||||
# All results were errors
|
||||
assert all(isinstance(result, UnsupportedProductError) for result in results)
|
||||
assert all(str(result) == error_message for result in results)
|
||||
|
||||
# Assert that one request was made but not 2 requests.
|
||||
duration = end_time - start_time
|
||||
assert 1 <= duration <= 1.1
|
||||
|
||||
# Assert that every result came after ~1 seconds, no fast completions.
|
||||
assert all(
|
||||
1 <= completed_time - start_time <= 1.1 for completed_time in completed_at
|
||||
)
|
||||
|
||||
# Assert that the cluster is definitely not Elasticsearch
|
||||
assert t._verified_elasticsearch == product_error
|
||||
|
||||
# See that the first request is always 'GET /' for ES check
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert calls[0][0] == ("GET", "/")
|
||||
|
||||
# The rest of the requests are 'GET /_search' afterwards
|
||||
assert all(call[0][:2] == ("GET", "/_search") for call in calls[1:])
|
||||
|
||||
@pytest.mark.parametrize("error_cls", [ConnectionError, NotFoundError])
|
||||
async def test_multiple_requests_verify_elasticsearch_retry_on_errors(
|
||||
self, event_loop, error_cls
|
||||
):
|
||||
t = AsyncTransport(
|
||||
[
|
||||
{
|
||||
"exception": error_cls(),
|
||||
"delay": 0.1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
async def request_task():
|
||||
try:
|
||||
results.append(await t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(event_loop.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
tasks = []
|
||||
start_time = event_loop.time()
|
||||
for _ in range(5):
|
||||
tasks.append(event_loop.create_task(request_task()))
|
||||
await asyncio.gather(*tasks)
|
||||
end_time = event_loop.time()
|
||||
|
||||
# Exactly 5 results completed
|
||||
assert len(results) == 5
|
||||
|
||||
# All results were errors and not wrapped in 'NotElasticsearchError'
|
||||
assert all(isinstance(result, error_cls) for result in results)
|
||||
|
||||
# Assert that 5 requests were made in total (5 transport requests per x 0.1s/conn request)
|
||||
duration = end_time - start_time
|
||||
assert 0.5 <= duration <= 0.6
|
||||
|
||||
# Assert that the cluster is still in the unknown/unverified stage.
|
||||
assert t._verified_elasticsearch is None
|
||||
|
||||
# See that the API isn't hit, instead it's the index requests that are failing.
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert len(calls) == 5
|
||||
assert all(call[0] == ("GET", "/") for call in calls)
|
||||
|
||||
@@ -31,7 +31,7 @@ from mock import Mock, patch
|
||||
from requests.auth import AuthBase
|
||||
from urllib3._collections import HTTPHeaderDict
|
||||
|
||||
from elasticsearch import Elasticsearch, __versionstr__
|
||||
from elasticsearch import __versionstr__
|
||||
from elasticsearch.compat import reraise_exceptions
|
||||
from elasticsearch.connection import (
|
||||
Connection,
|
||||
@@ -1045,9 +1045,3 @@ class TestConnectionHttpbin:
|
||||
conn = RequestsHttpConnection("not.a.host.name")
|
||||
with pytest.raises(ConnectionError):
|
||||
conn.perform_request("GET", "/")
|
||||
|
||||
def test_elasticsearch_connection_error(self):
|
||||
es = Elasticsearch("http://not.a.host.name")
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
es.search()
|
||||
|
||||
@@ -26,16 +26,8 @@ from mock import patch
|
||||
|
||||
from elasticsearch.connection import Connection
|
||||
from elasticsearch.connection_pool import DummyConnectionPool
|
||||
from elasticsearch.exceptions import (
|
||||
AuthenticationException,
|
||||
AuthorizationException,
|
||||
ConnectionError,
|
||||
ElasticsearchWarning,
|
||||
NotFoundError,
|
||||
TransportError,
|
||||
UnsupportedProductError,
|
||||
)
|
||||
from elasticsearch.transport import Transport, _ProductChecker, get_host_info
|
||||
from elasticsearch.exceptions import ConnectionError, TransportError
|
||||
from elasticsearch.transport import Transport, get_host_info
|
||||
|
||||
from .test_cases import TestCase
|
||||
|
||||
@@ -45,14 +37,11 @@ class DummyConnection(Connection):
|
||||
self.exception = kwargs.pop("exception", None)
|
||||
self.status, self.data = kwargs.pop("status", 200), kwargs.pop("data", "{}")
|
||||
self.headers = kwargs.pop("headers", {})
|
||||
self.delay = kwargs.pop("delay", None)
|
||||
self.calls = []
|
||||
super(DummyConnection, self).__init__(**kwargs)
|
||||
|
||||
def perform_request(self, *args, **kwargs):
|
||||
self.calls.append((args, kwargs))
|
||||
if self.delay is not None:
|
||||
time.sleep(self.delay)
|
||||
if self.exception:
|
||||
raise self.exception
|
||||
return self.status, self.headers, self.data
|
||||
@@ -129,16 +118,12 @@ class TestHostsInfoCallback(TestCase):
|
||||
class TestTransport(TestCase):
|
||||
def test_single_connection_uses_dummy_connection_pool(self):
|
||||
t = Transport([{}])
|
||||
t._verified_elasticsearch = True
|
||||
self.assertIsInstance(t.connection_pool, DummyConnectionPool)
|
||||
|
||||
t = Transport([{"host": "localhost"}])
|
||||
t._verified_elasticsearch = True
|
||||
self.assertIsInstance(t.connection_pool, DummyConnectionPool)
|
||||
|
||||
def test_request_timeout_extracted_from_params_and_passed(self):
|
||||
t = Transport([{}], meta_header=False, connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", params={"request_timeout": 42})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -152,7 +137,6 @@ class TestTransport(TestCase):
|
||||
t = Transport(
|
||||
[{}], opaque_id="app-1", meta_header=False, connection_class=DummyConnection
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/")
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -173,7 +157,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_request_with_custom_user_agent_header(self):
|
||||
t = Transport([{}], meta_header=False, connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", headers={"user-agent": "my-custom-value/1.2.3"})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -188,7 +171,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_send_get_body_as_source(self):
|
||||
t = Transport([{}], send_get_body_as="source", connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body={})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -198,7 +180,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_send_get_body_as_post(self):
|
||||
t = Transport([{}], send_get_body_as="POST", connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body={})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -206,7 +187,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_client_meta_header(self):
|
||||
t = Transport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body={})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -219,7 +199,6 @@ class TestTransport(TestCase):
|
||||
HTTP_CLIENT_META = ("dm", "1.2.3")
|
||||
|
||||
t = Transport([{}], connection_class=DummyConnectionWithMeta)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body={}, headers={"Custom": "header"})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -232,7 +211,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_client_meta_header_not_sent(self):
|
||||
t = Transport([{}], meta_header=False, connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body={})
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -246,7 +224,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_body_gets_encoded_into_bytes(self):
|
||||
t = Transport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body="你好")
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -257,7 +234,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_body_bytes_get_passed_untouched(self):
|
||||
t = Transport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
body = b"\xe4\xbd\xa0\xe5\xa5\xbd"
|
||||
t.perform_request("GET", "/", body=body)
|
||||
@@ -266,7 +242,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_body_surrogates_replaced_encoded_into_bytes(self):
|
||||
t = Transport([{}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
t.perform_request("GET", "/", body="你好\uda6a")
|
||||
self.assertEqual(1, len(t.get_connection().calls))
|
||||
@@ -277,14 +252,12 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_kwargs_passed_on_to_connections(self):
|
||||
t = Transport([{"host": "google.com"}], port=123)
|
||||
t._verified_elasticsearch = True
|
||||
self.assertEqual(1, len(t.connection_pool.connections))
|
||||
self.assertEqual("http://google.com:123", t.connection_pool.connections[0].host)
|
||||
|
||||
def test_kwargs_passed_on_to_connection_pool(self):
|
||||
dt = object()
|
||||
t = Transport([{}, {}], dead_timeout=dt)
|
||||
t._verified_elasticsearch = True
|
||||
self.assertIs(dt, t.connection_pool.dead_timeout)
|
||||
|
||||
def test_custom_connection_class(self):
|
||||
@@ -293,13 +266,11 @@ class TestTransport(TestCase):
|
||||
self.kwargs = kwargs
|
||||
|
||||
t = Transport([{}], connection_class=MyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
self.assertEqual(1, len(t.connection_pool.connections))
|
||||
self.assertIsInstance(t.connection_pool.connections[0], MyConnection)
|
||||
|
||||
def test_add_connection(self):
|
||||
t = Transport([{}], randomize_hosts=False)
|
||||
t._verified_elasticsearch = True
|
||||
t.add_connection({"host": "google.com", "port": 1234})
|
||||
|
||||
self.assertEqual(2, len(t.connection_pool.connections))
|
||||
@@ -312,7 +283,6 @@ class TestTransport(TestCase):
|
||||
[{"exception": ConnectionError("abandon ship")}],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
|
||||
self.assertEqual(4, len(t.get_connection().calls))
|
||||
@@ -322,7 +292,6 @@ class TestTransport(TestCase):
|
||||
[{"exception": ConnectionError("abandon ship")}] * 2,
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
|
||||
self.assertEqual(0, len(t.connection_pool.connections))
|
||||
@@ -330,7 +299,6 @@ class TestTransport(TestCase):
|
||||
def test_resurrected_connection_will_be_marked_as_live_on_success(self):
|
||||
for method in ("GET", "HEAD"):
|
||||
t = Transport([{}, {}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
con1 = t.connection_pool.get_connection()
|
||||
con2 = t.connection_pool.get_connection()
|
||||
t.connection_pool.mark_dead(con1)
|
||||
@@ -342,7 +310,6 @@ class TestTransport(TestCase):
|
||||
|
||||
def test_sniff_will_use_seed_connections(self):
|
||||
t = Transport([{"data": CLUSTER_NODES}], connection_class=DummyConnection)
|
||||
t._verified_elasticsearch = True
|
||||
t.set_connections([{"data": "invalid"}])
|
||||
|
||||
t.sniff_hosts()
|
||||
@@ -355,7 +322,6 @@ class TestTransport(TestCase):
|
||||
connection_class=DummyConnection,
|
||||
sniff_on_start=True,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
self.assertEqual(1, len(t.connection_pool.connections))
|
||||
self.assertEqual("http://1.1.1.1:123", t.get_connection().host)
|
||||
|
||||
@@ -366,7 +332,6 @@ class TestTransport(TestCase):
|
||||
sniff_on_start=True,
|
||||
sniff_timeout=12,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
self.assertEqual(
|
||||
(("GET", "/_nodes/_all/http"), {"timeout": None}),
|
||||
t.seed_connections[0].calls[0],
|
||||
@@ -378,7 +343,6 @@ class TestTransport(TestCase):
|
||||
connection_class=DummyConnection,
|
||||
sniff_timeout=42,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
t.sniff_hosts()
|
||||
self.assertEqual(
|
||||
(("GET", "/_nodes/_all/http"), {"timeout": 42}),
|
||||
@@ -391,7 +355,6 @@ class TestTransport(TestCase):
|
||||
connection_class=DummyConnection,
|
||||
randomize_hosts=False,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
connection = t.connection_pool.connections[1]
|
||||
|
||||
t.sniff_hosts()
|
||||
@@ -406,7 +369,6 @@ class TestTransport(TestCase):
|
||||
max_retries=0,
|
||||
randomize_hosts=False,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
|
||||
self.assertEqual(1, len(t.connection_pool.connections))
|
||||
@@ -422,7 +384,6 @@ class TestTransport(TestCase):
|
||||
max_retries=3,
|
||||
randomize_hosts=False,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
conn_err, conn_data = t.connection_pool.connections
|
||||
response = t.perform_request("GET", "/")
|
||||
@@ -437,7 +398,6 @@ class TestTransport(TestCase):
|
||||
connection_class=DummyConnection,
|
||||
sniffer_timeout=5,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
for _ in range(4):
|
||||
t.perform_request("GET", "/")
|
||||
@@ -458,7 +418,6 @@ class TestTransport(TestCase):
|
||||
connection_class=DummyConnection,
|
||||
sniff_timeout=42,
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
t.sniff_hosts()
|
||||
# Ensure we parsed out the fqdn and port from the fqdn/ip:port string.
|
||||
self.assertEqual(
|
||||
@@ -474,480 +433,6 @@ class TestTransport(TestCase):
|
||||
sniff_on_connection_fail=True,
|
||||
cloud_id="cluster:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyQ0ZmE4ODIxZTc1NjM0MDMyYmVkMWNmMjIxMTBlMmY5NyQ0ZmE4ODIxZTc1NjM0MDMyYmVkMWNmMjIxMTBlMmY5Ng==",
|
||||
)
|
||||
t._verified_elasticsearch = True
|
||||
|
||||
self.assertFalse(t.sniff_on_connection_fail)
|
||||
self.assertIs(sniff_hosts.call_args, None) # Assert not called.
|
||||
|
||||
|
||||
TAGLINE = "You Know, for Search"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["headers", "response", "product_error"],
|
||||
[
|
||||
# All empty.
|
||||
({}, {}, _ProductChecker.UNSUPPORTED_PRODUCT),
|
||||
# Don't check the product header immediately, need to check version first.
|
||||
(
|
||||
{"x-elastic-product": "Elasticsearch"},
|
||||
{},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Version not there.
|
||||
({}, {"tagline": TAGLINE}, _ProductChecker.UNSUPPORTED_PRODUCT),
|
||||
# Version is nonsense
|
||||
(
|
||||
{},
|
||||
{"version": "1.0.0", "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Version number not there
|
||||
({}, {"version": {}, "tagline": TAGLINE}, _ProductChecker.UNSUPPORTED_PRODUCT),
|
||||
# Version number is nonsense
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "nonsense"}, "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Version number way in the past
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "1.0.0"}, "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Version number way in the future
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "999.0.0"}, "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Build flavor not supposed to be missing
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "7.13.0"}, "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_DISTRIBUTION,
|
||||
),
|
||||
# Build flavor is 'oss'
|
||||
(
|
||||
{},
|
||||
{
|
||||
"version": {"number": "7.10.0", "build_flavor": "oss"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
_ProductChecker.UNSUPPORTED_DISTRIBUTION,
|
||||
),
|
||||
# Build flavor is nonsense
|
||||
(
|
||||
{},
|
||||
{
|
||||
"version": {"number": "7.13.0", "build_flavor": "nonsense"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
_ProductChecker.UNSUPPORTED_DISTRIBUTION,
|
||||
),
|
||||
# Tagline is nonsense
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "7.1.0-SNAPSHOT"}, "tagline": "nonsense"},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Product header is not supposed to be missing
|
||||
(
|
||||
{},
|
||||
{"version": {"number": "7.14.0"}, "tagline": "You Know, for Search"},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
# Product header is nonsense
|
||||
(
|
||||
{"x-elastic-product": "nonsense"},
|
||||
{"version": {"number": "7.15.0"}, "tagline": TAGLINE},
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_verify_elasticsearch_errors(headers, response, product_error):
|
||||
assert _ProductChecker.check_product(headers, response) == product_error
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["headers", "response"],
|
||||
[
|
||||
({}, {"version": {"number": "6.0.0"}, "tagline": TAGLINE}),
|
||||
({}, {"version": {"number": "6.99.99"}, "tagline": TAGLINE}),
|
||||
(
|
||||
{},
|
||||
{
|
||||
"version": {"number": "7.0.0", "build_flavor": "default"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
),
|
||||
(
|
||||
{},
|
||||
{
|
||||
"version": {"number": "7.13.99", "build_flavor": "default"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
),
|
||||
(
|
||||
{"x-elastic-product": "Elasticsearch"},
|
||||
{
|
||||
"version": {"number": "7.14.0", "build_flavor": "default"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
),
|
||||
(
|
||||
{"x-elastic-product": "Elasticsearch"},
|
||||
{
|
||||
"version": {"number": "7.99.99", "build_flavor": "default"},
|
||||
"tagline": TAGLINE,
|
||||
},
|
||||
),
|
||||
(
|
||||
{"x-elastic-product": "Elasticsearch"},
|
||||
{
|
||||
"version": {"number": "8.0.0"},
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_verify_elasticsearch_passes(headers, response):
|
||||
result = _ProductChecker.check_product(headers, response)
|
||||
assert result == _ProductChecker.SUCCESS
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["headers", "data"],
|
||||
[
|
||||
(
|
||||
{},
|
||||
'{"version":{"number":"6.99.0"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
(
|
||||
{},
|
||||
"""{
|
||||
"name" : "io",
|
||||
"cluster_name" : "elasticsearch",
|
||||
"cluster_uuid" : "HaMHUswUSGGnzla8B17Iqw",
|
||||
"version" : {
|
||||
"number" : "7.6.0",
|
||||
"build_flavor" : "default",
|
||||
"build_type" : "tar",
|
||||
"build_hash" : "7f634e9f44834fbc12724506cc1da681b0c3b1e3",
|
||||
"build_date" : "2020-02-06T00:09:00.449973Z",
|
||||
"build_snapshot" : false,
|
||||
"lucene_version" : "8.4.0",
|
||||
"minimum_wire_compatibility_version" : "6.8.0",
|
||||
"minimum_index_compatibility_version" : "6.0.0-beta1"
|
||||
},
|
||||
"tagline" : "You Know, for Search"
|
||||
}""",
|
||||
),
|
||||
(
|
||||
{},
|
||||
'{"version":{"number":"7.13.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
(
|
||||
{"X-elastic-product": "Elasticsearch"},
|
||||
'{"version":{"number":"7.14.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_verify_elasticsearch(headers, data):
|
||||
t = Transport(
|
||||
[{"data": data, "headers": headers}], connection_class=DummyConnection
|
||||
)
|
||||
t.perform_request("GET", "/_search")
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
_ = [call[1]["headers"].pop("x-elastic-client-meta") for call in calls]
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
("GET", "/"),
|
||||
{
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
},
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
(
|
||||
("GET", "/_search", None, None),
|
||||
{
|
||||
"headers": {},
|
||||
"ignore": (),
|
||||
"timeout": None,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception_cls", [AuthorizationException, AuthenticationException]
|
||||
)
|
||||
def test_verify_elasticsearch_skips_on_auth_errors(exception_cls):
|
||||
t = Transport(
|
||||
[{"exception": exception_cls(exception_cls.status_code)}],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
with pytest.warns(ElasticsearchWarning) as warns:
|
||||
with pytest.raises(exception_cls):
|
||||
t.perform_request(
|
||||
"GET",
|
||||
"/_search",
|
||||
headers={"Authorization": "testme"},
|
||||
params={"request_timeout": 3},
|
||||
)
|
||||
|
||||
# Assert that a warning was raised due to security privileges
|
||||
assert [str(w.message) for w in warns] == [
|
||||
"The client is unable to verify that the server is "
|
||||
"Elasticsearch due security privileges on the server side"
|
||||
]
|
||||
|
||||
# Assert that the cluster is "verified"
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
# See that the headers were passed along to the "info" request made
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
_ = [call[1]["headers"].pop("x-elastic-client-meta") for call in calls]
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
("GET", "/"),
|
||||
{
|
||||
"headers": {
|
||||
"accept": "application/json",
|
||||
"authorization": "testme",
|
||||
},
|
||||
"timeout": 3,
|
||||
},
|
||||
),
|
||||
(
|
||||
("GET", "/_search", {}, None),
|
||||
{
|
||||
"headers": {
|
||||
"Authorization": "testme",
|
||||
},
|
||||
"ignore": (),
|
||||
"timeout": 3,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_multiple_requests_verify_elasticsearch_success():
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
return pytest.skip("Requires the 'threading' module")
|
||||
|
||||
t = Transport(
|
||||
[
|
||||
{
|
||||
"data": '{"version":{"number":"7.13.0","build_flavor":"default"},"tagline":"You Know, for Search"}',
|
||||
"delay": 1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
class RequestThread(threading.Thread):
|
||||
def run(self):
|
||||
try:
|
||||
results.append(t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(time.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
threads = []
|
||||
start_time = time.time()
|
||||
for _ in range(10):
|
||||
thread = RequestThread()
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
end_time = time.time()
|
||||
|
||||
# Exactly 10 results completed
|
||||
assert len(results) == 10
|
||||
|
||||
# No errors in the results
|
||||
assert all(isinstance(result, dict) for result in results)
|
||||
|
||||
# Assert that this took longer than 2 seconds but less than 2.1 seconds
|
||||
duration = end_time - start_time
|
||||
assert 2 <= duration <= 2.1
|
||||
|
||||
# Assert that every result came after ~2 seconds, no fast completions.
|
||||
assert all(
|
||||
2 <= completed_time - start_time <= 2.1 for completed_time in completed_at
|
||||
)
|
||||
|
||||
# Assert that the cluster is "verified"
|
||||
assert t._verified_elasticsearch is True
|
||||
|
||||
# See that the first request is always 'GET /' for ES check
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert calls[0][0] == ("GET", "/")
|
||||
|
||||
# The rest of the requests are 'GET /_search' afterwards
|
||||
assert all(call[0][:2] == ("GET", "/_search") for call in calls[1:])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["build_flavor", "tagline", "product_error", "error_message"],
|
||||
[
|
||||
(
|
||||
"default",
|
||||
"BAD TAGLINE",
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
"The client noticed that the server is not Elasticsearch and we do not support this unknown product",
|
||||
),
|
||||
(
|
||||
"BAD BUILD FLAVOR",
|
||||
"BAD TAGLINE",
|
||||
_ProductChecker.UNSUPPORTED_PRODUCT,
|
||||
"The client noticed that the server is not Elasticsearch and we do not support this unknown product",
|
||||
),
|
||||
(
|
||||
"BAD BUILD FLAVOR",
|
||||
"You Know, for Search",
|
||||
_ProductChecker.UNSUPPORTED_DISTRIBUTION,
|
||||
"The client noticed that the server is not a supported distribution of Elasticsearch",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_multiple_requests_verify_elasticsearch_product_error(
|
||||
build_flavor, tagline, product_error, error_message
|
||||
):
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
return pytest.skip("Requires the 'threading' module")
|
||||
|
||||
t = Transport(
|
||||
[
|
||||
{
|
||||
"data": '{"version":{"number":"7.13.0","build_flavor":"%s"},"tagline":"%s"}'
|
||||
% (build_flavor, tagline),
|
||||
"delay": 1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
class RequestThread(threading.Thread):
|
||||
def run(self):
|
||||
try:
|
||||
results.append(t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(time.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
threads = []
|
||||
start_time = time.time()
|
||||
for _ in range(10):
|
||||
thread = RequestThread()
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
end_time = time.time()
|
||||
|
||||
# Exactly 10 results completed
|
||||
assert len(results) == 10
|
||||
|
||||
# All results were errors
|
||||
assert all(isinstance(result, UnsupportedProductError) for result in results)
|
||||
assert all(str(result) == error_message for result in results)
|
||||
|
||||
# Assert that one request was made but not 2 requests.
|
||||
duration = end_time - start_time
|
||||
assert 1 <= duration <= 1.1
|
||||
|
||||
# Assert that every result came after ~1 seconds, no fast completions.
|
||||
assert all(
|
||||
1 <= completed_time - start_time <= 1.1 for completed_time in completed_at
|
||||
)
|
||||
|
||||
# Assert that the cluster is definitely not Elasticsearch
|
||||
assert t._verified_elasticsearch == product_error
|
||||
|
||||
# See that the first request is always 'GET /' for ES check
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert calls[0][0] == ("GET", "/")
|
||||
|
||||
# The rest of the requests are 'GET /_search' afterwards
|
||||
assert all(call[0][:2] == ("GET", "/_search") for call in calls[1:])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("error_cls", [ConnectionError, NotFoundError])
|
||||
def test_multiple_requests_verify_elasticsearch_retry_on_errors(error_cls):
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
return pytest.skip("Requires the 'threading' module")
|
||||
|
||||
t = Transport(
|
||||
[
|
||||
{
|
||||
"exception": error_cls(),
|
||||
"delay": 0.1,
|
||||
}
|
||||
],
|
||||
connection_class=DummyConnection,
|
||||
)
|
||||
|
||||
results = []
|
||||
completed_at = []
|
||||
|
||||
class RequestThread(threading.Thread):
|
||||
def run(self):
|
||||
try:
|
||||
results.append(t.perform_request("GET", "/_search"))
|
||||
except Exception as e:
|
||||
results.append(e)
|
||||
completed_at.append(time.time())
|
||||
|
||||
# Execute a bunch of requests concurrently.
|
||||
threads = []
|
||||
start_time = time.time()
|
||||
for _ in range(5):
|
||||
thread = RequestThread()
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
end_time = time.time()
|
||||
|
||||
# Exactly 5 results completed
|
||||
assert len(results) == 5
|
||||
|
||||
# All results were errors and not wrapped in 'UnsupportedProductError'
|
||||
assert all(isinstance(result, error_cls) for result in results)
|
||||
|
||||
# Assert that 5 requests were made in total (5 transport requests per x 0.1s/conn request)
|
||||
duration = end_time - start_time
|
||||
assert 0.5 <= duration <= 0.6
|
||||
|
||||
# Assert that the cluster is still in the unknown/unverified stage.
|
||||
assert t._verified_elasticsearch is None
|
||||
|
||||
# See that the API isn't hit, instead it's the index requests that are failing.
|
||||
calls = t.connection_pool.connections[0].calls
|
||||
assert len(calls) == 5
|
||||
assert all(call[0] == ("GET", "/") for call in calls)
|
||||
|
||||
Reference in New Issue
Block a user