diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 89992378a94a42a0974c20c380c6751b2a720d71..05e9e5b54cd409492b7168f28c4ca52d90179361 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 d6b077a2b3e5108839de16d070c6f577e22a1181..13c47a88a266b4ac63d06d5b407bd1346c58fb63 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 e8e7b328c44a7f245af6e5fa3f968453d13a97e3..516bd50b0c34fb8aff9404ad6bbef5b61c40326d 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 0192cde508d10ad6633aef5e470c088523e0a37f..1eb890c15abca738ea5d6922e2d29d6193dc7844 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 8c56fa4106e1b8f8334e903016b11bbc55414921..8d0729704e6c46bbcaca1221719b432feab2356a 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 762074d0c7f78206e55194c78f4c64a55df70b1f..593d67c65bc08c127d0586ed709667f277a54d4f 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 7cfd3e92772551d4da147b362b8ae09631a19b9b..44a25c20112f27d83e104e2988715fbe6162979c 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 af86e74028a54b49fdcb767b2d21bfe02d564948..1cd1b2adf1e0efb18f2d7628a784bcc08ffb305a 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 7b83376d5a53c3a4b7766b62a45372a5d6b78c87..596dac8ddf2fd949da61196d990513d3a860b1df 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 71250a42a5a4d1b74f1589167b77d64237d747bf..e4fd333a89868197d56a758bac14d75bf52301db 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 72c1b9645faa30227aa83d1110fe46853cfbc360..e5753d1cbbbcb839d11efba13c10e6e67e301837 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 a8f722ce87ee4e8c86a070800041b54bff4bb5ee..11a6a706100ba0245bd97049e543998a4e8e9ce4 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 07ed510f51d535dfc40d55693dfdf57d972c9fb6..372432c05f7a9d41eb815058061afa65f0972e7b 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 9d04b6636747c837c593fa124325855a637cd9e5..5e898bb01c1ee33743ed1ab89bf6c13fe1997665 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 0000000000000000000000000000000000000000..627ae0a70c3250f345a387dd144bd8bb2745de89 --- /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 0000000000000000000000000000000000000000..00aef74c5f3649d73844d245967c933d692fabdb --- /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 5c5e0277596dceda6055fdfa4149acc61c654c62..f9b29ecf3a04ee76089d872535617f13aff9d9f6 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 841b324e3cc577f9a9d0302a4d676c40467e60a8..4b101ddda2f25c58959e6b9b7e54a52696e71f5d 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 fee46957419a0cf8d81ea629ec7c93b24b063d30..ec96e47aac01391c54eb13c33194ad2b29633903 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 a714f8e224fb87478d40c080aae6acebbcbf0d93..5db183cc3f4fdd023a1caa37575425a99a5aef4d 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 73b19d87ec21765eee9f410266139d0eeaa3f2bc..4ed1dc34a4e571a319002824a2db98e8eca966c8 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 7b42b2dc6e70d4ad58c107fb2ccc32003255964e..8643ebee2299e24487d5f22df43dbd8a4b05c25f 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 0000000000000000000000000000000000000000..31e7411b9d4b0564bc0fd48ee1b7d8f15a943dd7 --- /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 0000000000000000000000000000000000000000..bb3292d3fc502ddf78fc9cd742dcb12c535bdd14 --- /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 0000000000000000000000000000000000000000..3f3861710ad189ed035e313ad2bd3b16baf4e9e9 --- /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 2c08b0f39f98dfc5fc7f731f322f224ea15e62ae..f776370cbf416ffa701a7951c74963d8fd507038 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