|
22 | 22 | from platform import python_version
|
23 | 23 |
|
24 | 24 | from ._version import __versionstr__
|
| 25 | +from .compat import Lock |
25 | 26 | from .connection import Urllib3HttpConnection
|
26 | 27 | from .connection_pool import ConnectionPool, DummyConnectionPool, EmptyConnectionPool
|
27 | 28 | from .exceptions import (
|
@@ -204,9 +205,22 @@ def __init__(
|
204 | 205 | if http_client_meta:
|
205 | 206 | self._client_meta += (http_client_meta,)
|
206 | 207 |
|
207 |
| - # Flag which is set after verifying that we're |
208 |
| - # connected to Elasticsearch. |
209 |
| - self._verified_elasticsearch = False |
| 208 | + # Tri-state flag that describes what state the verification |
| 209 | + # of whether we're connected to an Elasticsearch cluster or not. |
| 210 | + # The three states are: |
| 211 | + # - 'None': Means we've either not started the verification process |
| 212 | + # or that the verification is in progress. '_verified_once' ensures |
| 213 | + # that multiple requests don't kick off multiple verification processes. |
| 214 | + # - 'True': Means we've verified that we're talking to Elasticsearch or |
| 215 | + # that we can't rule out Elasticsearch due to auth issues. A warning |
| 216 | + # will be raised if we receive 401/403. |
| 217 | + # - 'False': Means we've discovered we're not talking to Elasticsearch, |
| 218 | + # should raise an error in this case for every request. |
| 219 | + self._verified_elasticsearch = None |
| 220 | + |
| 221 | + # Ensures that the ES verification request only fires once and that |
| 222 | + # all requests block until this request returns back. |
| 223 | + self._verified_once = Once() |
210 | 224 |
|
211 | 225 | def add_connection(self, host):
|
212 | 226 | """
|
@@ -391,7 +405,17 @@ def perform_request(self, method, url, headers=None, params=None, body=None):
|
391 | 405 | )
|
392 | 406 |
|
393 | 407 | # Before we make the actual API call we verify the Elasticsearch instance.
|
394 |
| - self._do_verify_elasticsearch(headers=headers, timeout=timeout) |
| 408 | + if self._verified_elasticsearch is None: |
| 409 | + self._verified_once.call( |
| 410 | + self._do_verify_elasticsearch, headers=headers, timeout=timeout |
| 411 | + ) |
| 412 | + |
| 413 | + # If '_verified_elasticsearch' is False we know we're not connected to Elasticsearch. |
| 414 | + if self._verified_elasticsearch is False: |
| 415 | + raise NotElasticsearchError( |
| 416 | + "The client noticed that the server is not Elasticsearch " |
| 417 | + "and we do not support this unknown product" |
| 418 | + ) |
395 | 419 |
|
396 | 420 | for attempt in range(self.max_retries + 1):
|
397 | 421 | connection = self.get_connection()
|
@@ -513,7 +537,7 @@ def _do_verify_elasticsearch(self, headers, timeout):
|
513 | 537 | error we instead emit an 'ElasticsearchWarning'.
|
514 | 538 | """
|
515 | 539 | # Product check has already been done, no need to do again.
|
516 |
| - if self._verified_elasticsearch: |
| 540 | + if self._verified_elasticsearch is not None: |
517 | 541 | return
|
518 | 542 |
|
519 | 543 | headers = {header.lower(): value for header, value in (headers or {}).items()}
|
@@ -566,10 +590,9 @@ def _do_verify_elasticsearch(self, headers, timeout):
|
566 | 590 | raise error
|
567 | 591 |
|
568 | 592 | # Check the information we got back from the index request.
|
569 |
| - _verify_elasticsearch(info_headers, info_response) |
570 |
| - |
571 |
| - # If we made it through the above call this config is verified. |
572 |
| - self._verified_elasticsearch = True |
| 593 | + self._verified_elasticsearch = _verify_elasticsearch( |
| 594 | + info_headers, info_response |
| 595 | + ) |
573 | 596 |
|
574 | 597 |
|
575 | 598 | def _verify_elasticsearch(headers, response):
|
@@ -616,7 +639,20 @@ def _verify_elasticsearch(headers, response):
|
616 | 639 | # 7.14+ and there's a bad 'X-Elastic-Product' HTTP header
|
617 | 640 | or ((7, 14, 0) <= version_number and bad_product_header)
|
618 | 641 | ):
|
619 |
| - raise NotElasticsearchError( |
620 |
| - "The client noticed that the server is not Elasticsearch " |
621 |
| - "and we do not support this unknown product" |
622 |
| - ) |
| 642 | + return False |
| 643 | + |
| 644 | + return True |
| 645 | + |
| 646 | + |
| 647 | +class Once: |
| 648 | + """Simple class which forces a function to only execute once.""" |
| 649 | + |
| 650 | + def __init__(self): |
| 651 | + self._lock = Lock() |
| 652 | + self._called = False |
| 653 | + |
| 654 | + def call(self, func, *args, **kwargs): |
| 655 | + with self._lock: |
| 656 | + if not self._called: |
| 657 | + self._called = True |
| 658 | + func(*args, **kwargs) |
0 commit comments