Skip to content

Commit

Permalink
feat: add indices recreation possibility for linked-data-work and lin…
Browse files Browse the repository at this point in the history
…ked-data-authority (#643)
  • Loading branch information
askhat-abishev authored Aug 19, 2024
1 parent 1306d13 commit fa2ca56
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 23 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Provides `consortium-search v1.2`

### Features
* Implement indices recreation of linked-data-work and linked-data-authority ([MSEARCH-820](https://issues.folio.org/browse/MSEARCH-820))
* Extension of mod-search consortium items/holdings API ([MSEARCH-788](https://issues.folio.org/browse/MSEARCH-788))
* Create location index and process location events ([MSEARCH-703](https://issues.folio.org/browse/MSEARCH-703))
* Implement reindexing of locations ([MSEARCH-702](https://issues.folio.org/browse/MSEARCH-702))
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,13 @@ x-okapi-token: [JWT_TOKEN]
}
```

* `resourceName` parameter is optional and equal to `instance` by default. Possible values: `instance`, `authority`, `locations`
Please note that `locations` reindex is synchronous
* `resourceName` parameter is optional and equal to `instance` by default. Possible values: `instance`, `authority`, `locations`,
`linked-data-work`, `linked-data-authority`. Please note that `locations` reindex is synchronous.
* `recreateIndex` parameter is optional and equal to `false` by default. If it is equal to `true` then mod-search
will drop existing indices for tenant and resource, creating them again. Executing request with this parameter
equal to `true` in query will erase all the tenant data in mod-search.
* Please note that for `linked-data-work` and `linked-data-authority` resources the endpoint is used only for index recreation
purpose and actual reindex operation is triggered through mod-linked-data.

### Monitoring reindex process

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/folio/search/service/IndexService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static java.lang.Boolean.TRUE;
import static org.folio.search.utils.SearchUtils.INSTANCE_RESOURCE;
import static org.folio.search.utils.SearchUtils.LINKED_DATA_AUTHORITY_RESOURCE;
import static org.folio.search.utils.SearchUtils.LINKED_DATA_WORK_RESOURCE;
import static org.folio.search.utils.SearchUtils.LOCATION_RESOURCE;
import static org.springframework.web.util.UriComponentsBuilder.fromUriString;

Expand Down Expand Up @@ -156,6 +158,8 @@ && notConsortiumMemberTenant(tenantId)) {

if (LOCATION_RESOURCE.equals(resource)) {
return reindexInventoryLocations(tenantId, resources);
} else if (isLinkedDataResource(resource)) {
return new ReindexJob();
} else {
return reindexInventoryAsync(resource);
}
Expand Down Expand Up @@ -288,4 +292,8 @@ private boolean notConsortiumMemberTenant(String tenantId) {
private static String normalizeResourceName(String url) {
return url.replace("_", "-");
}

private boolean isLinkedDataResource(String resource) {
return LINKED_DATA_WORK_RESOURCE.equals(resource) || LINKED_DATA_AUTHORITY_RESOURCE.equals(resource);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/model/linked_data_authority.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "linked-data-authority",
"eventBodyJavaClass": "org.folio.search.domain.dto.LinkedDataAuthority",
"reindexSupported": false,
"reindexSupported": true,
"fields": {
"id": {
"index": "keyword"
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/model/linked_data_work.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "linked-data-work",
"eventBodyJavaClass": "org.folio.search.domain.dto.LinkedDataWork",
"reindexSupported": true,
"languageSourcePaths": [ "$.languages" ],
"fields": {
"id": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ properties:
- instance
- authority
- location
- linked-data-work
- linked-data-authority
indexSettings:
description: Index settings to apply for index
$ref: "../../schemas/entity/indexSettings.yaml"
95 changes: 75 additions & 20 deletions src/test/java/org/folio/search/controller/IndexManagementIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,32 @@
import static org.awaitility.Durations.FIVE_SECONDS;
import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.AUTHORITY;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.LINKED_DATA_WORK;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.LOCATION;
import static org.folio.search.utils.SearchUtils.CAMPUS_RESOURCE;
import static org.folio.search.utils.SearchUtils.INSTITUTION_RESOURCE;
import static org.folio.search.utils.SearchUtils.LIBRARY_RESOURCE;
import static org.folio.search.utils.SearchUtils.LINKED_DATA_WORK_RESOURCE;
import static org.folio.search.utils.SearchUtils.LOCATION_RESOURCE;
import static org.folio.search.utils.SearchUtils.getResourceName;
import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID;
import static org.folio.search.utils.TestUtils.asJsonString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.stream.Stream;
import org.folio.search.domain.dto.Authority;
import org.folio.search.domain.dto.IndexDynamicSettings;
import org.folio.search.domain.dto.Instance;
import org.folio.search.domain.dto.ReindexRequest;
import org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum;
import org.folio.search.domain.dto.UpdateIndexDynamicSettingsRequest;
import org.folio.search.support.base.ApiEndpoints;
import org.folio.search.support.base.BaseIntegrationTest;
Expand All @@ -31,7 +39,12 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.RequestBuilder;

@IntegrationTest
class IndexManagementIT extends BaseIntegrationTest {
Expand Down Expand Up @@ -69,11 +82,7 @@ void runReindex_positive_instance() throws Exception {

@Test
void runReindex_positive_authority() throws Exception {
var request = post(ApiEndpoints.reindexPath())
.content(asJsonString(new ReindexRequest().resourceName(AUTHORITY)))
.headers(defaultHeaders())
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON);
var request = getReindexRequestBuilder(asJsonString(new ReindexRequest().resourceName(AUTHORITY)));

mockMvc.perform(request)
.andExpect(status().isOk())
Expand All @@ -84,11 +93,7 @@ void runReindex_positive_authority() throws Exception {

@Test
void runReindex_positive_locations() throws Exception {
var request = post(ApiEndpoints.reindexPath())
.content(asJsonString(new ReindexRequest().resourceName(LOCATION)))
.headers(defaultHeaders())
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON);
var request = getReindexRequestBuilder(asJsonString(new ReindexRequest().resourceName(LOCATION)));

assertThat(countDefaultIndexDocument(LOCATION_RESOURCE)).isZero();
assertThat(countDefaultIndexDocument(CAMPUS_RESOURCE)).isZero();
Expand All @@ -114,14 +119,42 @@ void runReindex_positive_locations() throws Exception {
});
}

@ParameterizedTest
@EnumSource(value = ResourceNameEnum.class, names = {"LINKED_DATA_WORK", "LINKED_DATA_AUTHORITY"})
void runReindex_shouldRecreate_linkedDataResourcesIndexes(ResourceNameEnum resourceNameEnum) throws Exception {
var resourceName = resourceNameEnum.getValue();
var request =
getReindexRequestBuilder(asJsonString(new ReindexRequest().resourceName(resourceNameEnum).recreateIndex(true)));
var indexIdBeforeReindex = getIndexId(resourceName);

mockMvc.perform(request)
.andExpect(status().isOk());

await().atMost(FIVE_SECONDS)
.pollInterval(ONE_HUNDRED_MILLISECONDS)
.untilAsserted(() -> {
assertNotEquals(indexIdBeforeReindex, getIndexId(resourceName));
assertThat(countDefaultIndexDocument(resourceName)).isEqualTo(0);
});
}

@ParameterizedTest
@MethodSource("requestBuilderDataProvider")
void runReindex_shouldNotRecreate_linkedDataResourcesIndexes(RequestBuilder requestBuilder) throws Exception {
var indexIdBeforeReindex = getIndexId(LINKED_DATA_WORK_RESOURCE);

mockMvc.perform(requestBuilder)
.andExpect(status().isOk());

await().atMost(FIVE_SECONDS)
.pollInterval(ONE_HUNDRED_MILLISECONDS)
.untilAsserted(() -> assertEquals(indexIdBeforeReindex, getIndexId(LINKED_DATA_WORK_RESOURCE)));
}

@Test
void runReindex_negative_instanceSubject() throws Exception {
var resource = "instance_subject";
var request = post(ApiEndpoints.reindexPath())
.content(reindexRequestJson(resource))
.headers(defaultHeaders())
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON);
var request = getReindexRequestBuilder(reindexRequestJson(resource));

mockMvc.perform(request)
.andExpect(status().isBadRequest())
Expand All @@ -134,11 +167,7 @@ void runReindex_negative_instanceSubject() throws Exception {
@Test
void runReindex_negative_contributor() throws Exception {
var resource = "contributor";
var request = post(ApiEndpoints.reindexPath())
.content(reindexRequestJson(resource))
.headers(defaultHeaders())
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON);
var request = getReindexRequestBuilder(reindexRequestJson(resource));

mockMvc.perform(request)
.andExpect(status().isBadRequest())
Expand Down Expand Up @@ -191,4 +220,30 @@ private static String reindexUnexpectedResource(String resource) {
return REINDEX_UNEXPECTED_RESOURCE.formatted(resource);
}

private static Stream<Arguments> requestBuilderDataProvider() {
return Stream.of(
arguments(
getReindexRequestBuilder(asJsonString(new ReindexRequest().resourceName(LINKED_DATA_WORK)))
),
arguments(
post(ApiEndpoints.reindexPath())
.content(asJsonString(new ReindexRequest().resourceName(LINKED_DATA_WORK).recreateIndex(true)))
.headers(defaultHeaders(MEMBER_TENANT_ID))
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON)
),
arguments(
getReindexRequestBuilder(asJsonString(new ReindexRequest().resourceName(AUTHORITY).recreateIndex(true)))
)
);
}

private static RequestBuilder getReindexRequestBuilder(String content) {
return post(ApiEndpoints.reindexPath())
.content(content)
.headers(defaultHeaders())
.header(XOkapiHeaders.URL, okapi.getOkapiUrl())
.contentType(MediaType.APPLICATION_JSON);
}

}
69 changes: 69 additions & 0 deletions src/test/java/org/folio/search/service/IndexServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.AUTHORITY;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.LINKED_DATA_AUTHORITY;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.LINKED_DATA_WORK;
import static org.folio.search.domain.dto.ReindexRequest.ResourceNameEnum.LOCATION;
import static org.folio.search.utils.SearchResponseHelper.getSuccessFolioCreateIndexResponse;
import static org.folio.search.utils.SearchResponseHelper.getSuccessIndexOperationResponse;
Expand All @@ -18,10 +20,13 @@
import static org.folio.search.utils.TestConstants.EMPTY_JSON_OBJECT;
import static org.folio.search.utils.TestConstants.EMPTY_OBJECT;
import static org.folio.search.utils.TestConstants.INDEX_NAME;
import static org.folio.search.utils.TestConstants.MEMBER_TENANT_ID;
import static org.folio.search.utils.TestConstants.TENANT_ID;
import static org.folio.search.utils.TestUtils.randomId;
import static org.folio.search.utils.TestUtils.resourceDescription;
import static org.folio.search.utils.TestUtils.secondaryResourceDescription;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atMostOnce;
import static org.mockito.Mockito.doReturn;
Expand Down Expand Up @@ -58,6 +63,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
Expand Down Expand Up @@ -469,6 +475,48 @@ void reindexInventory_positive_locationsAndRecreateIndex() {
verifyNoInteractions(resourceReindexClient);
}

@ParameterizedTest
@EnumSource(value = ReindexRequest.ResourceNameEnum.class, names = {"LINKED_DATA_WORK", "LINKED_DATA_AUTHORITY"})
void reindexInventory_shouldRecreate_linkedDataResourcesIndexes(ReindexRequest.ResourceNameEnum resourceNameEnum) {
var resourceName = resourceNameEnum.getValue();
var linkedDataResourceIndex = getIndexName(resourceName, TENANT_ID);
when(resourceDescriptionService.find(resourceName)).thenReturn(
Optional.of(resourceDescription(resourceName)));
when(resourceDescriptionService.getSecondaryResourceNames(resourceName))
.thenReturn(List.of());
when(indexRepository.indexExists(linkedDataResourceIndex)).thenReturn(true);
when(mappingsHelper.getMappings(resourceName)).thenReturn(EMPTY_OBJECT);
when(settingsHelper.getSettingsJson(resourceName)).thenReturn(EMPTY_JSON_OBJECT);

var reindexRequest = new ReindexRequest().resourceName(resourceNameEnum).recreateIndex(true);
var actual = indexService.reindexInventory(TENANT_ID, reindexRequest);

assertNotNull(actual);
verify(indexRepository).dropIndex(linkedDataResourceIndex);
verify(indexRepository).createIndex(linkedDataResourceIndex, EMPTY_JSON_OBJECT.toString(), EMPTY_OBJECT);
verifyNoInteractions(consortiumInstanceService);
verifyNoInteractions(locationService);
verifyNoInteractions(resourceReindexClient);
}

@ParameterizedTest
@MethodSource("tenantIdAndReindexRequestDataProvider")
void reindexInventory_shouldNotRecreate_linkedDataResourcesIndexes(String tenantId, ReindexRequest reindexRequest) {
var resourceName = reindexRequest.getResourceName().getValue();
when(resourceDescriptionService.find(resourceName)).thenReturn(
Optional.of(resourceDescription(resourceName)));
when(resourceDescriptionService.getSecondaryResourceNames(resourceName))
.thenReturn(List.of());

var actual = indexService.reindexInventory(tenantId, reindexRequest);

assertNotNull(actual);
verifyNoInteractions(indexRepository);
verifyNoInteractions(consortiumInstanceService);
verifyNoInteractions(locationService);
verifyNoInteractions(resourceReindexClient);
}

@Test
void shouldDropIndexWhenExists() {
when(indexRepository.indexExists(INDEX_NAME)).thenReturn(true);
Expand Down Expand Up @@ -525,4 +573,25 @@ private static Stream<Arguments> customDynamicSettingsTestData() {
Arguments.of(null, 0)
);
}

private static Stream<Arguments> tenantIdAndReindexRequestDataProvider() {
return Stream.of(
arguments(
TENANT_ID,
new ReindexRequest().resourceName(LINKED_DATA_WORK)
),
arguments(
TENANT_ID,
new ReindexRequest().resourceName(LINKED_DATA_AUTHORITY)
),
arguments(
MEMBER_TENANT_ID,
new ReindexRequest().resourceName(LINKED_DATA_WORK).recreateIndex(true)
),
arguments(
MEMBER_TENANT_ID,
new ReindexRequest().resourceName(LINKED_DATA_AUTHORITY).recreateIndex(true)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.opensearch.action.search.SearchRequest;
import org.opensearch.client.RequestOptions;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.indices.GetIndexRequest;
import org.opensearch.index.reindex.DeleteByQueryRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
Expand Down Expand Up @@ -196,6 +197,11 @@ protected static long countIndexDocument(String resource, String tenantId) throw
return searchResponse.getHits().getTotalHits().value;
}

protected static String getIndexId(String resource) throws IOException {
var getIndexResponse = elasticClient.indices().get(new GetIndexRequest(getIndexName(resource, TENANT_ID)), DEFAULT);
return getIndexResponse.getSetting(getIndexResponse.getIndices()[0], "index.uuid");
}

protected static long countDefaultIndexDocument(String resource) throws IOException {
return countIndexDocument(resource, TENANT_ID);
}
Expand Down

0 comments on commit fa2ca56

Please sign in to comment.