Commit c5e5a8f8 authored by ethiraj krishnamanaidu's avatar ethiraj krishnamanaidu
Browse files

Merge branch 'patch_api' into 'master'

Patch api for Partition service

See merge request !19
parents 12ab92bc fbeccdc6
Pipeline #17163 passed with stages
in 19 minutes and 49 seconds
......@@ -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'
......
......@@ -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
......
......@@ -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.
```
......
......@@ -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) {
......
......@@ -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);
......
......@@ -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> {
}
......@@ -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);
......
......@@ -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";
......
......@@ -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());
}
......
......@@ -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) {
......
......@@ -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**
......
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();
}
}
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();
}
}
......@@ -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
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);
}
}
}
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);
}
}
......@@ -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) {
......
......@@ -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);
......
......@@ -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);