Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Index templates #209

Merged
merged 7 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- use index templates for Collection and Item indices [#208](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/discussions/208)
- Added API `title`, `version`, and `description` parameters from environment variables `STAC_FASTAPI_TITLE`, `STAC_FASTAPI_VERSION` and `STAC_FASTAPI_DESCRIPTION`, respectively. [#207](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/207)

### Changed
Expand Down
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ make ingest

## Elasticsearch Mappings

Mappings apply to search index, not source.
Mappings apply to search index, not source. The mappings are stored in index templates on application startup.
These templates will be used implicitly when creating new Collection and Item indices.


## Managing Elasticsearch Indices
### Snapshots

This section covers how to create a snapshot repository and then create and restore snapshots with this.

Expand Down Expand Up @@ -219,3 +221,52 @@ curl -X "POST" "http://localhost:8080/collections" \

Voila! You have a copy of the collection now that has a resource URI (`/collections/my-collection-copy`) and can be
correctly queried by collection name.

### Reindexing
This section covers how to reindex documents stored in Elasticsearch/OpenSearch.
A reindex operation might be useful to apply changes to documents or to correct dynamically generated mappings.

The index templates will make sure that manually created indices will also have the correct mappings and settings.

In this example, we will make a copy of an existing Item index `items_my-collection-000001` but change the Item identifier to be lowercase.

```shell
curl -X "POST" "http://localhost:9200/_reindex" \
-H 'Content-Type: application/json' \
-d $'{
"source": {
"index": "items_my-collection-000001"
},
"dest": {
"index": "items_my-collection-000002"
},
"script": {
"source": "ctx._source.id = ctx._source.id.toLowerCase()",
"lang": "painless"
}
}'
```

If we are happy with the data in the newly created index, we can move the alias `items_my-collection` to the new index `items_my-collection-000002`.
```shell
curl -X "POST" "http://localhost:9200/_aliases" \
-h 'Content-Type: application/json' \
-d $'{
"actions": [
{
"remove": {
"index": "*",
"alias": "items_my-collection"
}
},
{
"add": {
"index": "items_my-collection-000002",
"alias": "items_my-collection"
}
}
]
}'
```

The modified Items with lowercase identifiers will now be visible to users accessing `my-collection` in the STAC API.
2 changes: 2 additions & 0 deletions stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from stac_fastapi.elasticsearch.database_logic import (
DatabaseLogic,
create_collection_index,
create_index_templates,
)
from stac_fastapi.extensions.core import (
ContextExtension,
Expand Down Expand Up @@ -78,6 +79,7 @@

@app.on_event("startup")
async def _startup_event() -> None:
await create_index_templates()
await create_collection_index()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,36 @@ def indices(collection_ids: Optional[List[str]]) -> str:
return ",".join([index_by_collection_id(c) for c in collection_ids])


async def create_index_templates() -> None:
"""
Create index templates for the Collection and Item indices.

Returns:
None

"""
client = AsyncElasticsearchSettings().create_client
await client.indices.put_template(
name=f"template_{COLLECTIONS_INDEX}",
body={
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
"mappings": ES_COLLECTIONS_MAPPINGS,
},
)
await client.indices.put_template(
name=f"template_{ITEMS_INDEX_PREFIX}",
body={
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
"settings": ES_ITEMS_SETTINGS,
"mappings": ES_ITEMS_MAPPINGS,
},
)
await client.close()


async def create_collection_index() -> None:
"""
Create the index for a Collection.
Create the index for a Collection. The settings of the index template will be used implicitly.

Returns:
None
Expand All @@ -184,14 +211,13 @@ async def create_collection_index() -> None:
await client.options(ignore_status=400).indices.create(
index=f"{COLLECTIONS_INDEX}-000001",
aliases={COLLECTIONS_INDEX: {}},
mappings=ES_COLLECTIONS_MAPPINGS,
)
await client.close()


async def create_item_index(collection_id: str):
"""
Create the index for Items.
Create the index for Items. The settings of the index template will be used implicitly.

Args:
collection_id (str): Collection identifier.
Expand All @@ -206,8 +232,6 @@ async def create_item_index(collection_id: str):
await client.options(ignore_status=400).indices.create(
index=f"{index_by_collection_id(collection_id)}-000001",
aliases={index_name: {}},
mappings=ES_ITEMS_MAPPINGS,
settings=ES_ITEMS_SETTINGS,
)
await client.close()

Expand Down
2 changes: 2 additions & 0 deletions stac_fastapi/opensearch/stac_fastapi/opensearch/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from stac_fastapi.opensearch.database_logic import (
DatabaseLogic,
create_collection_index,
create_index_templates,
)

settings = OpensearchSettings()
Expand Down Expand Up @@ -78,6 +79,7 @@

@app.on_event("startup")
async def _startup_event() -> None:
await create_index_templates()
await create_collection_index()


Expand Down
38 changes: 31 additions & 7 deletions stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,44 @@ def indices(collection_ids: Optional[List[str]]) -> str:
return ",".join([index_by_collection_id(c) for c in collection_ids])


async def create_index_templates() -> None:
"""
Create index templates for the Collection and Item indices.

Returns:
None

"""
client = AsyncSearchSettings().create_client
await client.indices.put_template(
name=f"template_{COLLECTIONS_INDEX}",
body={
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
"mappings": ES_COLLECTIONS_MAPPINGS,
},
)
await client.indices.put_template(
name=f"template_{ITEMS_INDEX_PREFIX}",
body={
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
"settings": ES_ITEMS_SETTINGS,
"mappings": ES_ITEMS_MAPPINGS,
},
)
await client.close()


async def create_collection_index() -> None:
"""
Create the index for a Collection.
Create the index for a Collection. The settings of the index template will be used implicitly.

Returns:
None

"""
client = AsyncSearchSettings().create_client

search_body = {
"mappings": ES_COLLECTIONS_MAPPINGS,
search_body: Dict[str, Any] = {
"aliases": {COLLECTIONS_INDEX: {}},
}

Expand All @@ -203,7 +229,7 @@ async def create_collection_index() -> None:

async def create_item_index(collection_id: str):
"""
Create the index for Items.
Create the index for Items. The settings of the index template will be used implicitly.

Args:
collection_id (str): Collection identifier.
Expand All @@ -214,10 +240,8 @@ async def create_item_index(collection_id: str):
"""
client = AsyncSearchSettings().create_client
index_name = index_by_collection_id(collection_id)
search_body = {
search_body: Dict[str, Any] = {
"aliases": {index_name: {}},
"mappings": ES_ITEMS_MAPPINGS,
"settings": ES_ITEMS_SETTINGS,
}

try:
Expand Down
3 changes: 3 additions & 0 deletions stac_fastapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from stac_fastapi.opensearch.database_logic import (
DatabaseLogic,
create_collection_index,
create_index_templates,
)
else:
from stac_fastapi.elasticsearch.config import (
Expand All @@ -32,6 +33,7 @@
from stac_fastapi.elasticsearch.database_logic import (
DatabaseLogic,
create_collection_index,
create_index_templates,
)

from stac_fastapi.extensions.core import ( # FieldsExtension,
Expand Down Expand Up @@ -215,6 +217,7 @@ async def app():

@pytest_asyncio.fixture(scope="session")
async def app_client(app):
await create_index_templates()
await create_collection_index()

async with AsyncClient(app=app, base_url="http://test-server") as c:
Expand Down
Empty file.
51 changes: 51 additions & 0 deletions stac_fastapi/tests/database/test_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
import uuid
from copy import deepcopy

import pytest

from ..conftest import MockRequest, database

if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch":
from stac_fastapi.opensearch.database_logic import (
COLLECTIONS_INDEX,
ES_COLLECTIONS_MAPPINGS,
ES_ITEMS_MAPPINGS,
index_by_collection_id,
)
else:
from stac_fastapi.elasticsearch.database_logic import (
COLLECTIONS_INDEX,
ES_COLLECTIONS_MAPPINGS,
ES_ITEMS_MAPPINGS,
index_by_collection_id,
)


@pytest.mark.asyncio
async def test_index_mapping_collections(ctx):
response = await database.client.indices.get_mapping(index=COLLECTIONS_INDEX)
if not isinstance(response, dict):
response = response.body
actual_mappings = next(iter(response.values()))["mappings"]
assert (
actual_mappings["dynamic_templates"]
== ES_COLLECTIONS_MAPPINGS["dynamic_templates"]
)


@pytest.mark.asyncio
async def test_index_mapping_items(ctx, txn_client):
collection = deepcopy(ctx.collection)
collection["id"] = str(uuid.uuid4())
await txn_client.create_collection(collection, request=MockRequest)
response = await database.client.indices.get_mapping(
index=index_by_collection_id(collection["id"])
)
if not isinstance(response, dict):
response = response.body
actual_mappings = next(iter(response.values()))["mappings"]
assert (
actual_mappings["dynamic_templates"] == ES_ITEMS_MAPPINGS["dynamic_templates"]
)
await txn_client.delete_collection(collection["id"])