2021-08-06 12:59:39 +05:30
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
#
|
|
|
|
|
# The OpenSearch Contributors require contributions made to
|
|
|
|
|
# this file be licensed under the Apache-2.0 license or a
|
|
|
|
|
# compatible open source license.
|
|
|
|
|
#
|
|
|
|
|
# Modifications Copyright OpenSearch Contributors. See
|
|
|
|
|
# GitHub history for details.
|
|
|
|
|
#
|
2020-07-02 13:15:25 -05:00
|
|
|
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
|
|
|
# license agreements. See the NOTICE file distributed with
|
|
|
|
|
# this work for additional information regarding copyright
|
|
|
|
|
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
|
|
|
# the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
|
# not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing,
|
|
|
|
|
# software distributed under the License is distributed on an
|
|
|
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
# KIND, either express or implied. See the License for the
|
|
|
|
|
# specific language governing permissions and limitations
|
|
|
|
|
# under the License.
|
2020-04-23 11:22:08 -05:00
|
|
|
|
2022-10-04 00:15:18 +05:30
|
|
|
|
2019-07-28 00:02:31 +02:00
|
|
|
import time
|
2015-01-09 00:16:34 +01:00
|
|
|
from itertools import chain
|
2013-05-03 02:03:05 +02:00
|
|
|
|
2013-08-08 18:06:36 +02:00
|
|
|
from .connection import Urllib3HttpConnection
|
2020-05-13 13:21:42 -05:00
|
|
|
from .connection_pool import ConnectionPool, DummyConnectionPool, EmptyConnectionPool
|
2019-05-10 09:16:33 -06:00
|
|
|
from .exceptions import (
|
|
|
|
|
ConnectionError,
|
|
|
|
|
ConnectionTimeout,
|
2021-01-13 14:21:04 -06:00
|
|
|
SerializationError,
|
|
|
|
|
TransportError,
|
2019-05-10 09:16:33 -06:00
|
|
|
)
|
2021-01-13 14:21:04 -06:00
|
|
|
from .serializer import DEFAULT_SERIALIZERS, Deserializer, JSONSerializer
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2013-05-22 18:45:55 +02:00
|
|
|
|
|
|
|
|
def get_host_info(node_info, host):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2013-05-22 18:45:55 +02:00
|
|
|
Simple callback that takes the node info from `/_cluster/nodes` and a
|
|
|
|
|
parsed connection information and return the connection information. If
|
|
|
|
|
`None` is returned this node will be skipped.
|
|
|
|
|
|
|
|
|
|
Useful for filtering nodes (by proximity for example) or if additional
|
2021-09-16 14:59:29 +05:30
|
|
|
information needs to be provided for the :class:`~opensearchpy.Connection`
|
2022-11-28 14:29:37 -08:00
|
|
|
class. By default cluster_manager only nodes are filtered out since they shouldn't
|
2014-07-31 00:33:07 +02:00
|
|
|
typically be used for API operations.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
2013-05-22 18:45:55 +02:00
|
|
|
:arg node_info: node information from `/_cluster/nodes`
|
|
|
|
|
:arg host: connection information (host, port) extracted from the node info
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2022-11-28 14:29:37 -08:00
|
|
|
# ignore cluster_manager only nodes
|
|
|
|
|
if node_info.get("roles", []) == ["cluster_manager"]:
|
2014-07-31 00:33:07 +02:00
|
|
|
return None
|
2013-05-22 18:45:55 +02:00
|
|
|
return host
|
2013-05-03 02:03:05 +02:00
|
|
|
|
2019-05-10 09:16:33 -06:00
|
|
|
|
2013-05-03 01:08:25 +02:00
|
|
|
class Transport(object):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
|
|
|
|
Encapsulation of transport-related to logic. Handles instantiation of the
|
|
|
|
|
individual connections as well as creating a connection pool to hold them.
|
|
|
|
|
|
|
|
|
|
Main interface is the `perform_request` method.
|
|
|
|
|
"""
|
2019-05-10 09:16:33 -06:00
|
|
|
|
2020-05-13 13:21:42 -05:00
|
|
|
DEFAULT_CONNECTION_CLASS = Urllib3HttpConnection
|
|
|
|
|
|
2019-05-10 09:16:33 -06:00
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
hosts,
|
2020-05-13 13:21:42 -05:00
|
|
|
connection_class=None,
|
2019-05-10 09:16:33 -06:00
|
|
|
connection_pool_class=ConnectionPool,
|
|
|
|
|
host_info_callback=get_host_info,
|
|
|
|
|
sniff_on_start=False,
|
|
|
|
|
sniffer_timeout=None,
|
|
|
|
|
sniff_timeout=0.1,
|
|
|
|
|
sniff_on_connection_fail=False,
|
|
|
|
|
serializer=JSONSerializer(),
|
|
|
|
|
serializers=None,
|
|
|
|
|
default_mimetype="application/json",
|
|
|
|
|
max_retries=3,
|
|
|
|
|
retry_on_status=(502, 503, 504),
|
|
|
|
|
retry_on_timeout=False,
|
|
|
|
|
send_get_body_as="GET",
|
|
|
|
|
**kwargs
|
|
|
|
|
):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
|
|
|
|
:arg hosts: list of dictionaries, each containing keyword arguments to
|
|
|
|
|
create a `connection_class` instance
|
2021-09-16 14:59:29 +05:30
|
|
|
:arg connection_class: subclass of :class:`~opensearchpy.Connection` to use
|
|
|
|
|
:arg connection_pool_class: subclass of :class:`~opensearchpy.ConnectionPool` to use
|
2013-05-22 18:45:55 +02:00
|
|
|
:arg host_info_callback: callback responsible for taking the node information from
|
2019-09-19 11:13:02 +08:00
|
|
|
`/_cluster/nodes`, along with already extracted information, and
|
2013-05-22 18:45:55 +02:00
|
|
|
producing a list of arguments (same as `hosts` parameter)
|
2013-05-19 18:26:10 +02:00
|
|
|
:arg sniff_on_start: flag indicating whether to obtain a list of nodes
|
2019-09-19 11:13:02 +08:00
|
|
|
from the cluster at startup time
|
2013-05-23 17:33:36 +02:00
|
|
|
:arg sniffer_timeout: number of seconds between automatic sniffs
|
2013-11-18 02:17:17 +05:30
|
|
|
:arg sniff_on_connection_fail: flag controlling if connection failure triggers a sniff
|
2014-02-10 16:56:00 +01:00
|
|
|
:arg sniff_timeout: timeout used for the sniff request - it should be a
|
|
|
|
|
fast api call and we are talking potentially to more nodes so we want
|
2015-01-04 19:42:29 +01:00
|
|
|
to fail quickly. Not used during initial sniffing (if
|
|
|
|
|
``sniff_on_start`` is on) when the connection still isn't
|
|
|
|
|
initialized.
|
2013-05-19 18:26:10 +02:00
|
|
|
:arg serializer: serializer instance
|
2013-12-14 18:15:15 +01:00
|
|
|
:arg serializers: optional dict of serializer instances that will be
|
|
|
|
|
used for deserializing data coming from the server. (key is the mimetype)
|
|
|
|
|
:arg default_mimetype: when no mimetype is specified by the server
|
|
|
|
|
response assume this mimetype, defaults to `'application/json'`
|
2013-05-19 18:26:10 +02:00
|
|
|
:arg max_retries: maximum number of retries before an exception is propagated
|
2014-11-24 17:08:23 +01:00
|
|
|
:arg retry_on_status: set of HTTP status codes on which we should retry
|
2016-04-26 20:08:06 +02:00
|
|
|
on a different node. defaults to ``(502, 503, 504)``
|
2014-11-10 23:52:26 +01:00
|
|
|
:arg retry_on_timeout: should timeout trigger a retry on different
|
|
|
|
|
node? (default `False`)
|
2013-12-02 22:57:47 +01:00
|
|
|
:arg send_get_body_as: for GET requests with body this option allows
|
|
|
|
|
you to specify an alternate way of execution for environments that
|
|
|
|
|
don't support passing bodies with GET requests. If you set this to
|
|
|
|
|
'POST' a POST method will be used instead, if to 'source' then the body
|
|
|
|
|
will be serialized and passed as a query parameter `source`.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
|
|
|
|
Any extra keyword arguments will be passed to the `connection_class`
|
2017-05-19 23:49:42 +02:00
|
|
|
when creating and instance unless overridden by that connection's
|
2013-05-19 18:26:10 +02:00
|
|
|
options provided as part of the hosts parameter.
|
|
|
|
|
"""
|
2020-05-13 13:21:42 -05:00
|
|
|
if connection_class is None:
|
|
|
|
|
connection_class = self.DEFAULT_CONNECTION_CLASS
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2013-12-14 18:15:15 +01:00
|
|
|
# serialization config
|
|
|
|
|
_serializers = DEFAULT_SERIALIZERS.copy()
|
|
|
|
|
# if a serializer has been specified, use it for deserialization as well
|
|
|
|
|
_serializers[serializer.mimetype] = serializer
|
|
|
|
|
# if custom serializers map has been supplied, override the defaults with it
|
|
|
|
|
if serializers:
|
|
|
|
|
_serializers.update(serializers)
|
|
|
|
|
# create a deserializer with our config
|
|
|
|
|
self.deserializer = Deserializer(_serializers, default_mimetype)
|
|
|
|
|
|
2013-05-03 02:03:05 +02:00
|
|
|
self.max_retries = max_retries
|
2014-11-10 23:52:26 +01:00
|
|
|
self.retry_on_timeout = retry_on_timeout
|
2014-11-24 17:08:23 +01:00
|
|
|
self.retry_on_status = retry_on_status
|
2013-12-02 22:57:47 +01:00
|
|
|
self.send_get_body_as = send_get_body_as
|
2013-05-03 01:08:25 +02:00
|
|
|
|
|
|
|
|
# data serializer
|
|
|
|
|
self.serializer = serializer
|
|
|
|
|
|
|
|
|
|
# store all strategies...
|
|
|
|
|
self.connection_pool_class = connection_pool_class
|
|
|
|
|
self.connection_class = connection_class
|
|
|
|
|
|
|
|
|
|
# ...save kwargs to be passed to the connections
|
|
|
|
|
self.kwargs = kwargs
|
|
|
|
|
self.hosts = hosts
|
|
|
|
|
|
2020-05-13 13:21:42 -05:00
|
|
|
# Start with an empty pool specifically for `AsyncTransport`.
|
|
|
|
|
# It should never be used, will be replaced on first call to
|
|
|
|
|
# .set_connections()
|
|
|
|
|
self.connection_pool = EmptyConnectionPool()
|
|
|
|
|
|
|
|
|
|
if hosts:
|
|
|
|
|
# ...and instantiate them
|
|
|
|
|
self.set_connections(hosts)
|
|
|
|
|
# retain the original connection instances for sniffing
|
|
|
|
|
self.seed_connections = list(self.connection_pool.connections[:])
|
|
|
|
|
else:
|
|
|
|
|
self.seed_connections = []
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2013-05-03 02:03:05 +02:00
|
|
|
# sniffing data
|
2013-05-23 17:33:36 +02:00
|
|
|
self.sniffer_timeout = sniffer_timeout
|
2020-05-13 13:21:42 -05:00
|
|
|
self.sniff_on_start = sniff_on_start
|
2013-05-03 02:03:05 +02:00
|
|
|
self.sniff_on_connection_fail = sniff_on_connection_fail
|
2013-05-23 17:33:36 +02:00
|
|
|
self.last_sniff = time.time()
|
2014-02-10 16:56:00 +01:00
|
|
|
self.sniff_timeout = sniff_timeout
|
2013-05-03 02:03:05 +02:00
|
|
|
|
2013-05-22 18:45:55 +02:00
|
|
|
# callback to construct host dict from data in /_cluster/nodes
|
|
|
|
|
self.host_info_callback = host_info_callback
|
2013-05-03 02:03:05 +02:00
|
|
|
|
|
|
|
|
if sniff_on_start:
|
2015-01-04 19:42:29 +01:00
|
|
|
self.sniff_hosts(True)
|
2013-05-03 02:03:05 +02:00
|
|
|
|
2013-05-03 01:08:25 +02:00
|
|
|
def add_connection(self, host):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2021-09-16 14:59:29 +05:30
|
|
|
Create a new :class:`~opensearchpy.Connection` instance and add it to the pool.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
|
|
|
|
:arg host: kwargs that will be used to create the instance
|
|
|
|
|
"""
|
2013-05-03 01:08:25 +02:00
|
|
|
self.hosts.append(host)
|
|
|
|
|
self.set_connections(self.hosts)
|
|
|
|
|
|
|
|
|
|
def set_connections(self, hosts):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2018-01-05 13:50:52 +00:00
|
|
|
Instantiate all the connections and create new connection pool to hold them.
|
|
|
|
|
Tries to identify unchanged hosts and re-use existing
|
2021-09-16 14:59:29 +05:30
|
|
|
:class:`~opensearchpy.Connection` instances.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
|
|
|
|
:arg hosts: same as `__init__`
|
|
|
|
|
"""
|
2023-02-14 11:09:14 -08:00
|
|
|
|
2013-05-03 01:08:25 +02:00
|
|
|
# construct the connections
|
|
|
|
|
def _create_connection(host):
|
2013-05-24 01:29:31 +02:00
|
|
|
# if this is not the initial setup look at the existing connection
|
|
|
|
|
# options and identify connections that haven't changed and can be
|
|
|
|
|
# kept around.
|
2019-05-10 09:16:33 -06:00
|
|
|
if hasattr(self, "connection_pool"):
|
2023-02-14 11:09:14 -08:00
|
|
|
for connection, old_host in self.connection_pool.connection_opts:
|
2013-05-24 01:29:31 +02:00
|
|
|
if old_host == host:
|
|
|
|
|
return connection
|
|
|
|
|
|
|
|
|
|
# previously unseen params, create new connection
|
2013-05-03 01:08:25 +02:00
|
|
|
kwargs = self.kwargs.copy()
|
|
|
|
|
kwargs.update(host)
|
|
|
|
|
return self.connection_class(**kwargs)
|
2019-05-10 09:16:33 -06:00
|
|
|
|
2013-05-24 01:29:31 +02:00
|
|
|
connections = map(_create_connection, hosts)
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2014-12-20 00:35:24 +01:00
|
|
|
connections = list(zip(connections, hosts))
|
|
|
|
|
if len(connections) == 1:
|
|
|
|
|
self.connection_pool = DummyConnectionPool(connections)
|
|
|
|
|
else:
|
|
|
|
|
# pass the hosts dicts to the connection pool to optionally extract parameters from
|
2019-05-10 09:16:33 -06:00
|
|
|
self.connection_pool = self.connection_pool_class(
|
|
|
|
|
connections, **self.kwargs
|
|
|
|
|
)
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2013-05-24 01:42:04 +02:00
|
|
|
def get_connection(self):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2021-09-16 14:59:29 +05:30
|
|
|
Retrieve a :class:`~opensearchpy.Connection` instance from the
|
|
|
|
|
:class:`~opensearchpy.ConnectionPool` instance.
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2013-05-24 01:42:04 +02:00
|
|
|
if self.sniffer_timeout:
|
2013-05-23 17:33:36 +02:00
|
|
|
if time.time() >= self.last_sniff + self.sniffer_timeout:
|
2013-05-03 02:03:05 +02:00
|
|
|
self.sniff_hosts()
|
|
|
|
|
return self.connection_pool.get_connection()
|
|
|
|
|
|
2015-12-07 18:26:00 +01:00
|
|
|
def _get_sniff_data(self, initial=False):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2019-09-19 11:13:02 +08:00
|
|
|
Perform the request to get sniffing information. Returns a list of
|
2015-12-07 18:26:00 +01:00
|
|
|
dictionaries (one per node) containing all the information from the
|
|
|
|
|
cluster.
|
2015-12-07 18:41:13 +01:00
|
|
|
|
|
|
|
|
It also sets the last_sniff attribute in case of a successful attempt.
|
|
|
|
|
|
|
|
|
|
In rare cases it might be possible to override this method in your
|
|
|
|
|
custom Transport class to serve data from alternative source like
|
|
|
|
|
configuration management.
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2013-05-23 17:33:36 +02:00
|
|
|
previous_sniff = self.last_sniff
|
2015-12-07 18:26:00 +01:00
|
|
|
|
2013-05-23 17:33:36 +02:00
|
|
|
try:
|
|
|
|
|
# reset last_sniff timestamp
|
|
|
|
|
self.last_sniff = time.time()
|
2013-05-24 01:42:04 +02:00
|
|
|
# go through all current connections as well as the
|
|
|
|
|
# seed_connections for good measure
|
2015-01-08 15:43:14 -08:00
|
|
|
for c in chain(self.connection_pool.connections, self.seed_connections):
|
2013-05-24 01:42:04 +02:00
|
|
|
try:
|
|
|
|
|
# use small timeout for the sniffing request, should be a fast api call
|
2016-12-06 12:17:57 +01:00
|
|
|
_, headers, node_info = c.perform_request(
|
2019-05-10 09:16:33 -06:00
|
|
|
"GET",
|
|
|
|
|
"/_nodes/_all/http",
|
|
|
|
|
timeout=self.sniff_timeout if not initial else None,
|
|
|
|
|
)
|
2021-06-24 09:46:33 -05:00
|
|
|
|
|
|
|
|
# Lowercase all the header names for consistency in accessing them.
|
|
|
|
|
headers = {
|
|
|
|
|
header.lower(): value for header, value in headers.items()
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-10 09:16:33 -06:00
|
|
|
node_info = self.deserializer.loads(
|
|
|
|
|
node_info, headers.get("content-type")
|
|
|
|
|
)
|
2013-05-24 01:42:04 +02:00
|
|
|
break
|
|
|
|
|
except (ConnectionError, SerializationError):
|
|
|
|
|
pass
|
|
|
|
|
else:
|
2014-04-30 18:25:16 +02:00
|
|
|
raise TransportError("N/A", "Unable to sniff hosts.")
|
2020-03-09 11:51:35 -05:00
|
|
|
except Exception:
|
2013-05-23 17:33:36 +02:00
|
|
|
# keep the previous value on error
|
|
|
|
|
self.last_sniff = previous_sniff
|
|
|
|
|
raise
|
2013-05-22 18:45:55 +02:00
|
|
|
|
2019-05-10 09:16:33 -06:00
|
|
|
return list(node_info["nodes"].values())
|
2015-12-07 18:26:00 +01:00
|
|
|
|
2016-03-14 22:23:04 +01:00
|
|
|
def _get_host_info(self, host_info):
|
|
|
|
|
host = {}
|
2019-05-10 09:16:33 -06:00
|
|
|
address = host_info.get("http", {}).get("publish_address")
|
2016-03-14 22:23:04 +01:00
|
|
|
|
2017-01-03 16:12:15 +02:00
|
|
|
# malformed or no address given
|
2019-05-10 09:16:33 -06:00
|
|
|
if not address or ":" not in address:
|
2016-03-14 22:23:04 +01:00
|
|
|
return None
|
|
|
|
|
|
2020-03-09 11:51:35 -05:00
|
|
|
if "/" in address:
|
2019-10-16 21:17:29 +00:00
|
|
|
# Support 7.x host/ip:port behavior where http.publish_host has been set.
|
2020-03-09 11:51:35 -05:00
|
|
|
fqdn, ipaddress = address.split("/", 1)
|
2019-10-16 21:17:29 +00:00
|
|
|
host["host"] = fqdn
|
2020-03-09 11:51:35 -05:00
|
|
|
_, host["port"] = ipaddress.rsplit(":", 1)
|
2019-10-16 21:17:29 +00:00
|
|
|
host["port"] = int(host["port"])
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
host["host"], host["port"] = address.rsplit(":", 1)
|
|
|
|
|
host["port"] = int(host["port"])
|
2016-03-14 22:23:04 +01:00
|
|
|
|
|
|
|
|
return self.host_info_callback(host_info, host)
|
2015-12-07 18:26:00 +01:00
|
|
|
|
|
|
|
|
def sniff_hosts(self, initial=False):
|
|
|
|
|
"""
|
|
|
|
|
Obtain a list of nodes from the cluster and create a new connection
|
|
|
|
|
pool using the information retrieved.
|
|
|
|
|
|
|
|
|
|
To extract the node connection parameters use the ``nodes_to_host_callback``.
|
|
|
|
|
|
|
|
|
|
:arg initial: flag indicating if this is during startup
|
|
|
|
|
(``sniff_on_start``), ignore the ``sniff_timeout`` if ``True``
|
|
|
|
|
"""
|
|
|
|
|
node_info = self._get_sniff_data(initial)
|
|
|
|
|
|
2016-03-14 22:23:04 +01:00
|
|
|
hosts = list(filter(None, (self._get_host_info(n) for n in node_info)))
|
2013-05-22 18:45:55 +02:00
|
|
|
|
2017-05-15 21:56:49 +02:00
|
|
|
# we weren't able to get any nodes or host_info_callback blocked all -
|
|
|
|
|
# raise error.
|
2013-11-03 22:38:30 +01:00
|
|
|
if not hosts:
|
2019-05-10 09:16:33 -06:00
|
|
|
raise TransportError(
|
|
|
|
|
"N/A", "Unable to sniff hosts - no viable hosts found."
|
|
|
|
|
)
|
2013-11-03 22:38:30 +01:00
|
|
|
|
2013-05-03 02:03:05 +02:00
|
|
|
self.set_connections(hosts)
|
|
|
|
|
|
2013-06-14 16:46:58 +02:00
|
|
|
def mark_dead(self, connection):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
|
|
|
|
Mark a connection as dead (failed) in the connection pool. If sniffing
|
2013-05-24 01:42:04 +02:00
|
|
|
on failure is enabled this will initiate the sniffing process.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
2021-09-16 14:59:29 +05:30
|
|
|
:arg connection: instance of :class:`~opensearchpy.Connection` that failed
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
2013-06-14 16:36:56 +02:00
|
|
|
# mark as dead even when sniffing to avoid hitting this host during the sniff process
|
2013-06-14 16:46:58 +02:00
|
|
|
self.connection_pool.mark_dead(connection)
|
2013-05-24 01:42:04 +02:00
|
|
|
if self.sniff_on_connection_fail:
|
|
|
|
|
self.sniff_hosts()
|
2013-05-03 02:03:05 +02:00
|
|
|
|
2017-07-12 22:34:17 -04:00
|
|
|
def perform_request(self, method, url, headers=None, params=None, body=None):
|
2013-05-19 18:26:10 +02:00
|
|
|
"""
|
|
|
|
|
Perform the actual request. Retrieve a connection from the connection
|
2023-10-09 13:45:18 -04:00
|
|
|
pool, pass all the information to its perform_request method and
|
2013-05-19 18:26:10 +02:00
|
|
|
return the data.
|
|
|
|
|
|
|
|
|
|
If an exception was raised, mark the connection as failed and retry (up
|
|
|
|
|
to `max_retries` times).
|
|
|
|
|
|
2019-09-19 11:13:02 +08:00
|
|
|
If the operation was successful and the connection used was previously
|
2023-10-09 13:45:18 -04:00
|
|
|
marked as dead, mark it as live, resetting its failure count.
|
2013-05-19 18:26:10 +02:00
|
|
|
|
|
|
|
|
:arg method: HTTP method to use
|
|
|
|
|
:arg url: absolute url (without host) to target
|
2017-07-12 22:34:17 -04:00
|
|
|
:arg headers: dictionary of headers, will be handed over to the
|
2021-09-16 14:59:29 +05:30
|
|
|
underlying :class:`~opensearchpy.Connection` class
|
2013-05-19 18:26:10 +02:00
|
|
|
:arg params: dictionary of query parameters, will be handed over to the
|
2021-09-16 14:59:29 +05:30
|
|
|
underlying :class:`~opensearchpy.Connection` class for serialization
|
2019-11-21 17:26:59 +01:00
|
|
|
:arg body: body of the request, will be serialized using serializer and
|
2013-05-19 18:26:10 +02:00
|
|
|
passed to the connection
|
|
|
|
|
"""
|
2021-08-17 12:02:41 +05:30
|
|
|
method, params, body, ignore, timeout = self._resolve_request_args(
|
|
|
|
|
method, params, body
|
2020-05-13 13:21:42 -05:00
|
|
|
)
|
2020-03-11 16:33:15 -05:00
|
|
|
|
2013-05-24 01:45:15 +02:00
|
|
|
for attempt in range(self.max_retries + 1):
|
2013-06-14 16:46:58 +02:00
|
|
|
connection = self.get_connection()
|
2013-05-03 01:08:25 +02:00
|
|
|
|
|
|
|
|
try:
|
2019-05-10 09:16:33 -06:00
|
|
|
status, headers_response, data = connection.perform_request(
|
|
|
|
|
method,
|
|
|
|
|
url,
|
|
|
|
|
params,
|
|
|
|
|
body,
|
|
|
|
|
headers=headers,
|
|
|
|
|
ignore=ignore,
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
)
|
2014-11-24 17:08:23 +01:00
|
|
|
|
2021-06-24 09:46:33 -05:00
|
|
|
# Lowercase all the header names for consistency in accessing them.
|
|
|
|
|
headers_response = {
|
|
|
|
|
header.lower(): value for header, value in headers_response.items()
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-24 17:08:23 +01:00
|
|
|
except TransportError as e:
|
2019-05-10 09:16:33 -06:00
|
|
|
if method == "HEAD" and e.status_code == 404:
|
2016-03-10 19:06:20 +01:00
|
|
|
return False
|
|
|
|
|
|
2014-11-24 17:08:23 +01:00
|
|
|
retry = False
|
|
|
|
|
if isinstance(e, ConnectionTimeout):
|
|
|
|
|
retry = self.retry_on_timeout
|
|
|
|
|
elif isinstance(e, ConnectionError):
|
|
|
|
|
retry = True
|
|
|
|
|
elif e.status_code in self.retry_on_status:
|
|
|
|
|
retry = True
|
|
|
|
|
|
|
|
|
|
if retry:
|
2020-07-20 11:17:19 -05:00
|
|
|
try:
|
|
|
|
|
# only mark as dead if we are retrying
|
|
|
|
|
self.mark_dead(connection)
|
|
|
|
|
except TransportError:
|
|
|
|
|
# If sniffing on failure, it could fail too. Catch the
|
|
|
|
|
# exception not to interrupt the retries.
|
|
|
|
|
pass
|
2014-11-10 23:52:26 +01:00
|
|
|
# raise exception on last retry
|
|
|
|
|
if attempt == self.max_retries:
|
2020-07-20 11:17:19 -05:00
|
|
|
raise e
|
2014-11-10 23:52:26 +01:00
|
|
|
else:
|
2020-07-20 11:17:19 -05:00
|
|
|
raise e
|
2014-11-10 23:52:26 +01:00
|
|
|
|
2013-05-03 01:08:25 +02:00
|
|
|
else:
|
2023-10-09 13:45:18 -04:00
|
|
|
# connection didn't fail, confirm its live status
|
2017-12-27 21:02:30 +01:00
|
|
|
self.connection_pool.mark_live(connection)
|
|
|
|
|
|
2019-05-10 09:16:33 -06:00
|
|
|
if method == "HEAD":
|
2016-03-10 19:06:20 +01:00
|
|
|
return 200 <= status < 300
|
|
|
|
|
|
2014-02-10 18:14:39 +01:00
|
|
|
if data:
|
2019-05-10 09:16:33 -06:00
|
|
|
data = self.deserializer.loads(
|
|
|
|
|
data, headers_response.get("content-type")
|
|
|
|
|
)
|
2015-12-08 22:19:07 +01:00
|
|
|
return data
|
2013-05-03 01:08:25 +02:00
|
|
|
|
2016-03-03 19:31:31 -05:00
|
|
|
def close(self):
|
|
|
|
|
"""
|
2018-01-01 17:35:26 +01:00
|
|
|
Explicitly closes connections
|
2016-03-03 19:31:31 -05:00
|
|
|
"""
|
|
|
|
|
self.connection_pool.close()
|
2020-05-13 13:21:42 -05:00
|
|
|
|
2021-08-17 12:02:41 +05:30
|
|
|
def _resolve_request_args(self, method, params, body):
|
2020-05-13 13:21:42 -05:00
|
|
|
"""Resolves parameters for .perform_request()"""
|
|
|
|
|
if body is not None:
|
|
|
|
|
body = self.serializer.dumps(body)
|
|
|
|
|
|
|
|
|
|
# some clients or environments don't support sending GET with body
|
|
|
|
|
if method in ("HEAD", "GET") and self.send_get_body_as != "GET":
|
|
|
|
|
# send it as post instead
|
|
|
|
|
if self.send_get_body_as == "POST":
|
|
|
|
|
method = "POST"
|
|
|
|
|
|
|
|
|
|
# or as source parameter
|
|
|
|
|
elif self.send_get_body_as == "source":
|
|
|
|
|
if params is None:
|
|
|
|
|
params = {}
|
|
|
|
|
params["source"] = body
|
|
|
|
|
body = None
|
|
|
|
|
|
|
|
|
|
if body is not None:
|
|
|
|
|
try:
|
|
|
|
|
body = body.encode("utf-8", "surrogatepass")
|
|
|
|
|
except (UnicodeDecodeError, AttributeError):
|
|
|
|
|
# bytes/str - no need to re-encode
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
ignore = ()
|
|
|
|
|
timeout = None
|
|
|
|
|
if params:
|
|
|
|
|
timeout = params.pop("request_timeout", None)
|
2022-10-25 10:41:29 -07:00
|
|
|
if not timeout:
|
|
|
|
|
timeout = params.pop("timeout", None)
|
2020-05-13 13:21:42 -05:00
|
|
|
ignore = params.pop("ignore", ())
|
|
|
|
|
if isinstance(ignore, int):
|
|
|
|
|
ignore = (ignore,)
|
|
|
|
|
|
2021-08-17 12:02:41 +05:30
|
|
|
return method, params, body, ignore, timeout
|