From fbeccdc6a0d34958e0c5bc220a00c5e155b8690d Mon Sep 17 00:00:00 2001 From: Alok Joshi <ajoshi19@slb.com> Date: Tue, 24 Nov 2020 17:52:55 -0500 Subject: [PATCH] Patch api for Partition service --- .gitlab-ci.yml | 2 +- docs/api/partition_openapi.yaml | 37 +++++- docs/tutorial/Partition.md | 29 +++++ .../osdu/partition/api/PartitionApi.java | 8 ++ .../interfaces/IPartitionService.java | 2 + .../interfaces/IPartitionServiceCache.java | 1 - .../service/CachedPartitionServiceImpl.java | 21 +++- .../osdu/partition/api/PartitionApiTest.java | 6 + .../CachedPartitionServiceImplTest.java | 34 +++++- .../aws/service/PartitionServiceImpl.java | 7 +- provider/partition-azure/README.md | 7 +- .../azure/cache/PartitionListCacheImpl.java | 30 ++++- .../cache/PartitionServiceCacheImpl.java | 31 ++++- .../azure/di/AzureBootstrapConfig.java | 1 + .../provider/azure/di/RedisConfig.java | 68 +++++++++++ .../partition/provider/azure/di/VmConfig.java | 26 +++++ .../azure/persistence/CloudTableStore.java | 12 +- .../persistence/PartitionTableStore.java | 2 +- .../azure/service/PartitionServiceImpl.java | 20 +++- .../src/main/resources/application.properties | 15 ++- .../persistence/PartitionTableStoreTest.java | 3 +- .../service/PartitionServiceImplTest.java | 26 +++++ .../partition/api/TestUpdatePartition.java | 39 +++++++ .../partition/api/UpdatePartitionTest.java | 109 ++++++++++++++++++ .../descriptor/UpdatePartitionDescriptor.java | 31 +++++ .../osdu/partition/util/TestUtils.java | 25 ++++ 26 files changed, 561 insertions(+), 31 deletions(-) create mode 100644 provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/RedisConfig.java create mode 100644 provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/VmConfig.java create mode 100644 testing/partition-test-azure/src/test/java/org/opengroup/osdu/partition/api/TestUpdatePartition.java create mode 100644 testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/UpdatePartitionTest.java create mode 100644 testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/descriptor/UpdatePartitionDescriptor.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 89992378a..05e9e5b54 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ include: file: "scanners/gitlab-ultimate.yml" - project: "osdu/platform/ci-cd-pipelines" - file: "scanners/fossa.yml" + file: "scanners/fossa-maven.yml" - project: 'osdu/platform/ci-cd-pipelines' file: 'cloud-providers/aws.yml' diff --git a/docs/api/partition_openapi.yaml b/docs/api/partition_openapi.yaml index d6b077a2b..13c47a88a 100644 --- a/docs/api/partition_openapi.yaml +++ b/docs/api/partition_openapi.yaml @@ -156,6 +156,39 @@ paths: security: - JWT: - global + patch: + tags: + - partition-api + summary: update + operationId: updateUsingPATCH + consumes: + - application/json + produces: + - application/json + parameters: + - name: partitionId + in: path + description: partitionId + required: true + type: string + - in: body + name: partitionInfo + description: partitionInfo + required: true + schema: + $ref: '#/definitions/PartitionInfo' + responses: + '204': + description: No Content + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + security: + - JWT: + - global delete: tags: - partition-api @@ -183,12 +216,14 @@ paths: - global securityDefinitions: JWT: - type: oauth2 + type: apiKey name: Authorization in: header definitions: PartitionInfo: type: object + required: + - properties properties: properties: type: object diff --git a/docs/tutorial/Partition.md b/docs/tutorial/Partition.md index e8e7b328c..516bd50b0 100644 --- a/docs/tutorial/Partition.md +++ b/docs/tutorial/Partition.md @@ -7,6 +7,7 @@ * [APIs](#apis) * [Get partition details](#get-partition) * [Create a new partition](#create-partition) + * [Update an existing partition](#update-partition) * [Delete an existing partition](#delete-partition) * [List of partitions](#list-partition) @@ -128,6 +129,34 @@ curl --request POST \ ``` </details> +[Back to Table of Contents](#TOC) + +### Update an existing partition<a name="update-partition"></a> +This api is used to update the properties of an existing partition. With this api, we can modify existing properties or add new ones. Deletion of properties can not be achieved, we'll have to delete the partition and re-create it for the same effect. +``` +PATCH api/partition/v1/partitions/{partitionId} +``` +<details><summary>curl</summary> + +``` +curl --request PATCH \ + --url 'https://<base_url>/api/partition/v1/partitions/mypartition' \ + --header 'Authorization: Bearer <JWT>' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "properties": { + "compliance-ruleset": { + "value": "shared-update-value" + }, + "new-key": { + "sensitive": true, + "value": "new-value" + } + } + }' +``` +</details> + ### Delete an existing partition<a name="delete-partition"></a> This api is used to delete an existing partition. A plausible use case would be partition teardown infrastructure script. ``` diff --git a/partition-core/src/main/java/org/opengroup/osdu/partition/api/PartitionApi.java b/partition-core/src/main/java/org/opengroup/osdu/partition/api/PartitionApi.java index 0192cde50..1eb890c15 100644 --- a/partition-core/src/main/java/org/opengroup/osdu/partition/api/PartitionApi.java +++ b/partition-core/src/main/java/org/opengroup/osdu/partition/api/PartitionApi.java @@ -19,6 +19,7 @@ import org.opengroup.osdu.partition.model.Property; import org.opengroup.osdu.partition.provider.interfaces.IPartitionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -47,6 +48,13 @@ public class PartitionApi { return ResponseEntity.created(partitionLocation).build(); } + @PatchMapping("/{partitionId}") + @PreAuthorize("@authorizationFilter.hasPermissions()") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void patch(@PathVariable("partitionId") String partitionId, @RequestBody @Valid PartitionInfo partitionInfo) { + this.partitionService.updatePartition(partitionId, partitionInfo); + } + @GetMapping("/{partitionId}") @PreAuthorize("@authorizationFilter.hasPermissions()") public ResponseEntity<Map<String, Property>> get(@PathVariable("partitionId") String partitionId) { diff --git a/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionService.java b/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionService.java index 8c56fa410..8d0729704 100644 --- a/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionService.java +++ b/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionService.java @@ -22,6 +22,8 @@ public interface IPartitionService { PartitionInfo createPartition(String partitionId, PartitionInfo partitionInfo); + PartitionInfo updatePartition(String partitionId, PartitionInfo partitionInfo); + PartitionInfo getPartition(String partitionId); boolean deletePartition(String partitionId); diff --git a/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionServiceCache.java b/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionServiceCache.java index 762074d0c..593d67c65 100644 --- a/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionServiceCache.java +++ b/partition-core/src/main/java/org/opengroup/osdu/partition/provider/interfaces/IPartitionServiceCache.java @@ -15,7 +15,6 @@ package org.opengroup.osdu.partition.provider.interfaces; import org.opengroup.osdu.core.common.cache.ICache; -import org.opengroup.osdu.partition.model.PartitionInfo; public interface IPartitionServiceCache<String, V> extends ICache<String, V> { } diff --git a/partition-core/src/main/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImpl.java b/partition-core/src/main/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImpl.java index 7cfd3e927..44a25c201 100644 --- a/partition-core/src/main/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImpl.java +++ b/partition-core/src/main/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImpl.java @@ -14,6 +14,8 @@ package org.opengroup.osdu.partition.service; +import org.apache.http.HttpStatus; +import org.opengroup.osdu.core.common.model.http.AppException; import org.opengroup.osdu.partition.model.PartitionInfo; import org.opengroup.osdu.partition.provider.interfaces.IPartitionService; import org.opengroup.osdu.partition.provider.interfaces.IPartitionServiceCache; @@ -33,13 +35,17 @@ public class CachedPartitionServiceImpl implements IPartitionService { private IPartitionService partitionService; @Inject - private IPartitionServiceCache partitionServiceCache; + @Qualifier("partitionServiceCache") + private IPartitionServiceCache<String, PartitionInfo> partitionServiceCache; @Inject - private IPartitionServiceCache partitionListCache; + @Qualifier("partitionListCache") + private IPartitionServiceCache<String, List<String>> partitionListCache; @Override public PartitionInfo createPartition(String partitionId, PartitionInfo partitionInfo) { + if (partitionServiceCache.get(partitionId) != null) + throw new AppException(HttpStatus.SC_CONFLICT, "partition exist", "Partition with same id exist"); PartitionInfo pi = partitionService.createPartition(partitionId, partitionInfo); if (pi != null) { @@ -49,6 +55,17 @@ public class CachedPartitionServiceImpl implements IPartitionService { return pi; } + @Override + public PartitionInfo updatePartition(String partitionId, PartitionInfo partitionInfo) { + PartitionInfo pi = partitionService.updatePartition(partitionId, partitionInfo); + + if(pi != null) { + partitionServiceCache.put(partitionId, pi); + } + + return pi; + } + @Override public PartitionInfo getPartition(String partitionId) { PartitionInfo pi = (PartitionInfo) partitionServiceCache.get(partitionId); diff --git a/partition-core/src/test/java/org/opengroup/osdu/partition/api/PartitionApiTest.java b/partition-core/src/test/java/org/opengroup/osdu/partition/api/PartitionApiTest.java index af86e7402..1cd1b2adf 100644 --- a/partition-core/src/test/java/org/opengroup/osdu/partition/api/PartitionApiTest.java +++ b/partition-core/src/test/java/org/opengroup/osdu/partition/api/PartitionApiTest.java @@ -77,6 +77,12 @@ public class PartitionApiTest { assertNotNull(result.getHeaders().get(HttpHeaders.LOCATION)); } + @Test + public void should_return204_when_givenUpdatingValidPartitionId() { + String partitionId = "partition1"; + this.sut.patch(partitionId, partitionInfo); + } + @Test public void should_return200AndPartitionProperties_when_gettingPartitionIdSuccessfully() { String partitionId = "partition1"; diff --git a/partition-core/src/test/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImplTest.java b/partition-core/src/test/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImplTest.java index 7b83376d5..596dac8dd 100644 --- a/partition-core/src/test/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImplTest.java +++ b/partition-core/src/test/java/org/opengroup/osdu/partition/service/CachedPartitionServiceImplTest.java @@ -36,10 +36,10 @@ public class CachedPartitionServiceImplTest { private IPartitionService partitionServiceImpl; @Mock - private IPartitionServiceCache partitionServiceCache; + private IPartitionServiceCache<String, PartitionInfo> partitionServiceCache; @Mock - private IPartitionServiceCache partitionListCache; + private IPartitionServiceCache<String, List<String>> partitionListCache; @InjectMocks private CachedPartitionServiceImpl cachedPartitionServiceImpl; @@ -64,12 +64,42 @@ public class CachedPartitionServiceImplTest { String partId = "key"; PartitionInfo newPi = PartitionInfo.builder().build(); + when(partitionServiceCache.get(partId)).thenReturn(null); when(partitionServiceImpl.createPartition(partId, newPi)).thenReturn(null); cachedPartitionServiceImpl.createPartition(partId, newPi); verify(partitionServiceImpl, times(1)).createPartition(partId, newPi); verify(partitionServiceCache, times(0)).put(any(), any()); + verify(partitionServiceCache, times(1)).get(any()); + } + + @Test + public void updatePartitionSucceed() { + String partId = "key"; + + PartitionInfo newPi = PartitionInfo.builder().build(); + PartitionInfo retPi = PartitionInfo.builder().build(); + + when(partitionServiceImpl.updatePartition(partId, newPi)).thenReturn(retPi); + + cachedPartitionServiceImpl.updatePartition(partId, newPi); + + verify(partitionServiceImpl, times(1)).updatePartition(partId, newPi); + verify(partitionServiceCache, times(1)).put(partId, retPi); + } + + @Test + public void updatePartitionFailed() { + String partId = "key"; + PartitionInfo newPi = PartitionInfo.builder().build(); + + when(partitionServiceImpl.updatePartition(partId, newPi)).thenReturn(null); + + cachedPartitionServiceImpl.updatePartition(partId, newPi); + + verify(partitionServiceImpl, times(1)).updatePartition(partId, newPi); + verify(partitionServiceCache, times(0)).put(any(), any()); verify(partitionServiceCache, times(0)).get(any()); } diff --git a/provider/partition-aws/src/main/java/org/opengroup/osdu/partition/provider/aws/service/PartitionServiceImpl.java b/provider/partition-aws/src/main/java/org/opengroup/osdu/partition/provider/aws/service/PartitionServiceImpl.java index 71250a42a..e4fd333a8 100644 --- a/provider/partition-aws/src/main/java/org/opengroup/osdu/partition/provider/aws/service/PartitionServiceImpl.java +++ b/provider/partition-aws/src/main/java/org/opengroup/osdu/partition/provider/aws/service/PartitionServiceImpl.java @@ -17,8 +17,6 @@ package org.opengroup.osdu.partition.provider.aws.service; import java.util.List; import java.util.Map; -import com.amazonaws.partitions.model.Partition; - import org.apache.http.HttpStatus; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; import org.opengroup.osdu.core.common.model.http.AppException; @@ -106,6 +104,11 @@ public class PartitionServiceImpl implements IPartitionService { return partitionInfo; } + @Override + public PartitionInfo updatePartition(String partitionId, PartitionInfo partitionInfo) { + throw new AppException(HttpStatus.SC_NOT_IMPLEMENTED, "Not implemented", "Not implemented"); + } + @Override public PartitionInfo getPartition(String partitionId) { diff --git a/provider/partition-azure/README.md b/provider/partition-azure/README.md index 72c1b9645..e5753d1cb 100644 --- a/provider/partition-azure/README.md +++ b/provider/partition-azure/README.md @@ -34,12 +34,13 @@ az keyvault secret show --vault-name $KEY_VAULT_NAME --name $KEY_VAULT_SECRET_NA | `AZURE_CLIENT_ID` | `********` | Identity to run the service locally. This enables access to Azure resources. You only need this if running locally | yes | keyvault secret: `$KEYVAULT_URI/secrets/app-dev-sp-username` | | `AZURE_CLIENT_SECRET` | `********` | Secret for `$AZURE_CLIENT_ID` | yes | keyvault secret: `$KEYVAULT_URI/secrets/app-dev-sp-password` | | `KEYVAULT_URI` | (non-secret) | KeyVault URI | no | variable `AZURE_KEYVAULT_URI` from GitLab variable group `Azure Target Env - {{env}}` | -| `aad_client_id` | `********` | AAD client application ID | yes | keyvault secret: `$KEYVAULT_URI/secrets/aad-client-id` | +| `azure.activedirectory.app-resource-id` | `********` | AAD client application ID | yes | output of infrastructure deployment | +| `azure.activedirectory.client-id` | `********` | AAD client application ID | yes | keyvault secret: `$KEYVAULT_URI/secrets/aad-client-id` | | `azure.activedirectory.AppIdUri` | `api://${azure.activedirectory.client-id}` | URI for AAD Application | no | -- | | `azure.activedirectory.session-stateless` | `true` | Flag run in stateless mode (needed by AAD dependency) | no | -- | | `appinsights_key` | `********` | Application Insights Instrumentation Key, required to hook AppInsights with Partition application | yes | keyvault secret: `$KEYVAULT_URI/secrets/appinsights-key` | - - +| `cache.provider` | (non-secret) | Cache to be used (can use `vm` for local testing) | no | - | +| `redis.ssl.enabled` | (non-secret) | `true` if connecting to redis cache with SSL enabled, `false` otherwise | no | - **Required to run integration tests** diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionListCacheImpl.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionListCacheImpl.java index a8f722ce8..11a6a7061 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionListCacheImpl.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionListCacheImpl.java @@ -1,18 +1,38 @@ package org.opengroup.osdu.partition.provider.azure.cache; -import org.opengroup.osdu.core.common.cache.VmCache; -import org.opengroup.osdu.partition.model.PartitionInfo; +import org.opengroup.osdu.core.common.cache.ICache; import org.opengroup.osdu.partition.provider.interfaces.IPartitionServiceCache; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import javax.annotation.Resource; import java.util.List; @Service @Qualifier("partitionListCache") -public class PartitionListCacheImpl extends VmCache<String, List<String>> implements IPartitionServiceCache<String, List<String>> { +public class PartitionListCacheImpl implements IPartitionServiceCache<String, List<String>> { - public PartitionListCacheImpl() { - super(5 * 60, 1000); + @Resource(name="partitionListCache") + private ICache<String, List<String>> cache; + + @Override + public void put(String s, List<String> o) { + this.cache.put(s, o); + } + + @Override + public List<String> get(String s) { + return this.cache.get(s); + } + + @Override + public void delete(String s) { + this.cache.delete(s); + } + + @Override + public void clearAll() { + this.cache.clearAll(); } + } diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionServiceCacheImpl.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionServiceCacheImpl.java index 07ed510f5..372432c05 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionServiceCacheImpl.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/cache/PartitionServiceCacheImpl.java @@ -1,18 +1,37 @@ package org.opengroup.osdu.partition.provider.azure.cache; -import org.opengroup.osdu.core.common.cache.VmCache; +import org.opengroup.osdu.core.common.cache.ICache; import org.opengroup.osdu.partition.model.PartitionInfo; import org.opengroup.osdu.partition.provider.interfaces.IPartitionServiceCache; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; +import javax.annotation.Resource; + @Service -@Primary @Qualifier("partitionServiceCache") -public class PartitionServiceCacheImpl extends VmCache<String, PartitionInfo> implements IPartitionServiceCache<String, PartitionInfo> { +public class PartitionServiceCacheImpl implements IPartitionServiceCache<String, PartitionInfo> { + + @Resource(name="partitionServiceCache") + private ICache<String, PartitionInfo> cache; + + @Override + public void put(String s, PartitionInfo o) { + this.cache.put(s, o); + } + + @Override + public PartitionInfo get(String s) { + return this.cache.get(s); + } + + @Override + public void delete(String s) { + this.cache.delete(s); + } - public PartitionServiceCacheImpl() { - super(5 * 60, 1000); + @Override + public void clearAll() { + this.cache.clearAll(); } } diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/AzureBootstrapConfig.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/AzureBootstrapConfig.java index 9d04b6636..5e898bb01 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/AzureBootstrapConfig.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/AzureBootstrapConfig.java @@ -45,4 +45,5 @@ public class AzureBootstrapConfig { public String storageAccountKey(SecretClient kv) { return KeyVaultFacade.getSecretWithValidation(kv, "tbl-storage-key"); } + } \ No newline at end of file diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/RedisConfig.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/RedisConfig.java new file mode 100644 index 000000000..627ae0a70 --- /dev/null +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/RedisConfig.java @@ -0,0 +1,68 @@ +package org.opengroup.osdu.partition.provider.azure.di; + +import com.azure.security.keyvault.secrets.SecretClient; +import org.opengroup.osdu.azure.KeyVaultFacade; +import org.opengroup.osdu.core.common.cache.RedisCache; +import org.opengroup.osdu.partition.model.PartitionInfo; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.inject.Named; + +@Configuration +public class RedisConfig { + + @Bean + @Named("REDIS_HOST") + public String redisHost(SecretClient kv) { + return KeyVaultFacade.getSecretWithValidation(kv, "redis-hostname"); + } + + @Bean + @Named("REDIS_PASSWORD") + public String redisPassword(SecretClient kv) { + return KeyVaultFacade.getSecretWithValidation(kv, "redis-password"); + } + + @Configuration + @ConditionalOnExpression(value = "'${cache.provider}' == 'redis' && '${redis.ssl.enabled:true}'") + static class SslConfig { + + @Value("${redis.port}") + private int port; + + @Value("${redis.expiration}") + private int expiration; + + @Value("${redis.database}") + private int database; + + @Bean + public RedisCache<String, PartitionInfo> partitionServiceCache(@Named("REDIS_HOST") String host, @Named("REDIS_PASSWORD") String password) { + return new RedisCache<>(host, port, password, expiration, database, String.class, PartitionInfo.class); + } + + } + + @Configuration + @ConditionalOnExpression(value = "'${cache.provider}' == 'redis' && !'${redis.ssl.enabled:true}'") + static class NoSslConfig { + + @Value("${redis.port}") + private int port; + + @Value("${redis.expiration}") + private int expiration; + + @Value("${redis.database}") + private int database; + + @Bean + public RedisCache<String, PartitionInfo> partitionServiceCache(@Named("REDIS_HOST") String host) { + return new RedisCache<>(host, port, expiration, database, String.class, PartitionInfo.class); + } + + } +} diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/VmConfig.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/VmConfig.java new file mode 100644 index 000000000..00aef74c5 --- /dev/null +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/di/VmConfig.java @@ -0,0 +1,26 @@ +package org.opengroup.osdu.partition.provider.azure.di; + +import org.opengroup.osdu.core.common.cache.VmCache; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class VmConfig { + + @Bean + public VmCache<String, List<String>> partitionListCache(@Value("${cache.expiration}") final int expiration, + @Value("${cache.maxSize}") final int maxSize) { + return new VmCache<>(expiration * 60, maxSize); + } + + @Bean + @ConditionalOnProperty(value = "cache.provider", havingValue = "vm", matchIfMissing = true) + public VmCache<String, List<String>> partitionServiceCache(@Value("${cache.expiration}") final int expiration, + @Value("${cache.maxSize}") final int maxSize) { + return new VmCache<>(expiration * 60, maxSize); + } +} diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/CloudTableStore.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/CloudTableStore.java index 5c5e02775..f9b29ecf3 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/CloudTableStore.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/CloudTableStore.java @@ -50,7 +50,11 @@ public class CloudTableStore { TableQuery<? extends TableEntity> partitionQuery = TableQuery.from(clazzType) .where(partitionFilter); - return this.cloudTableClient.execute(partitionQuery); + try { + return this.cloudTableClient.execute(partitionQuery); + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "error getting partition", e.getMessage(), e); + } } public Iterable<? extends TableEntity> queryByCompoundKey(final Class<? extends TableEntity> clazzType, @@ -72,7 +76,11 @@ public class CloudTableStore { TableQuery<? extends TableEntity> partitionQuery = TableQuery.from(clazzType) .where(combinedFilter); - return this.cloudTableClient.execute(partitionQuery); + try { + return this.cloudTableClient.execute(partitionQuery); + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "error getting partition", e.getMessage(), e); + } } public void insertBatchEntities(TableBatchOperation batchOperation) { diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStore.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStore.java index 841b324e3..4b101ddda 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStore.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStore.java @@ -55,7 +55,7 @@ public class PartitionTableStore { properties.put(VALUE, new EntityProperty(String.valueOf(property.getValue()))); properties.put(SENSITIVE, new EntityProperty(property.isSensitive())); partitionEntity.setProperties(properties); - batchOperation.insertOrReplace(partitionEntity); + batchOperation.insertOrMerge(partitionEntity); } this.cloudTableStore.insertBatchEntities(batchOperation); diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImpl.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImpl.java index fee469574..ec96e47aa 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImpl.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImpl.java @@ -31,6 +31,8 @@ import java.util.Map; @Service public class PartitionServiceImpl implements IPartitionService { + private final String PARTITION_NOT_FOUND = "partition not found"; + @Autowired private PartitionTableStore tableStore; @@ -45,13 +47,27 @@ public class PartitionServiceImpl implements IPartitionService { return partitionInfo; } + @Override + public PartitionInfo updatePartition(String partitionId, PartitionInfo partitionInfo) { + if (!this.tableStore.partitionExists(partitionId)) { + throw new AppException(HttpStatus.SC_NOT_FOUND, PARTITION_NOT_FOUND, String.format("%s partition not found", partitionId)); + } + + if(partitionInfo.getProperties().containsKey("id")) { + throw new AppException(HttpStatus.SC_BAD_REQUEST, "can not update id", "the field id can not be updated"); + } + + this.tableStore.addPartition(partitionId, partitionInfo); + return PartitionInfo.builder().properties(this.tableStore.getPartition(partitionId)).build(); + } + @Override public PartitionInfo getPartition(String partitionId) { Map<String, Property> out = new HashMap<>(); out.putAll(this.tableStore.getPartition(partitionId)); if (out.isEmpty()) { - throw new AppException(HttpStatus.SC_NOT_FOUND, "partition not found", String.format("%s partition not found", partitionId)); + throw new AppException(HttpStatus.SC_NOT_FOUND, PARTITION_NOT_FOUND, String.format("%s partition not found", partitionId)); } return PartitionInfo.builder().properties(out).build(); @@ -60,7 +76,7 @@ public class PartitionServiceImpl implements IPartitionService { @Override public boolean deletePartition(String partitionId) { if (!this.tableStore.partitionExists(partitionId)) { - throw new AppException(HttpStatus.SC_NOT_FOUND, "partition not found", String.format("%s partition not found", partitionId)); + throw new AppException(HttpStatus.SC_NOT_FOUND, PARTITION_NOT_FOUND, String.format("%s partition not found", partitionId)); } this.tableStore.deletePartition(partitionId); diff --git a/provider/partition-azure/src/main/resources/application.properties b/provider/partition-azure/src/main/resources/application.properties index a714f8e22..5db183cc3 100644 --- a/provider/partition-azure/src/main/resources/application.properties +++ b/provider/partition-azure/src/main/resources/application.properties @@ -24,4 +24,17 @@ azure.application-insights.instrumentation-key=${appinsights_key} # Azure service connection properties AZURE_CLIENT_ID=${AZURE_CLIENT_ID} AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET} -AZURE_TENANT_ID=${AZURE_TENANT_ID} \ No newline at end of file +AZURE_TENANT_ID=${AZURE_TENANT_ID} + +# Cache configuration, provider [vm or redis] +cache.provider=redis + +# VM +cache.expiration=5 +cache.maxSize=1000 + +# Redis [set ssl.enabled to 'false' when running locally, if using redis cache] +redis.port=6380 +redis.expiration=3600 +redis.ssl.enabled=true +redis.database=${REDIS_DATABASE} \ No newline at end of file diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStoreTest.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStoreTest.java index 73b19d87e..4ed1dc34a 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStoreTest.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/persistence/PartitionTableStoreTest.java @@ -68,7 +68,7 @@ public class PartitionTableStoreTest { } @Test - public void should_addPartiton_whenPartionProvided() { + public void should_addPartiton_whenPartitionProvided() { sut.addPartition(PARTITION_ID, new PartitionInfo()); } @@ -84,7 +84,6 @@ public class PartitionTableStoreTest { } } - @Test public void should_getAll_partitions() { Collection<PartitionEntity> list = new ArrayList<>(); diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImplTest.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImplTest.java index 7b42b2dc6..8643ebee2 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImplTest.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/service/PartitionServiceImplTest.java @@ -78,6 +78,32 @@ public class PartitionServiceImplTest { assertTrue(partInfo.getProperties().containsKey("storageAccount")); } + @Test + public void should_ThrowNotFoundError_when_updatePartition_whenPartitionDoesnsExist() { + when(this.tableStore.partitionExists(PARTITION_ID)).thenReturn(false); + + try { + sut.updatePartition(PARTITION_ID, this.partitionInfo); + } catch (AppException e) { + assertEquals(404, e.getError().getCode()); + assertTrue(e.getError().getReason().equalsIgnoreCase("partition not found")); + assertTrue(e.getError().getMessage().equalsIgnoreCase("my-tenant partition not found")); + } + } + + @Test + public void should_ThrowBadRequestError_when_updatePartition_whenUpdatingPartitionId() { + when(this.tableStore.partitionExists(PARTITION_ID)).thenReturn(true); + + try { + sut.updatePartition(PARTITION_ID, this.partitionInfo); + } catch (AppException e) { + assertEquals(400, e.getError().getCode()); + assertTrue(e.getError().getReason().equalsIgnoreCase("can not update id")); + assertTrue(e.getError().getMessage().equalsIgnoreCase("the field id can not be updated")); + } + } + @Test public void should_returnPartition_when_partitionExists() { when(this.tableStore.getPartition(PARTITION_ID)).thenReturn(properties); diff --git a/testing/partition-test-azure/src/test/java/org/opengroup/osdu/partition/api/TestUpdatePartition.java b/testing/partition-test-azure/src/test/java/org/opengroup/osdu/partition/api/TestUpdatePartition.java new file mode 100644 index 000000000..31e7411b9 --- /dev/null +++ b/testing/partition-test-azure/src/test/java/org/opengroup/osdu/partition/api/TestUpdatePartition.java @@ -0,0 +1,39 @@ +package org.opengroup.osdu.partition.api; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opengroup.osdu.partition.util.AzureTestUtils; + +public class TestUpdatePartition extends UpdatePartitionTest { + + @Before + @Override + public void setup() { + this.testUtils = new AzureTestUtils(); + } + + @After + @Override + public void tearDown() { + this.testUtils = null; + } + + @Test + @Override + public void should_return401_when_noAccessToken() throws Exception { + // revisit this later -- Istio is changing the response code + } + + @Test + @Override + public void should_return401_when_accessingWithCredentialsWithoutPermission() throws Exception { + // revisit this later -- Istio is changing the response code + } + + @Test + @Override + public void should_return401_when_makingHttpRequestWithoutToken() throws Exception { + // revisit this later -- Istio is changing the response code + } +} diff --git a/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/UpdatePartitionTest.java b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/UpdatePartitionTest.java new file mode 100644 index 000000000..bb3292d3f --- /dev/null +++ b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/UpdatePartitionTest.java @@ -0,0 +1,109 @@ +package org.opengroup.osdu.partition.api; + +import com.sun.jersey.api.client.ClientResponse; +import org.junit.Test; +import org.opengroup.osdu.partition.api.descriptor.CreatePartitionDescriptor; +import org.opengroup.osdu.partition.api.descriptor.DeletePartitionDescriptor; +import org.opengroup.osdu.partition.api.descriptor.UpdatePartitionDescriptor; +import org.opengroup.osdu.partition.util.BaseTestTemplate; +import org.springframework.http.HttpStatus; + +import static org.junit.Assert.assertEquals; + +public abstract class UpdatePartitionTest extends BaseTestTemplate { + + private String partitionId = getIntegrationTestPrefix() + System.currentTimeMillis(); + + private String nonExistentPartitionId = "nonexistent-partition"+System.currentTimeMillis(); + + public UpdatePartitionTest() { + super(new UpdatePartitionDescriptor()); + } + + @Override + protected String getId() { + return partitionId; + } + + @Override + protected void deleteResource() throws Exception { + DeletePartitionDescriptor deletePartitionDes = new DeletePartitionDescriptor(); + deletePartitionDes.setPartitionId(partitionId); + ClientResponse response = deletePartitionDes.run(this.getId(), this.testUtils.getAccessToken()); + assertEquals(this.error(""), HttpStatus.NO_CONTENT.value(), (long) response.getStatus()); + } + + @Override + protected void createResource() throws Exception { + CreatePartitionDescriptor createPartitionDescriptor = new CreatePartitionDescriptor(); + createPartitionDescriptor.setPartitionId(partitionId); + + ClientResponse createResponse = createPartitionDescriptor.run(this.getId(), this.testUtils.getAccessToken()); + assertEquals(this.error((String) createResponse.getEntity(String.class)) + , HttpStatus.CREATED.value(), (long) createResponse.getStatus()); + } + + @Override + protected int expectedOkResponseCode() { + return HttpStatus.CREATED.value(); + } + + @Test + public void should_return404_when_updatingNonExistentPartition() throws Exception { + ClientResponse response = this.descriptor.run(nonExistentPartitionId, this.testUtils.getAccessToken()); + assertEquals(this.error(""), HttpStatus.NOT_FOUND.value(), (long) response.getStatus()); + } + + @Test + public void should_return400_when_updatingPartitionWithIdField() throws Exception { + createResource(); + ClientResponse response = this.descriptor.runWithCustomPayload(this.getId(), getInvalidBodyForUpdatePartition(), this.testUtils.getAccessToken()); + assertEquals(this.error(""), HttpStatus.BAD_REQUEST.value(), (long) response.getStatus()); + deleteResource(); + } + + @Test + @Override + public void should_return20XResponseCode_when_makingValidHttpsRequest() throws Exception { + createResource(); + ClientResponse response = this.descriptor.runWithCustomPayload(this.getId(), getValidBodyForUpdatePartition(), this.testUtils.getAccessToken()); + deleteResource(); + assertEquals(response.getStatus(), HttpStatus.NO_CONTENT.value()); + assertEquals("[GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH]", response.getHeaders().getFirst("Access-Control-Allow-Methods")); + assertEquals("[origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey]", response.getHeaders().getFirst("Access-Control-Allow-Headers")); + assertEquals("[*]", response.getHeaders().getFirst("Access-Control-Allow-Origin")); + assertEquals("[true]", response.getHeaders().getFirst("Access-Control-Allow-Credentials")); + assertEquals("[default-src 'self']", response.getHeaders().getFirst("Content-Security-Policy")); + assertEquals("[max-age=31536000; includeSubDomains]", response.getHeaders().getFirst("Strict-Transport-Security")); + assertEquals("[0]", response.getHeaders().getFirst("Expires")); + assertEquals("DENY", response.getHeaders().getFirst("X-Frame-Options")); + assertEquals("private, max-age=300", response.getHeaders().getFirst("Cache-Control")); + assertEquals("[1; mode=block]", response.getHeaders().getFirst("X-XSS-Protection")); + assertEquals("[nosniff]", response.getHeaders().getFirst("X-Content-Type-Options")); + } + + private String getInvalidBodyForUpdatePartition() { + StringBuffer sb = new StringBuffer(); + sb.append("{\n"); + sb.append(" \"properties\": {") + .append("\"elasticPassword\": {\"sensitive\":true,\"value\":\"test-password\"},") + .append("\"serviceBusConnection\": {\"sensitive\":true,\"value\":\"test-service-bus-connection\"},") + .append("\"complianceRuleSet\": {\"value\":\"shared\"},") + .append("\"id\": {\"value\":\"test-id\"}") + .append("}\n") + .append("}"); + return sb.toString(); + } + + private String getValidBodyForUpdatePartition() { + StringBuffer sb = new StringBuffer(); + sb.append("{\n"); + sb.append(" \"properties\": {") + .append("\"updateElasticPassword\": {\"sensitive\":true,\"value\":\"test-password\"},") + .append("\"serviceBusConnection\": {\"sensitive\":true,\"value\":\"test-service-bus-connection-update\"},") + .append("\"complianceRuleSet\": {\"value\":\"shared\"}") + .append("}\n") + .append("}"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/descriptor/UpdatePartitionDescriptor.java b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/descriptor/UpdatePartitionDescriptor.java new file mode 100644 index 000000000..3f3861710 --- /dev/null +++ b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/api/descriptor/UpdatePartitionDescriptor.java @@ -0,0 +1,31 @@ +package org.opengroup.osdu.partition.api.descriptor; + +import org.opengroup.osdu.partition.util.RestDescriptor; +import org.springframework.web.bind.annotation.RequestMethod; + +public class UpdatePartitionDescriptor extends RestDescriptor { + + + @Override + public String getPath() { + return "api/partition/v1/partitions/" + this.arg(); + } + + @Override + public String getHttpMethod() { + return RequestMethod.PATCH.toString(); + } + + @Override + public String getValidBody() { + StringBuffer sb = new StringBuffer(); + sb.append("{\n"); + sb.append(" \"properties\": {") + .append("\"elasticPassword\": {\"sensitive\":true,\"value\":\"test-password\"},") + .append("\"serviceBusConnection\": {\"sensitive\":true,\"value\":\"test-service-bus-connection\"},") + .append("\"complianceRuleSet\": {\"value\":\"shared\"}") + .append("}\n") + .append("}"); + return sb.toString(); + } +} diff --git a/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/util/TestUtils.java b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/util/TestUtils.java index 2c08b0f39..f776370cb 100644 --- a/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/util/TestUtils.java +++ b/testing/partition-test-core/src/main/java/org/opengroup/osdu/partition/util/TestUtils.java @@ -18,6 +18,9 @@ package org.opengroup.osdu.partition.util; import static org.junit.Assert.assertEquals; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.HttpURLConnection; import java.net.URL; import java.security.SecureRandom; import java.security.cert.X509Certificate; @@ -127,6 +130,28 @@ public abstract class TestUtils { HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } catch (Exception e) { } + allowMethods("PATCH"); return Client.create(); } + + private static void allowMethods(String... methods) { + try { + Field methodsField = HttpURLConnection.class.getDeclaredField("methods"); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL); + + methodsField.setAccessible(true); + + String[] oldMethods = (String[]) methodsField.get(null); + Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods)); + methodsSet.addAll(Arrays.asList(methods)); + String[] newMethods = methodsSet.toArray(new String[0]); + + methodsField.set(null/*static field*/, newMethods); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } } \ No newline at end of file -- GitLab