diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/PartitionSetupApi.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/PartitionSetupApi.java index 56566ed015536ad821fe7328215f8e3f7ed98b24..6a5aae39f978613e70ecf1bf95bc6f1950707e47 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/PartitionSetupApi.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/PartitionSetupApi.java @@ -21,9 +21,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; import org.opengroup.osdu.core.common.model.http.AppError; import org.opengroup.osdu.indexer.logging.AuditLogger; import org.opengroup.osdu.indexer.service.IClusterConfigurationService; +import org.opengroup.osdu.indexer.service.IndexAliasService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -46,27 +48,34 @@ public class PartitionSetupApi { private static final String OPS = "users.datalake.ops"; + @Autowired + private IndexAliasService indexAliasService; @Autowired private IClusterConfigurationService clusterConfigurationService; @Autowired + private JaxRsDpsLog jaxRsDpsLog; + @Autowired private AuditLogger auditLogger; @Operation(summary = "${partitionSetupApi.provisionPartition.summary}", description = "${partitionSetupApi.provisionPartition.description}", - security = {@SecurityRequirement(name = "Authorization")}, tags = { "partition-setup-api" }) + security = {@SecurityRequirement(name = "Authorization")}, tags = {"partition-setup-api"}) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "400", description = "Bad Request", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "401", description = "Unauthorized", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "403", description = "User not authorized to perform the action", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "404", description = "Not Found", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "500", description = "Internal Server Error", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "502", description = "Bad Gateway", content = {@Content(schema = @Schema(implementation = AppError.class))}), - @ApiResponse(responseCode = "503", description = "Service Unavailable", content = {@Content(schema = @Schema(implementation = AppError.class))}) + @ApiResponse(responseCode = "400", description = "Bad Request", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "403", description = "User not authorized to perform the action", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "404", description = "Not Found", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "502", description = "Bad Gateway", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "503", description = "Service Unavailable", content = {@Content(schema = @Schema(implementation = AppError.class))}) }) @PreAuthorize("@authorizationFilter.hasPermission('" + OPS + "')") @PutMapping(path = "/provision", consumes = "application/json") public ResponseEntity<?> provisionPartition(@RequestHeader(DATA_PARTITION_ID) String dataPartitionId) throws IOException { + this.jaxRsDpsLog.info("applying cluster configuration for partition: " + dataPartitionId); this.clusterConfigurationService.updateClusterConfiguration(); + this.jaxRsDpsLog.info("creating default alias for all pre-exiting indices for partition: " + dataPartitionId); + this.indexAliasService.createIndexAliasesForAll(); this.auditLogger.getConfigurePartition(singletonList(dataPartitionId)); return new ResponseEntity<>(org.springframework.http.HttpStatus.OK); } diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/IndexAliasesResult.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/IndexAliasesResult.java new file mode 100644 index 0000000000000000000000000000000000000000..c53f64d4ca68cb6f43709b2f6283f231f26f66b2 --- /dev/null +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/IndexAliasesResult.java @@ -0,0 +1,36 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +@Data +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +public class IndexAliasesResult { + private List<String> indicesWithAliases; + private List<String> indicesWithoutAliases; + + public IndexAliasesResult() { + indicesWithAliases = new ArrayList<>(); + indicesWithoutAliases = new ArrayList<>(); + } +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasService.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasService.java new file mode 100644 index 0000000000000000000000000000000000000000..a817d979f245c639d3576ab48eeab873d06d9736 --- /dev/null +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasService.java @@ -0,0 +1,24 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.service; + +import org.elasticsearch.client.RestHighLevelClient; +import org.opengroup.osdu.indexer.model.IndexAliasesResult; + +public interface IndexAliasService { + IndexAliasesResult createIndexAliasesForAll(); + boolean createIndexAlias(RestHighLevelClient restClient, String kind); +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f8b2c4f482a9f2a90ece49670c10b06a2707f439 --- /dev/null +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImpl.java @@ -0,0 +1,212 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.service; + +import com.google.api.client.util.Strings; +import org.apache.http.HttpStatus; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; +import org.opengroup.osdu.indexer.model.IndexAliasesResult; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +@Component +public class IndexAliasServiceImpl implements IndexAliasService{ + private static final String KIND_COMPLETE_VERSION_PATTERN = "[\\w-\\.\\*]+:[\\w-\\.\\*]+:[\\w-\\.\\*]+:(\\d+\\.\\d+\\.\\d+)$"; + + @Inject + private ElasticIndexNameResolver elasticIndexNameResolver; + @Inject + private ElasticClientHandler elasticClientHandler; + @Inject + private JaxRsDpsLog jaxRsDpsLog; + + @Override + public IndexAliasesResult createIndexAliasesForAll() { + IndexAliasesResult result = new IndexAliasesResult(); + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + List<String> allKinds = getAllKinds(restClient); + Set<String> allExistingAliases = getAllExistingAliases(restClient); + for (String kind : allKinds) { + String alias = elasticIndexNameResolver.getIndexAliasFromKind(kind); + String indexName = elasticIndexNameResolver.getIndexNameFromKind(kind); + if(allExistingAliases.contains(alias)) { + result.getIndicesWithAliases().add(indexName); + } + else { + if(createIndexAlias(restClient, kind)) { + result.getIndicesWithAliases().add(indexName); + } + else { + result.getIndicesWithoutAliases().add(indexName); + } + } + } + } + catch (Exception e) { + jaxRsDpsLog.error("elastic search request failed", e); + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "elastic search cannot respond", "an unknown error has occurred.", e); + } + + return result; + } + + @Override + public boolean createIndexAlias(RestHighLevelClient restClient, String kind) { + if(!elasticIndexNameResolver.isIndexAliasSupported(kind)) { + return false; + } + + try { + // To create an alias for an index, the index name must the concrete index name, not alias + String actualIndexName = resolveConcreteIndexName(restClient, kind); + if(Strings.isNullOrEmpty(actualIndexName)) + return false; + + Map<String, String> indexAliasMap = new HashMap<>(); + indexAliasMap.put(actualIndexName, elasticIndexNameResolver.getIndexAliasFromKind(kind)); + String kindWithMajorVersion = getKindWithMajorVersion(kind); + if(elasticIndexNameResolver.isIndexAliasSupported(kindWithMajorVersion)) { + String index = elasticIndexNameResolver.getIndexNameFromKind(kindWithMajorVersion); + String alias = elasticIndexNameResolver.getIndexAliasFromKind(kindWithMajorVersion); + indexAliasMap.put(index, alias); + } + + boolean ok = true; + for (Map.Entry<String, String> entry: indexAliasMap.entrySet()) { + IndicesAliasesRequest addRequest = new IndicesAliasesRequest(); + IndicesAliasesRequest.AliasActions aliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD) + .index(entry.getKey()) + .alias(entry.getValue()); + addRequest.addAliasAction(aliasActions); + AcknowledgedResponse response = restClient.indices().updateAliases(addRequest, RequestOptions.DEFAULT); + ok &= response.isAcknowledged(); + } + return ok; + } + catch(Exception e) { + jaxRsDpsLog.error(String.format("Fail to create index alias for kind '%s'", kind), e); + } + + return false; + } + + private Set<String> getAllExistingAliases(RestHighLevelClient restClient) throws IOException { + GetAliasesRequest request = new GetAliasesRequest(); + GetAliasesResponse response = restClient.indices().getAlias(request, RequestOptions.DEFAULT); + if(response.status() != RestStatus.OK) + return new HashSet<>(); + + Set<String> allAliases = new HashSet<>(); + for (Set<AliasMetadata> aliasSet: response.getAliases().values()) { + List<String> aliases = aliasSet.stream().map(a -> a.getAlias()).collect(Collectors.toList()); + allAliases.addAll(aliases); + } + return allAliases; + } + + private String getKindWithMajorVersion(String kind) { + // If kind is common:welldb:wellbore:1.2.0, then kind with major version is common:welldb:wellbore:1.*.* + int idx = kind.lastIndexOf(":"); + String version = kind.substring(idx+1); + if(version.indexOf(".") > 0) { + String kindWithoutVersion = kind.substring(0, idx); + String majorVersion = version.substring(0, version.indexOf(".")); + return String.format("%s:%s.*.*", kindWithoutVersion, majorVersion); + } + return null; + } + + private String resolveConcreteIndexName(RestHighLevelClient restClient, String kind) throws IOException { + String index = elasticIndexNameResolver.getIndexNameFromKind(kind); + if(!isCompleteVersionKind(kind)) { + return index; + } + + GetAliasesRequest request = new GetAliasesRequest(index); + GetAliasesResponse response = restClient.indices().getAlias(request, RequestOptions.DEFAULT); + if(response.status() == RestStatus.NOT_FOUND) { + /* index resolved from kind is actual concrete index + * Example: + * { + * "opendes-wke-well-1.0.7": { + * "aliases": {} + * } + * } + */ + return index; + } + if(response.status() == RestStatus.OK) { + /* index resolved from kind is NOT actual create index. It is just an alias + * The concrete index name in this example is "opendes-osdudemo-wellbore-1.0.0_1649167113090" + * Example: + * { + * "opendes-osdudemo-wellbore-1.0.0_1649167113090": { + * "aliases": { + * "opendes-osdudemo-wellbore-1.0.0": {} + * } + * } + * } + */ + Map<String, Set<AliasMetadata>> aliases = response.getAliases(); + for (Map.Entry<String, Set<AliasMetadata>> entry: aliases.entrySet()) { + String actualIndex = entry.getKey(); + List<String> aliaseNames = entry.getValue().stream().map(a -> a.getAlias()).collect(Collectors.toList()); + if(aliaseNames.contains(index)) + return actualIndex; + } + } + return index; + } + + private boolean isCompleteVersionKind(String kind) { + return !Strings.isNullOrEmpty(kind) && kind.matches(KIND_COMPLETE_VERSION_PATTERN); + } + + private List<String> getAllKinds(RestHighLevelClient client) throws IOException { + List<String> kinds; + SearchRequest elasticSearchRequest = new SearchRequest("_all"); + TermsAggregationBuilder termsAggregationBuilder = new TermsAggregationBuilder("kinds"); + termsAggregationBuilder.field("kind"); + termsAggregationBuilder.size(10000); + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.aggregation(termsAggregationBuilder); + elasticSearchRequest.source(sourceBuilder); + SearchResponse searchResponse = client.search(elasticSearchRequest, RequestOptions.DEFAULT); + Terms kindBuckets = searchResponse.getAggregations().get("kinds"); + kinds = kindBuckets.getBuckets().stream().map(bucket -> bucket.getKey().toString()).collect(Collectors.toList()); + return kinds; + } +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java index bc8d633ac9be3a39336ea79a634709e1f34c2940..e585fca490a13a11f53acac4c9a1c5f54ee1a2cf 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java @@ -50,6 +50,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.context.annotation.RequestScope; +import javax.inject.Inject; import java.io.IOException; import java.lang.reflect.Type; import java.util.*; @@ -64,6 +65,8 @@ public class IndicesServiceImpl implements IndicesService { private ElasticIndexNameResolver elasticIndexNameResolver; @Autowired private PartitionSafeIndexCache indexCache; + @Inject + private IndexAliasService indexAliasService; @Autowired private JaxRsDpsLog log; @@ -106,7 +109,8 @@ public class IndicesServiceImpl implements IndicesService { this.indexCache.put(index, true); this.log.info(String.format("Time taken to successfully create new index %s : %d milliseconds", request.index(), stopTime-startTime)); - createIndexAlias(client, index); + // Create alias for index + indexAliasService.createIndexAlias(client, elasticIndexNameResolver.getKindFromIndexName(index)); } return indexStatus; @@ -303,48 +307,4 @@ public class IndicesServiceImpl implements IndicesService { throw exception; } } - - private void createIndexAlias(RestHighLevelClient client, String index) { - String kind = this.elasticIndexNameResolver.getKindFromIndexName(index); - if(!elasticIndexNameResolver.isIndexAliasSupported(kind)) - return; - - try { - List<String> kinds = new ArrayList<>(); - kinds.add(kind); - String kindWithMajorVersion = getKindWithMajorVersion(kind); - if(elasticIndexNameResolver.isIndexAliasSupported(kindWithMajorVersion)) { - kinds.add(kindWithMajorVersion); - } - for (String kd : kinds) { - index = elasticIndexNameResolver.getIndexNameFromKind(kd); - String alias = elasticIndexNameResolver.getIndexAliasFromKind(kd); - IndicesAliasesRequest addRequest = new IndicesAliasesRequest(); - IndicesAliasesRequest.AliasActions aliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD) - .index(index) - .alias(alias); - addRequest.addAliasAction(aliasActions); - AcknowledgedResponse response = client.indices().updateAliases(addRequest, RequestOptions.DEFAULT); - if (response.isAcknowledged()) { - this.log.info(String.format("Alias %s was created for index %s", alias, index)); - } - } - } - catch(Exception ex) { - // Failed to create alias is not the end. It should not affect the status of index creation - this.log.error(String.format("Fail to create aliases for index %s", index), ex); - } - } - - private String getKindWithMajorVersion(String kind) { - // If kind is common:welldb:wellbore:1.2.0, then kind with major version is common:welldb:wellbore:1.*.* - int idx = kind.lastIndexOf(":"); - String version = kind.substring(idx+1); - if(version.indexOf(".") > 0) { - String kindWithoutVersion = kind.substring(0, idx); - String majorVersion = version.substring(0, version.indexOf(".")); - return String.format("%s:%s.*.*", kindWithoutVersion, majorVersion); - } - return null; - } } diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/PartitionSetupApiTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/PartitionSetupApiTest.java index 03c6d718f261f8e4d7b9f7fa7120d5a11fff86be..46c7a775dc8c0d2c9a3b05b6b81dafc38850bd7f 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/PartitionSetupApiTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/PartitionSetupApiTest.java @@ -18,9 +18,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; import org.opengroup.osdu.core.common.model.http.AppException; import org.opengroup.osdu.indexer.logging.AuditLogger; import org.opengroup.osdu.indexer.service.IClusterConfigurationService; +import org.opengroup.osdu.indexer.service.IndexAliasService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; @@ -38,6 +40,10 @@ public class PartitionSetupApiTest { private AuditLogger auditLogger; @Mock private IClusterConfigurationService clusterConfigurationService; + @Mock + private IndexAliasService indexAliasService; + @Mock + private JaxRsDpsLog jaxRsDpsLog; @InjectMocks private PartitionSetupApi sut; diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImplTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..df1e5d4397aa8209fafe272747c1fe9f5b3972f9 --- /dev/null +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndexAliasServiceImplTest.java @@ -0,0 +1,191 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.service; + +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.IndicesClient; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.aggregations.Aggregations; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; +import org.opengroup.osdu.indexer.model.IndexAliasesResult; +import org.opengroup.osdu.indexer.service.mock.BucketMock; +import org.opengroup.osdu.indexer.service.mock.TermMock; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.powermock.api.mockito.PowerMockito; +import org.springframework.context.annotation.Lazy; + +import java.io.IOException; +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +@RunWith(MockitoJUnitRunner.class) +public class IndexAliasServiceImplTest { + @Mock + private ElasticClientHandler elasticClientHandler; + @Mock + private ElasticIndexNameResolver elasticIndexNameResolver; + @Mock + @Lazy + private JaxRsDpsLog log; + @InjectMocks + private IndexAliasServiceImpl sut; + + private RestHighLevelClient restHighLevelClient; + private IndicesClient indicesClient; + private GetAliasesResponse getAliasesResponse, getAliasesNotFoundResponse; + + + private static String kind = "common:welldb:wellbore:1.2.0"; + private static String index = "common-welldb-wellbore-1.2.0"; + private static String alias = "a1234567890"; + + @Before + public void setup() { + initMocks(this); + indicesClient = PowerMockito.mock(IndicesClient.class); + restHighLevelClient = PowerMockito.mock(RestHighLevelClient.class); + getAliasesResponse = PowerMockito.mock(GetAliasesResponse.class); + getAliasesNotFoundResponse = PowerMockito.mock(GetAliasesResponse.class); + + } + + @Test + public void createIndexAlias_test_when_index_name_is_not_alias() throws IOException { + AcknowledgedResponse updateAliasesResponse = new AcknowledgedResponse(true); + when(elasticIndexNameResolver.getIndexNameFromKind(any())).thenReturn(index); + when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn(alias); + when(elasticIndexNameResolver.isIndexAliasSupported(any())).thenReturn(true); + when(restHighLevelClient.indices()).thenReturn(indicesClient); + when(indicesClient.getAlias(any(GetAliasesRequest.class), any(RequestOptions.class))).thenReturn(getAliasesNotFoundResponse); + when(getAliasesNotFoundResponse.status()).thenReturn(RestStatus.NOT_FOUND); + when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenReturn(updateAliasesResponse); + + boolean ok = sut.createIndexAlias(restHighLevelClient, kind); + Assert.assertTrue(ok); + } + + @Test + public void createIndexAlias_test_when_index_name_is_alias() throws IOException { + Map<String, Set<AliasMetadata>> aliases = new HashMap<>(); + Set<AliasMetadata> aliasMetadataSet = new HashSet<>(); + aliasMetadataSet.add(AliasMetadata.builder(index).build()); + aliases.put(index + "_123456789", aliasMetadataSet); + + AcknowledgedResponse updateAliasesResponse = new AcknowledgedResponse(true); + when(elasticIndexNameResolver.getIndexNameFromKind(any())).thenReturn(index); + when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn(alias); + when(elasticIndexNameResolver.isIndexAliasSupported(any())).thenReturn(true); + when(restHighLevelClient.indices()).thenReturn(indicesClient); + when(indicesClient.getAlias(any(GetAliasesRequest.class), any(RequestOptions.class))).thenReturn(getAliasesResponse); + when(getAliasesResponse.status()).thenReturn(RestStatus.OK); + when(getAliasesResponse.getAliases()).thenReturn(aliases); + when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenReturn(updateAliasesResponse); + + boolean ok = sut.createIndexAlias(restHighLevelClient, kind); + Assert.assertTrue(ok); + } + + @Test + public void createIndexAlias_test_when_updateAliases_fails() throws IOException { + AcknowledgedResponse updateAliasesResponse = new AcknowledgedResponse(false); + when(elasticIndexNameResolver.getIndexNameFromKind(any())).thenReturn(index); + when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn(alias); + when(elasticIndexNameResolver.isIndexAliasSupported(any())).thenReturn(true); + when(restHighLevelClient.indices()).thenReturn(indicesClient); + when(indicesClient.getAlias(any(GetAliasesRequest.class), any(RequestOptions.class))).thenReturn(getAliasesNotFoundResponse); + when(getAliasesNotFoundResponse.status()).thenReturn(RestStatus.NOT_FOUND); + when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenReturn(updateAliasesResponse); + + boolean ok = sut.createIndexAlias(restHighLevelClient, kind); + Assert.assertFalse(ok); + } + + @Test + public void createIndexAlias_test_when_updateAliases_throws_exception() throws IOException { + when(elasticIndexNameResolver.getIndexNameFromKind(any())).thenReturn(index); + when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn(alias); + when(elasticIndexNameResolver.isIndexAliasSupported(any())).thenReturn(true); + when(restHighLevelClient.indices()).thenReturn(indicesClient); + when(indicesClient.getAlias(any(GetAliasesRequest.class), any(RequestOptions.class))).thenReturn(getAliasesNotFoundResponse); + when(getAliasesNotFoundResponse.status()).thenReturn(RestStatus.NOT_FOUND); + when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenThrow(IOException.class); + + boolean ok = sut.createIndexAlias(restHighLevelClient, kind); + Assert.assertFalse(ok); + } + + @Test + public void createIndexAliasesForAll_test() throws IOException { + String unsupportedKind = "common:welldb:wellbore:1"; + String unsupportedIndex = unsupportedKind.replace(":", "-"); + + SearchResponse searchResponse = PowerMockito.mock(SearchResponse.class); + Aggregations aggregations = PowerMockito.mock(Aggregations.class); + TermMock terms = PowerMockito.mock(TermMock.class); + BucketMock bucket = PowerMockito.mock(BucketMock.class); + BucketMock bucket2 = PowerMockito.mock(BucketMock.class); + List<BucketMock> bucketList = Arrays.asList(bucket, bucket, bucket2); + AcknowledgedResponse updateAliasesResponse = new AcknowledgedResponse(true); + when(elasticIndexNameResolver.getIndexNameFromKind(any())) + .thenAnswer(invocation ->{ + String argument = invocation.getArgument(0); + return argument.replace(":", "-"); + }); + when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn(alias); + when(elasticIndexNameResolver.isIndexAliasSupported(any())) + .thenAnswer(invocation ->{ + String argument = invocation.getArgument(0); + return !unsupportedKind.equals(argument); + }); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + when(restHighLevelClient.indices()).thenReturn(indicesClient); + when(restHighLevelClient.search(any(SearchRequest.class), any(RequestOptions.class))).thenReturn(searchResponse); + when(searchResponse.getAggregations()).thenReturn(aggregations); + when(aggregations.get(anyString())).thenReturn(terms); + when(terms.getBuckets()).thenReturn(bucketList); + when(bucket.getKey()).thenReturn(kind); + when(bucket2.getKey()).thenReturn(unsupportedKind); + when(indicesClient.getAlias(any(GetAliasesRequest.class), any(RequestOptions.class))).thenReturn(getAliasesNotFoundResponse); + when(getAliasesNotFoundResponse.status()).thenReturn(RestStatus.NOT_FOUND); + when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenReturn(updateAliasesResponse); + + IndexAliasesResult result = sut.createIndexAliasesForAll(); + Assert.assertEquals(2,result.getIndicesWithAliases().size()); + Assert.assertEquals(index, result.getIndicesWithAliases().get(0)); + Assert.assertEquals(1,result.getIndicesWithoutAliases().size()); + Assert.assertEquals(unsupportedIndex, result.getIndicesWithoutAliases().get(0)); + } +} diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndicesServiceTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndicesServiceTest.java index 7f22b619a9364b308791473adbae5324a0687d9a..7bc3fe17d8a060337c7b624fe7d7c76036ff3d7f 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndicesServiceTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/IndicesServiceTest.java @@ -77,6 +77,8 @@ public class IndicesServiceTest { private Response response; @Mock private HttpEntity httpEntity; + @Mock + private IndexAliasService indexAliasService; @InjectMocks private IndicesServiceImpl sut; @@ -94,22 +96,19 @@ public class IndicesServiceTest { @Test public void create_elasticIndex() throws Exception { - String kind = "common:welldb:wellbore:1.2.0"; String index = "common-welldb-wellbore-1.2.0"; CreateIndexResponse indexResponse = new CreateIndexResponse(true, true, index); AcknowledgedResponse acknowledgedResponse = new AcknowledgedResponse(true); - when(elasticIndexNameResolver.getKindFromIndexName(any())).thenReturn(kind); when(elasticIndexNameResolver.getIndexNameFromKind(any())).thenReturn(index); - when(elasticIndexNameResolver.getIndexAliasFromKind(any())).thenReturn("a12345678"); - when(elasticIndexNameResolver.isIndexAliasSupported(any())).thenReturn(true); when(restHighLevelClient.indices()).thenReturn(indicesClient); when(indicesClient.create(any(CreateIndexRequest.class), any(RequestOptions.class))).thenReturn(indexResponse); when(indicesClient.updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class))).thenReturn(acknowledgedResponse); + boolean response = this.sut.createIndex(restHighLevelClient, index, null, "anytype", new HashMap<>()); assertTrue(response); when(this.indicesExistCache.get(index)).thenReturn(true); - verify(this.indicesClient, times(2)).updateAliases(any(IndicesAliasesRequest.class), any(RequestOptions.class)); + verify(this.indexAliasService, times(1)).createIndexAlias(any(), any()); } @Test diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/BucketMock.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/BucketMock.java new file mode 100644 index 0000000000000000000000000000000000000000..df778acd8c8a4213bd5acae54e2d1155e1c55698 --- /dev/null +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/BucketMock.java @@ -0,0 +1,59 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.service.mock; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; + +import java.io.IOException; + +public class BucketMock implements Terms.Bucket { + @Override + public Number getKeyAsNumber() { + return null; + } + + @Override + public long getDocCountError() { + return 0; + } + + @Override + public Object getKey() { + return null; + } + + @Override + public String getKeyAsString() { + return null; + } + + @Override + public long getDocCount() { + return 0; + } + + @Override + public Aggregations getAggregations() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + return null; + } +} diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/TermMock.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/TermMock.java new file mode 100644 index 0000000000000000000000000000000000000000..c0250cf4dee80b5224f6df8014090fc90abc491b --- /dev/null +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/mock/TermMock.java @@ -0,0 +1,65 @@ +/* + * Copyright © Schlumberger + * + * Licensed 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 + * + * https://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. + */ + +package org.opengroup.osdu.indexer.service.mock; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class TermMock implements Terms { + @Override + public List<BucketMock> getBuckets() { + return null; + } + + @Override + public Bucket getBucketByKey(String s) { + return null; + } + + @Override + public long getDocCountError() { + return 0; + } + + @Override + public long getSumOfOtherDocCounts() { + return 0; + } + + @Override + public String getName() { + return null; + } + + @Override + public String getType() { + return null; + } + + @Override + public Map<String, Object> getMetadata() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { + return null; + } +}