Rename module to opensearchpy

To avoid conflict with an existing package by name 'opensearch' being
present

Signed-off-by: Rushi Agrawal <[email protected]>
This commit is contained in:
Rushi Agrawal
2021-09-16 21:23:38 +05:30
parent f4891be3c3
commit ef0c23c0e4
129 changed files with 237 additions and 237 deletions
@@ -0,0 +1,25 @@
# 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.
#
# 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.
@@ -0,0 +1,65 @@
# 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.
#
# 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.
import asyncio
import pytest
import opensearchpy
from opensearchpy.helpers.test import CA_CERTS, OPENSEARCH_URL
from ...utils import wipe_cluster
pytestmark = pytest.mark.asyncio
@pytest.fixture(scope="function")
async def async_client():
client = None
try:
if not hasattr(opensearchpy, "AsyncOpenSearch"):
pytest.skip("test requires 'AsyncOpenSearch'")
kw = {"timeout": 3, "ca_certs": CA_CERTS}
client = opensearchpy.AsyncOpenSearch(OPENSEARCH_URL, **kw)
# wait for yellow status
for _ in range(100):
try:
await client.cluster.health(wait_for_status="yellow")
break
except ConnectionError:
await asyncio.sleep(0.1)
else:
# timeout
pytest.skip("OpenSearch failed to start.")
yield client
finally:
if client:
wipe_cluster(client)
await client.close()
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# 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.
#
# 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.
from __future__ import unicode_literals
import pytest
pytestmark = pytest.mark.asyncio
class TestUnicode:
async def test_indices_analyze(self, async_client):
await async_client.indices.analyze(body='{"text": "привет"}')
class TestBulk:
async def test_bulk_works_with_string_body(self, async_client):
docs = '{ "index" : { "_index" : "bulk_test_index", "_id" : "1" } }\n{"answer": 42}'
response = await async_client.bulk(body=docs)
assert response["errors"] is False
assert len(response["items"]) == 1
async def test_bulk_works_with_bytestring_body(self, async_client):
docs = b'{ "index" : { "_index" : "bulk_test_index", "_id" : "2" } }\n{"answer": 42}'
response = await async_client.bulk(body=docs)
assert response["errors"] is False
assert len(response["items"]) == 1
class TestYarlMissing:
async def test_aiohttp_connection_works_without_yarl(
self, async_client, monkeypatch
):
# This is a defensive test case for if aiohttp suddenly stops using yarl.
from opensearchpy._async import http_aiohttp
monkeypatch.setattr(http_aiohttp, "yarl", False)
resp = await async_client.info(pretty=True)
assert isinstance(resp, dict)
@@ -0,0 +1,901 @@
# 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.
#
# 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.
# Licensed to Elasticsearch B.V.under one or more agreements.
# Elasticsearch B.V.licenses this file to you under the Apache 2.0 License.
# See the LICENSE file in the project root for more information
import asyncio
import pytest
from mock import MagicMock, patch
from opensearchpy import TransportError, helpers
from opensearchpy.helpers import ScanError
pytestmark = pytest.mark.asyncio
class AsyncMock(MagicMock):
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)
def __await__(self):
return self().__await__()
class FailingBulkClient(object):
def __init__(
self, client, fail_at=(2,), fail_with=TransportError(599, "Error!", {})
):
self.client = client
self._called = 0
self._fail_at = fail_at
self.transport = client.transport
self._fail_with = fail_with
async def bulk(self, *args, **kwargs):
self._called += 1
if self._called in self._fail_at:
raise self._fail_with
return await self.client.bulk(*args, **kwargs)
class TestStreamingBulk(object):
async def test_actions_remain_unchanged(self, async_client):
actions = [{"_id": 1}, {"_id": 2}]
async for ok, item in helpers.async_streaming_bulk(
async_client, actions, index="test-index"
):
assert ok
assert [{"_id": 1}, {"_id": 2}] == actions
async def test_all_documents_get_inserted(self, async_client):
docs = [{"answer": x, "_id": x} for x in range(100)]
async for ok, item in helpers.async_streaming_bulk(
async_client, docs, index="test-index", refresh=True
):
assert ok
assert 100 == (await async_client.count(index="test-index"))["count"]
assert {"answer": 42} == (await async_client.get(index="test-index", id=42))[
"_source"
]
async def test_documents_data_types(self, async_client):
async def async_gen():
for x in range(100):
await asyncio.sleep(0)
yield {"answer": x, "_id": x}
def sync_gen():
for x in range(100):
yield {"answer": x, "_id": x}
async for ok, item in helpers.async_streaming_bulk(
async_client, async_gen(), index="test-index", refresh=True
):
assert ok
assert 100 == (await async_client.count(index="test-index"))["count"]
assert {"answer": 42} == (await async_client.get(index="test-index", id=42))[
"_source"
]
await async_client.delete_by_query(
index="test-index", body={"query": {"match_all": {}}}
)
async for ok, item in helpers.async_streaming_bulk(
async_client, sync_gen(), index="test-index", refresh=True
):
assert ok
assert 100 == (await async_client.count(index="test-index"))["count"]
assert {"answer": 42} == (await async_client.get(index="test-index", id=42))[
"_source"
]
async def test_all_errors_from_chunk_are_raised_on_failure(self, async_client):
await async_client.indices.create(
"i",
{
"mappings": {"properties": {"a": {"type": "integer"}}},
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
},
)
await async_client.cluster.health(wait_for_status="yellow")
try:
async for ok, item in helpers.async_streaming_bulk(
async_client, [{"a": "b"}, {"a": "c"}], index="i", raise_on_error=True
):
assert ok
except helpers.BulkIndexError as e:
assert 2 == len(e.errors)
else:
assert False, "exception should have been raised"
async def test_different_op_types(self, async_client):
await async_client.index(index="i", id=45, body={})
await async_client.index(index="i", id=42, body={})
docs = [
{"_index": "i", "_id": 47, "f": "v"},
{"_op_type": "delete", "_index": "i", "_id": 45},
{"_op_type": "update", "_index": "i", "_id": 42, "doc": {"answer": 42}},
]
async for ok, item in helpers.async_streaming_bulk(async_client, docs):
assert ok
assert not await async_client.exists(index="i", id=45)
assert {"answer": 42} == (await async_client.get(index="i", id=42))["_source"]
assert {"f": "v"} == (await async_client.get(index="i", id=47))["_source"]
async def test_transport_error_can_becaught(self, async_client):
failing_client = FailingBulkClient(async_client)
docs = [
{"_index": "i", "_id": 47, "f": "v"},
{"_index": "i", "_id": 45, "f": "v"},
{"_index": "i", "_id": 42, "f": "v"},
]
results = [
x
async for x in helpers.async_streaming_bulk(
failing_client,
docs,
raise_on_exception=False,
raise_on_error=False,
chunk_size=1,
)
]
assert 3 == len(results)
assert [True, False, True] == [r[0] for r in results]
exc = results[1][1]["index"].pop("exception")
assert isinstance(exc, TransportError)
assert 599 == exc.status_code
assert {
"index": {
"_index": "i",
"_id": 45,
"data": {"f": "v"},
"error": "TransportError(599, 'Error!')",
"status": 599,
}
} == results[1][1]
async def test_rejected_documents_are_retried(self, async_client):
failing_client = FailingBulkClient(
async_client, fail_with=TransportError(429, "Rejected!", {})
)
docs = [
{"_index": "i", "_id": 47, "f": "v"},
{"_index": "i", "_id": 45, "f": "v"},
{"_index": "i", "_id": 42, "f": "v"},
]
results = [
x
async for x in helpers.async_streaming_bulk(
failing_client,
docs,
raise_on_exception=False,
raise_on_error=False,
chunk_size=1,
max_retries=1,
initial_backoff=0,
)
]
assert 3 == len(results)
assert [True, True, True] == [r[0] for r in results]
await async_client.indices.refresh(index="i")
res = await async_client.search(index="i")
assert {"value": 3, "relation": "eq"} == res["hits"]["total"]
assert 4 == failing_client._called
async def test_rejected_documents_are_retried_at_most_max_retries_times(
self, async_client
):
failing_client = FailingBulkClient(
async_client, fail_at=(1, 2), fail_with=TransportError(429, "Rejected!", {})
)
docs = [
{"_index": "i", "_id": 47, "f": "v"},
{"_index": "i", "_id": 45, "f": "v"},
{"_index": "i", "_id": 42, "f": "v"},
]
results = [
x
async for x in helpers.async_streaming_bulk(
failing_client,
docs,
raise_on_exception=False,
raise_on_error=False,
chunk_size=1,
max_retries=1,
initial_backoff=0,
)
]
assert 3 == len(results)
assert [False, True, True] == [r[0] for r in results]
await async_client.indices.refresh(index="i")
res = await async_client.search(index="i")
assert {"value": 2, "relation": "eq"} == res["hits"]["total"]
assert 4 == failing_client._called
async def test_transport_error_is_raised_with_max_retries(self, async_client):
failing_client = FailingBulkClient(
async_client,
fail_at=(1, 2, 3, 4),
fail_with=TransportError(429, "Rejected!", {}),
)
async def streaming_bulk():
results = [
x
async for x in helpers.async_streaming_bulk(
failing_client,
[{"a": 42}, {"a": 39}],
raise_on_exception=True,
max_retries=3,
initial_backoff=0,
)
]
return results
with pytest.raises(TransportError):
await streaming_bulk()
assert 4 == failing_client._called
class TestBulk(object):
async def test_bulk_works_with_single_item(self, async_client):
docs = [{"answer": 42, "_id": 1}]
success, failed = await helpers.async_bulk(
async_client, docs, index="test-index", refresh=True
)
assert 1 == success
assert not failed
assert 1 == (await async_client.count(index="test-index"))["count"]
assert {"answer": 42} == (await async_client.get(index="test-index", id=1))[
"_source"
]
async def test_all_documents_get_inserted(self, async_client):
docs = [{"answer": x, "_id": x} for x in range(100)]
success, failed = await helpers.async_bulk(
async_client, docs, index="test-index", refresh=True
)
assert 100 == success
assert not failed
assert 100 == (await async_client.count(index="test-index"))["count"]
assert {"answer": 42} == (await async_client.get(index="test-index", id=42))[
"_source"
]
async def test_stats_only_reports_numbers(self, async_client):
docs = [{"answer": x} for x in range(100)]
success, failed = await helpers.async_bulk(
async_client, docs, index="test-index", refresh=True, stats_only=True
)
assert 100 == success
assert 0 == failed
assert 100 == (await async_client.count(index="test-index"))["count"]
async def test_errors_are_reported_correctly(self, async_client):
await async_client.indices.create(
"i",
{
"mappings": {"properties": {"a": {"type": "integer"}}},
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
},
)
await async_client.cluster.health(wait_for_status="yellow")
success, failed = await helpers.async_bulk(
async_client,
[{"a": 42}, {"a": "c", "_id": 42}],
index="i",
raise_on_error=False,
)
assert 1 == success
assert 1 == len(failed)
error = failed[0]
assert "42" == error["index"]["_id"]
assert "i" == error["index"]["_index"]
print(error["index"]["error"])
assert "MapperParsingException" in repr(
error["index"]["error"]
) or "mapper_parsing_exception" in repr(error["index"]["error"])
async def test_error_is_raised(self, async_client):
await async_client.indices.create(
"i",
{
"mappings": {"properties": {"a": {"type": "integer"}}},
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
},
)
await async_client.cluster.health(wait_for_status="yellow")
with pytest.raises(helpers.BulkIndexError):
await helpers.async_bulk(async_client, [{"a": 42}, {"a": "c"}], index="i")
async def test_ignore_error_if_raised(self, async_client):
# ignore the status code 400 in tuple
await helpers.async_bulk(
async_client, [{"a": 42}, {"a": "c"}], index="i", ignore_status=(400,)
)
# ignore the status code 400 in list
await helpers.async_bulk(
async_client,
[{"a": 42}, {"a": "c"}],
index="i",
ignore_status=[
400,
],
)
# ignore the status code 400
await helpers.async_bulk(
async_client, [{"a": 42}, {"a": "c"}], index="i", ignore_status=400
)
# ignore only the status code in the `ignore_status` argument
with pytest.raises(helpers.BulkIndexError):
await helpers.async_bulk(
async_client, [{"a": 42}, {"a": "c"}], index="i", ignore_status=(444,)
)
# ignore transport error exception
failing_client = FailingBulkClient(async_client)
await helpers.async_bulk(
failing_client, [{"a": 42}], index="i", ignore_status=(599,)
)
async def test_errors_are_collected_properly(self, async_client):
await async_client.indices.create(
"i",
{
"mappings": {"properties": {"a": {"type": "integer"}}},
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
},
)
await async_client.cluster.health(wait_for_status="yellow")
success, failed = await helpers.async_bulk(
async_client,
[{"a": 42}, {"a": "c"}],
index="i",
stats_only=True,
raise_on_error=False,
)
assert 1 == success
assert 1 == failed
class MockScroll:
def __init__(self):
self.calls = []
async def __call__(self, *args, **kwargs):
self.calls.append((args, kwargs))
if len(self.calls) == 1:
return {
"_scroll_id": "dummy_id",
"_shards": {"successful": 4, "total": 5, "skipped": 0},
"hits": {"hits": [{"scroll_data": 42}]},
}
elif len(self.calls) == 2:
return {
"_scroll_id": "dummy_id",
"_shards": {"successful": 4, "total": 5, "skipped": 0},
"hits": {"hits": []},
}
else:
raise Exception("no more responses")
class MockResponse:
def __init__(self, resp):
self.resp = resp
async def __call__(self, *args, **kwargs):
return self.resp
def __await__(self):
return self().__await__()
@pytest.fixture(scope="function")
async def scan_teardown(async_client):
yield
await async_client.clear_scroll(scroll_id="_all")
class TestScan(object):
async def test_order_can_be_preserved(self, async_client, scan_teardown):
bulk = []
for x in range(100):
bulk.append({"index": {"_index": "test_index", "_id": x}})
bulk.append({"answer": x, "correct": x == 42})
await async_client.bulk(bulk, refresh=True)
docs = [
doc
async for doc in helpers.async_scan(
async_client,
index="test_index",
query={"sort": "answer"},
preserve_order=True,
)
]
assert 100 == len(docs)
assert list(map(str, range(100))) == list(d["_id"] for d in docs)
assert list(range(100)) == list(d["_source"]["answer"] for d in docs)
async def test_all_documents_are_read(self, async_client, scan_teardown):
bulk = []
for x in range(100):
bulk.append({"index": {"_index": "test_index", "_id": x}})
bulk.append({"answer": x, "correct": x == 42})
await async_client.bulk(bulk, refresh=True)
docs = [
x
async for x in helpers.async_scan(async_client, index="test_index", size=2)
]
assert 100 == len(docs)
assert set(map(str, range(100))) == set(d["_id"] for d in docs)
assert set(range(100)) == set(d["_source"]["answer"] for d in docs)
async def test_scroll_error(self, async_client, scan_teardown):
bulk = []
for x in range(4):
bulk.append({"index": {"_index": "test_index"}})
bulk.append({"value": x})
await async_client.bulk(bulk, refresh=True)
with patch.object(async_client, "scroll", MockScroll()):
data = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=False,
clear_scroll=False,
)
]
assert len(data) == 3
assert data[-1] == {"scroll_data": 42}
with patch.object(async_client, "scroll", MockScroll()):
with pytest.raises(ScanError):
data = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=True,
clear_scroll=False,
)
]
assert len(data) == 3
assert data[-1] == {"scroll_data": 42}
async def test_initial_search_error(self, async_client, scan_teardown):
with patch.object(async_client, "clear_scroll", new_callable=AsyncMock):
with patch.object(
async_client,
"search",
MockResponse(
{
"_scroll_id": "dummy_id",
"_shards": {"successful": 4, "total": 5, "skipped": 0},
"hits": {"hits": [{"search_data": 1}]},
}
),
):
with patch.object(async_client, "scroll", MockScroll()):
data = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=False,
)
]
assert data == [{"search_data": 1}, {"scroll_data": 42}]
with patch.object(
async_client,
"search",
MockResponse(
{
"_scroll_id": "dummy_id",
"_shards": {"successful": 4, "total": 5, "skipped": 0},
"hits": {"hits": [{"search_data": 1}]},
}
),
):
with patch.object(async_client, "scroll", MockScroll()) as mock_scroll:
with pytest.raises(ScanError):
data = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=True,
)
]
assert data == [{"search_data": 1}]
assert mock_scroll.calls == []
async def test_no_scroll_id_fast_route(self, async_client, scan_teardown):
with patch.object(async_client, "search", MockResponse({"no": "_scroll_id"})):
with patch.object(async_client, "scroll") as scroll_mock:
with patch.object(async_client, "clear_scroll") as clear_mock:
data = [
x
async for x in helpers.async_scan(
async_client, index="test_index"
)
]
assert data == []
scroll_mock.assert_not_called()
clear_mock.assert_not_called()
@patch("opensearchpy._async.helpers.logger")
async def test_logger(self, logger_mock, async_client, scan_teardown):
bulk = []
for x in range(4):
bulk.append({"index": {"_index": "test_index"}})
bulk.append({"value": x})
await async_client.bulk(bulk, refresh=True)
with patch.object(async_client, "scroll", MockScroll()):
_ = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=False,
clear_scroll=False,
)
]
logger_mock.warning.assert_called()
with patch.object(async_client, "scroll", MockScroll()):
try:
_ = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
size=2,
raise_on_error=True,
clear_scroll=False,
)
]
except ScanError:
pass
logger_mock.warning.assert_called_with(
"Scroll request has only succeeded on %d (+%d skipped) shards out of %d.",
4,
0,
5,
)
async def test_clear_scroll(self, async_client, scan_teardown):
bulk = []
for x in range(4):
bulk.append({"index": {"_index": "test_index"}})
bulk.append({"value": x})
await async_client.bulk(bulk, refresh=True)
with patch.object(
async_client, "clear_scroll", wraps=async_client.clear_scroll
) as spy:
_ = [
x
async for x in helpers.async_scan(
async_client, index="test_index", size=2
)
]
spy.assert_called_once()
spy.reset_mock()
_ = [
x
async for x in helpers.async_scan(
async_client, index="test_index", size=2, clear_scroll=True
)
]
spy.assert_called_once()
spy.reset_mock()
_ = [
x
async for x in helpers.async_scan(
async_client, index="test_index", size=2, clear_scroll=False
)
]
spy.assert_not_called()
@pytest.mark.parametrize(
"kwargs",
[
{"api_key": ("name", "value")},
{"http_auth": ("username", "password")},
{"headers": {"custom", "header"}},
],
)
async def test_scan_auth_kwargs_forwarded(
self, async_client, scan_teardown, kwargs
):
((key, val),) = kwargs.items()
with patch.object(
async_client,
"search",
return_value=MockResponse(
{
"_scroll_id": "scroll_id",
"_shards": {"successful": 5, "total": 5, "skipped": 0},
"hits": {"hits": [{"search_data": 1}]},
}
),
) as search_mock:
with patch.object(
async_client,
"scroll",
return_value=MockResponse(
{
"_scroll_id": "scroll_id",
"_shards": {"successful": 5, "total": 5, "skipped": 0},
"hits": {"hits": []},
}
),
) as scroll_mock:
with patch.object(
async_client, "clear_scroll", return_value=MockResponse({})
) as clear_mock:
data = [
x
async for x in helpers.async_scan(
async_client, index="test_index", **kwargs
)
]
assert data == [{"search_data": 1}]
for api_mock in (search_mock, scroll_mock, clear_mock):
assert api_mock.call_args[1][key] == val
async def test_scan_auth_kwargs_favor_scroll_kwargs_option(
self, async_client, scan_teardown
):
with patch.object(
async_client,
"search",
return_value=MockResponse(
{
"_scroll_id": "scroll_id",
"_shards": {"successful": 5, "total": 5, "skipped": 0},
"hits": {"hits": [{"search_data": 1}]},
}
),
):
with patch.object(
async_client,
"scroll",
return_value=MockResponse(
{
"_scroll_id": "scroll_id",
"_shards": {"successful": 5, "total": 5, "skipped": 0},
"hits": {"hits": []},
}
),
):
with patch.object(
async_client, "clear_scroll", return_value=MockResponse({})
):
data = [
x
async for x in helpers.async_scan(
async_client,
index="test_index",
headers={"not scroll": "kwargs"},
scroll_kwargs={
"headers": {"scroll": "kwargs"},
"sort": "asc",
},
)
]
assert data == [{"search_data": 1}]
# Assert that we see 'scroll_kwargs' options used instead of 'kwargs'
assert async_client.scroll.call_args[1]["headers"] == {
"scroll": "kwargs"
}
assert async_client.scroll.call_args[1]["sort"] == "asc"
@pytest.fixture(scope="function")
async def reindex_setup(async_client):
bulk = []
for x in range(100):
bulk.append({"index": {"_index": "test_index", "_id": x}})
bulk.append(
{
"answer": x,
"correct": x == 42,
"type": "answers" if x % 2 == 0 else "questions",
}
)
await async_client.bulk(bulk, refresh=True)
yield
class TestReindex(object):
async def test_reindex_passes_kwargs_to_scan_and_bulk(
self, async_client, reindex_setup
):
await helpers.async_reindex(
async_client,
"test_index",
"prod_index",
scan_kwargs={"q": "type:answers"},
bulk_kwargs={"refresh": True},
)
assert await async_client.indices.exists("prod_index")
assert (
50
== (await async_client.count(index="prod_index", q="type:answers"))["count"]
)
assert {"answer": 42, "correct": True, "type": "answers"} == (
await async_client.get(index="prod_index", id=42)
)["_source"]
async def test_reindex_accepts_a_query(self, async_client, reindex_setup):
await helpers.async_reindex(
async_client,
"test_index",
"prod_index",
query={"query": {"bool": {"filter": {"term": {"type": "answers"}}}}},
)
await async_client.indices.refresh()
assert await async_client.indices.exists("prod_index")
assert (
50
== (await async_client.count(index="prod_index", q="type:answers"))["count"]
)
assert {"answer": 42, "correct": True, "type": "answers"} == (
await async_client.get(index="prod_index", id=42)
)["_source"]
async def test_all_documents_get_moved(self, async_client, reindex_setup):
await helpers.async_reindex(async_client, "test_index", "prod_index")
await async_client.indices.refresh()
assert await async_client.indices.exists("prod_index")
assert (
50
== (await async_client.count(index="prod_index", q="type:questions"))[
"count"
]
)
assert (
50
== (await async_client.count(index="prod_index", q="type:answers"))["count"]
)
assert {"answer": 42, "correct": True, "type": "answers"} == (
await async_client.get(index="prod_index", id=42)
)["_source"]
@pytest.fixture(scope="function")
async def parent_reindex_setup(async_client):
body = {
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
"mappings": {
"properties": {
"question_answer": {
"type": "join",
"relations": {"question": "answer"},
}
}
},
}
await async_client.indices.create(index="test-index", body=body)
await async_client.indices.create(index="real-index", body=body)
await async_client.index(
index="test-index", id=42, body={"question_answer": "question"}
)
await async_client.index(
index="test-index",
id=47,
routing=42,
body={"some": "data", "question_answer": {"name": "answer", "parent": 42}},
)
await async_client.indices.refresh(index="test-index")
class TestParentChildReindex:
async def test_children_are_reindexed_correctly(
self, async_client, parent_reindex_setup
):
await helpers.async_reindex(async_client, "test-index", "real-index")
q = await async_client.get(index="real-index", id=42)
assert {
"_id": "42",
"_index": "real-index",
"_primary_term": 1,
"_seq_no": 0,
"_source": {"question_answer": "question"},
"_type": "_doc",
"_version": 1,
"found": True,
} == q
q = await async_client.get(index="test-index", id=47, routing=42)
assert {
"_routing": "42",
"_id": "47",
"_index": "test-index",
"_primary_term": 1,
"_seq_no": 1,
"_source": {
"some": "data",
"question_answer": {"name": "answer", "parent": 42},
},
"_type": "_doc",
"_version": 1,
"found": True,
} == q
@@ -0,0 +1,233 @@
# 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.
#
# 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.
"""
Dynamically generated set of TestCases based on set of yaml files decribing
some integration tests. These files are shared among all official OpenSearch
clients.
"""
import inspect
import warnings
import pytest
from opensearchpy import OpenSearchWarning
from opensearchpy.helpers.test import _get_version
from ...test_server.test_rest_api_spec import (
IMPLEMENTED_FEATURES,
PARAMS_RENAMES,
RUN_ASYNC_REST_API_TESTS,
YAML_TEST_SPECS,
YamlRunner,
)
pytestmark = pytest.mark.asyncio
OPENSEARCH_VERSION = None
async def await_if_coro(x):
if inspect.iscoroutine(x):
return await x
return x
class AsyncYamlRunner(YamlRunner):
async def setup(self):
# Pull skips from individual tests to not do unnecessary setup.
skip_code = []
for action in self._run_code:
assert len(action) == 1
action_type, _ = list(action.items())[0]
if action_type == "skip":
skip_code.append(action)
else:
break
if self._setup_code or skip_code:
self.section("setup")
if skip_code:
await self.run_code(skip_code)
if self._setup_code:
await self.run_code(self._setup_code)
async def teardown(self):
if self._teardown_code:
self.section("teardown")
await self.run_code(self._teardown_code)
async def opensearch_version(self):
global OPENSEARCH_VERSION
if OPENSEARCH_VERSION is None:
version_string = (await self.client.info())["version"]["number"]
if "." not in version_string:
return ()
version = version_string.strip().split(".")
OPENSEARCH_VERSION = tuple(int(v) if v.isdigit() else 999 for v in version)
return OPENSEARCH_VERSION
def section(self, name):
print(("=" * 10) + " " + name + " " + ("=" * 10))
async def run(self):
try:
await self.setup()
self.section("test")
await self.run_code(self._run_code)
finally:
try:
await self.teardown()
except Exception:
pass
async def run_code(self, test):
"""Execute an instruction based on it's type."""
for action in test:
assert len(action) == 1
action_type, action = list(action.items())[0]
print(action_type, action)
if hasattr(self, "run_" + action_type):
await await_if_coro(getattr(self, "run_" + action_type)(action))
else:
raise RuntimeError("Invalid action type %r" % (action_type,))
async def run_do(self, action):
api = self.client
headers = action.pop("headers", None)
catch = action.pop("catch", None)
warn = action.pop("warnings", ())
allowed_warnings = action.pop("allowed_warnings", ())
assert len(action) == 1
# Remove the x_pack_rest_user authentication
# if it's given via headers. We're already authenticated
# via the 'elastic' user.
if (
headers
and headers.get("Authorization", None)
== "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA=="
):
headers.pop("Authorization")
method, args = list(action.items())[0]
args["headers"] = headers
# locate api endpoint
for m in method.split("."):
assert hasattr(api, m)
api = getattr(api, m)
# some parameters had to be renamed to not clash with python builtins,
# compensate
for k in PARAMS_RENAMES:
if k in args:
args[PARAMS_RENAMES[k]] = args.pop(k)
# resolve vars
for k in args:
args[k] = self._resolve(args[k])
warnings.simplefilter("always", category=OpenSearchWarning)
with warnings.catch_warnings(record=True) as caught_warnings:
try:
self.last_response = await api(**args)
except Exception as e:
if not catch:
raise
self.run_catch(catch, e)
else:
if catch:
raise AssertionError(
"Failed to catch %r in %r." % (catch, self.last_response)
)
# Filter out warnings raised by other components.
caught_warnings = [
str(w.message)
for w in caught_warnings
if w.category == OpenSearchWarning
and str(w.message) not in allowed_warnings
]
# This warning can show up in many places but isn't accounted for
# in tests, so we remove it to make sure things pass.
include_type_name_warning = (
"[types removal] Using include_type_name in create index requests is deprecated. "
"The parameter will be removed in the next major version."
)
if (
include_type_name_warning in caught_warnings
and include_type_name_warning not in warn
):
caught_warnings.remove(include_type_name_warning)
# Sorting removes the issue with order raised. We only care about
# if all warnings are raised in the single API call.
if warn and sorted(warn) != sorted(caught_warnings):
raise AssertionError(
"Expected warnings not equal to actual warnings: expected=%r actual=%r"
% (warn, caught_warnings)
)
async def run_skip(self, skip):
if "features" in skip:
features = skip["features"]
if not isinstance(features, (tuple, list)):
features = [features]
for feature in features:
if feature in IMPLEMENTED_FEATURES:
continue
pytest.skip("feature '%s' is not supported" % feature)
if "version" in skip:
version, reason = skip["version"], skip["reason"]
if version == "all":
pytest.skip(reason)
min_version, max_version = version.split("-")
min_version = _get_version(min_version) or (0,)
max_version = _get_version(max_version) or (999,)
if min_version <= (await self.opensearch_version()) <= max_version:
pytest.skip(reason)
async def _feature_enabled(self, name):
return False
@pytest.fixture(scope="function")
def async_runner(async_client):
return AsyncYamlRunner(async_client)
if RUN_ASYNC_REST_API_TESTS:
@pytest.mark.parametrize("test_spec", YAML_TEST_SPECS)
async def test_rest_api_spec(test_spec, async_runner):
if test_spec.get("skip", False):
pytest.skip("Manually skipped in 'SKIP_TESTS'")
async_runner.use_spec(test_spec)
await async_runner.run()