Skip to content

Commit d3c9138

Browse files
authored
Merge pull request #209 from StijnCaerts/#208-index-template
Index templates
2 parents f34e249 + 3b572e9 commit d3c9138

File tree

9 files changed

+171
-13
lines changed

9 files changed

+171
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Added
1111

12+
- use index templates for Collection and Item indices [#208](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/discussions/208)
1213
- 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)
1314

1415
### Changed

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,12 @@ make ingest
116116

117117
## Elasticsearch Mappings
118118

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

121122

122123
## Managing Elasticsearch Indices
124+
### Snapshots
123125

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

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

220222
Voila! You have a copy of the collection now that has a resource URI (`/collections/my-collection-copy`) and can be
221223
correctly queried by collection name.
224+
225+
### Reindexing
226+
This section covers how to reindex documents stored in Elasticsearch/OpenSearch.
227+
A reindex operation might be useful to apply changes to documents or to correct dynamically generated mappings.
228+
229+
The index templates will make sure that manually created indices will also have the correct mappings and settings.
230+
231+
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.
232+
233+
```shell
234+
curl -X "POST" "http://localhost:9200/_reindex" \
235+
-H 'Content-Type: application/json' \
236+
-d $'{
237+
"source": {
238+
"index": "items_my-collection-000001"
239+
},
240+
"dest": {
241+
"index": "items_my-collection-000002"
242+
},
243+
"script": {
244+
"source": "ctx._source.id = ctx._source.id.toLowerCase()",
245+
"lang": "painless"
246+
}
247+
}'
248+
```
249+
250+
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`.
251+
```shell
252+
curl -X "POST" "http://localhost:9200/_aliases" \
253+
-h 'Content-Type: application/json' \
254+
-d $'{
255+
"actions": [
256+
{
257+
"remove": {
258+
"index": "*",
259+
"alias": "items_my-collection"
260+
}
261+
},
262+
{
263+
"add": {
264+
"index": "items_my-collection-000002",
265+
"alias": "items_my-collection"
266+
}
267+
}
268+
]
269+
}'
270+
```
271+
272+
The modified Items with lowercase identifiers will now be visible to users accessing `my-collection` in the STAC API.

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from stac_fastapi.elasticsearch.database_logic import (
1717
DatabaseLogic,
1818
create_collection_index,
19+
create_index_templates,
1920
)
2021
from stac_fastapi.extensions.core import (
2122
ContextExtension,
@@ -78,6 +79,7 @@
7879

7980
@app.on_event("startup")
8081
async def _startup_event() -> None:
82+
await create_index_templates()
8183
await create_collection_index()
8284

8385

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,36 @@ def indices(collection_ids: Optional[List[str]]) -> str:
171171
return ",".join([index_by_collection_id(c) for c in collection_ids])
172172

173173

174+
async def create_index_templates() -> None:
175+
"""
176+
Create index templates for the Collection and Item indices.
177+
178+
Returns:
179+
None
180+
181+
"""
182+
client = AsyncElasticsearchSettings().create_client
183+
await client.indices.put_template(
184+
name=f"template_{COLLECTIONS_INDEX}",
185+
body={
186+
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
187+
"mappings": ES_COLLECTIONS_MAPPINGS,
188+
},
189+
)
190+
await client.indices.put_template(
191+
name=f"template_{ITEMS_INDEX_PREFIX}",
192+
body={
193+
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
194+
"settings": ES_ITEMS_SETTINGS,
195+
"mappings": ES_ITEMS_MAPPINGS,
196+
},
197+
)
198+
await client.close()
199+
200+
174201
async def create_collection_index() -> None:
175202
"""
176-
Create the index for a Collection.
203+
Create the index for a Collection. The settings of the index template will be used implicitly.
177204
178205
Returns:
179206
None
@@ -184,14 +211,13 @@ async def create_collection_index() -> None:
184211
await client.options(ignore_status=400).indices.create(
185212
index=f"{COLLECTIONS_INDEX}-000001",
186213
aliases={COLLECTIONS_INDEX: {}},
187-
mappings=ES_COLLECTIONS_MAPPINGS,
188214
)
189215
await client.close()
190216

191217

192218
async def create_item_index(collection_id: str):
193219
"""
194-
Create the index for Items.
220+
Create the index for Items. The settings of the index template will be used implicitly.
195221
196222
Args:
197223
collection_id (str): Collection identifier.
@@ -206,8 +232,6 @@ async def create_item_index(collection_id: str):
206232
await client.options(ignore_status=400).indices.create(
207233
index=f"{index_by_collection_id(collection_id)}-000001",
208234
aliases={index_name: {}},
209-
mappings=ES_ITEMS_MAPPINGS,
210-
settings=ES_ITEMS_SETTINGS,
211235
)
212236
await client.close()
213237

stac_fastapi/opensearch/stac_fastapi/opensearch/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from stac_fastapi.opensearch.database_logic import (
2626
DatabaseLogic,
2727
create_collection_index,
28+
create_index_templates,
2829
)
2930

3031
settings = OpensearchSettings()
@@ -78,6 +79,7 @@
7879

7980
@app.on_event("startup")
8081
async def _startup_event() -> None:
82+
await create_index_templates()
8183
await create_collection_index()
8284

8385

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,44 @@ def indices(collection_ids: Optional[List[str]]) -> str:
173173
return ",".join([index_by_collection_id(c) for c in collection_ids])
174174

175175

176+
async def create_index_templates() -> None:
177+
"""
178+
Create index templates for the Collection and Item indices.
179+
180+
Returns:
181+
None
182+
183+
"""
184+
client = AsyncSearchSettings().create_client
185+
await client.indices.put_template(
186+
name=f"template_{COLLECTIONS_INDEX}",
187+
body={
188+
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
189+
"mappings": ES_COLLECTIONS_MAPPINGS,
190+
},
191+
)
192+
await client.indices.put_template(
193+
name=f"template_{ITEMS_INDEX_PREFIX}",
194+
body={
195+
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
196+
"settings": ES_ITEMS_SETTINGS,
197+
"mappings": ES_ITEMS_MAPPINGS,
198+
},
199+
)
200+
await client.close()
201+
202+
176203
async def create_collection_index() -> None:
177204
"""
178-
Create the index for a Collection.
205+
Create the index for a Collection. The settings of the index template will be used implicitly.
179206
180207
Returns:
181208
None
182209
183210
"""
184211
client = AsyncSearchSettings().create_client
185212

186-
search_body = {
187-
"mappings": ES_COLLECTIONS_MAPPINGS,
213+
search_body: Dict[str, Any] = {
188214
"aliases": {COLLECTIONS_INDEX: {}},
189215
}
190216

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

204230
async def create_item_index(collection_id: str):
205231
"""
206-
Create the index for Items.
232+
Create the index for Items. The settings of the index template will be used implicitly.
207233
208234
Args:
209235
collection_id (str): Collection identifier.
@@ -214,10 +240,8 @@ async def create_item_index(collection_id: str):
214240
"""
215241
client = AsyncSearchSettings().create_client
216242
index_name = index_by_collection_id(collection_id)
217-
search_body = {
243+
search_body: Dict[str, Any] = {
218244
"aliases": {index_name: {}},
219-
"mappings": ES_ITEMS_MAPPINGS,
220-
"settings": ES_ITEMS_SETTINGS,
221245
}
222246

223247
try:

stac_fastapi/tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from stac_fastapi.opensearch.database_logic import (
2424
DatabaseLogic,
2525
create_collection_index,
26+
create_index_templates,
2627
)
2728
else:
2829
from stac_fastapi.elasticsearch.config import (
@@ -32,6 +33,7 @@
3233
from stac_fastapi.elasticsearch.database_logic import (
3334
DatabaseLogic,
3435
create_collection_index,
36+
create_index_templates,
3537
)
3638

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

216218
@pytest_asyncio.fixture(scope="session")
217219
async def app_client(app):
220+
await create_index_templates()
218221
await create_collection_index()
219222

220223
async with AsyncClient(app=app, base_url="http://test-server") as c:

stac_fastapi/tests/database/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import uuid
3+
from copy import deepcopy
4+
5+
import pytest
6+
7+
from ..conftest import MockRequest, database
8+
9+
if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch":
10+
from stac_fastapi.opensearch.database_logic import (
11+
COLLECTIONS_INDEX,
12+
ES_COLLECTIONS_MAPPINGS,
13+
ES_ITEMS_MAPPINGS,
14+
index_by_collection_id,
15+
)
16+
else:
17+
from stac_fastapi.elasticsearch.database_logic import (
18+
COLLECTIONS_INDEX,
19+
ES_COLLECTIONS_MAPPINGS,
20+
ES_ITEMS_MAPPINGS,
21+
index_by_collection_id,
22+
)
23+
24+
25+
@pytest.mark.asyncio
26+
async def test_index_mapping_collections(ctx):
27+
response = await database.client.indices.get_mapping(index=COLLECTIONS_INDEX)
28+
if not isinstance(response, dict):
29+
response = response.body
30+
actual_mappings = next(iter(response.values()))["mappings"]
31+
assert (
32+
actual_mappings["dynamic_templates"]
33+
== ES_COLLECTIONS_MAPPINGS["dynamic_templates"]
34+
)
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_index_mapping_items(ctx, txn_client):
39+
collection = deepcopy(ctx.collection)
40+
collection["id"] = str(uuid.uuid4())
41+
await txn_client.create_collection(collection, request=MockRequest)
42+
response = await database.client.indices.get_mapping(
43+
index=index_by_collection_id(collection["id"])
44+
)
45+
if not isinstance(response, dict):
46+
response = response.body
47+
actual_mappings = next(iter(response.values()))["mappings"]
48+
assert (
49+
actual_mappings["dynamic_templates"] == ES_ITEMS_MAPPINGS["dynamic_templates"]
50+
)
51+
await txn_client.delete_collection(collection["id"])

0 commit comments

Comments
 (0)