From 82ebf531110deba98086f8f7cb9c745519bbc4f4 Mon Sep 17 00:00:00 2001 From: JosephKeenan Date: Wed, 8 Dec 2021 18:16:44 +0000 Subject: Changing putOperationWithJson to postOperationWithJson -Modified responseEntity to reesponseEntity -Changed behaviour of sync to use new repsonses and removed JSON parsing -Updated tests to use new responsess -Tests have been updated and added jira to docs -Added messageConverters to RestTemplate to support plain text with ResponseEntity -Added docker log output for cps & dmi containers during CSIT teardown -Moved reponse conversion from service into DMIModelOperations class -Added response handling test (edgecases) -Updated response request body for passthrough-running CSIT test to pass Issue-ID: CPS-777 Signed-off-by: JosephKeenan Change-Id: If2acf83a97b8aad5aa2c342154d807a47cace6a0 --- .../api/impl/NetworkCmProxyDataServiceImpl.java | 51 +---------- .../cps/ncmp/api/impl/client/DmiRestClient.java | 8 +- .../ncmp/api/impl/config/NcmpConfiguration.java | 23 ++++- .../api/impl/operations/DmiDataOperations.java | 4 +- .../api/impl/operations/DmiModelOperations.java | 52 +++++++++-- ...tworkCmProxyDataServiceImplModelSyncSpec.groovy | 28 +++--- .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 5 - .../ncmp/api/impl/client/DmiRestClientSpec.groovy | 4 +- .../api/impl/config/NcmpConfigurationSpec.groovy | 17 ++-- .../impl/operations/DmiModelOperationsSpec.groovy | 101 +++++++++++++++++---- csit/plans/cps/teardown.sh | 6 ++ csit/tests/ncmp-passthrough/ncmp-passthrough.robot | 2 +- docs/release-notes.rst | 2 +- 13 files changed, 191 insertions(+), 112 deletions(-) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index faa2efe3a9..bd86cd0c33 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -28,10 +28,6 @@ import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; @@ -52,7 +48,6 @@ import org.onap.cps.ncmp.api.models.CmHandle; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.PersistenceCmHandle; import org.onap.cps.ncmp.api.models.PersistenceCmHandlesList; -import org.onap.cps.ncmp.api.models.YangResource; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; @@ -308,9 +303,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } private void syncAndCreateSchemaSet(final PersistenceCmHandle persistenceCmHandle) { - final List moduleReferencesFromCmHandle = - toModuleReferences(dmiModelOperations.getModuleReferences(persistenceCmHandle)); + dmiModelOperations.getModuleReferences(persistenceCmHandle); final List existingModuleReferences = new ArrayList<>(); final List unknownModuleReferences = new ArrayList<>(); prepareModuleSubsets(moduleReferencesFromCmHandle, existingModuleReferences, unknownModuleReferences); @@ -319,7 +313,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService if (unknownModuleReferences.isEmpty()) { newYangResourcesModuleNameToContentMap = new HashMap<>(); } else { - newYangResourcesModuleNameToContentMap = getNewYangResourcesFromDmi(persistenceCmHandle, + newYangResourcesModuleNameToContentMap = dmiModelOperations.getNewYangResourcesFromDmi(persistenceCmHandle, unknownModuleReferences); } cpsModuleService @@ -348,47 +342,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService persistenceCmHandle.getId()); } - private Map getNewYangResourcesFromDmi(final PersistenceCmHandle persistenceCmHandle, - final List unknownModuleReferences) { - final ResponseEntity responseEntity = - dmiModelOperations.getNewYangResourcesFromDmi(persistenceCmHandle, unknownModuleReferences); - - final JsonArray moduleResources = new Gson().fromJson(responseEntity.getBody(), - JsonArray.class); - final Map newYangResourcesModuleNameToContentMap = new HashMap<>(); - - for (final JsonElement moduleResource : moduleResources) { - final YangResource yangResource = toYangResource((JsonObject) moduleResource); - newYangResourcesModuleNameToContentMap.put(yangResource.getModuleName(), yangResource.getYangSource()); - } - return newYangResourcesModuleNameToContentMap; - } - - private static YangResource toYangResource(final JsonObject yangResourceAsJson) { - final YangResource yangResource = new YangResource(); - yangResource.setModuleName(yangResourceAsJson.get("moduleName").getAsString()); - yangResource.setRevision(yangResourceAsJson.get("revision").getAsString()); - yangResource.setYangSource(yangResourceAsJson.get("yangSource").getAsString()); - return yangResource; - } - private static List toModuleReferences( - final ResponseEntity dmiFetchModulesResponseEntity) { - final List moduleReferences = new ArrayList<>(); - final JsonObject bodyAsJsonObject = new Gson().fromJson(dmiFetchModulesResponseEntity.getBody(), - JsonObject.class); - final JsonArray moduleReferencesAsJson = bodyAsJsonObject.getAsJsonArray("schemas"); - for (final JsonElement moduleReferenceAsJson : moduleReferencesAsJson) { - final ModuleReference moduleReference = toModuleReference((JsonObject) moduleReferenceAsJson); - moduleReferences.add(moduleReference); - } - return moduleReferences; - } - private static ModuleReference toModuleReference(final JsonObject moduleReferenceAsJson) { - final ModuleReference moduleReference = new ModuleReference(); - moduleReference.setModuleName(moduleReferenceAsJson.get("moduleName").getAsString()); - moduleReference.setRevision(moduleReferenceAsJson.get("revision").getAsString()); - return moduleReference; - } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java index 9f3df6b9a5..94faa557fa 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java @@ -53,11 +53,11 @@ public class DmiRestClient { * @param httpHeaders http headers * @return response entity of type String */ - public ResponseEntity postOperationWithJsonData(final String dmiResourceUrl, + public ResponseEntity postOperationWithJsonData(final String dmiResourceUrl, final String jsonData, final HttpHeaders httpHeaders) { final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(httpHeaders)); - return restTemplate.postForEntity(dmiResourceUrl, httpEntity, String.class); + return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class); } private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders) { @@ -72,8 +72,8 @@ public class DmiRestClient { * @param httpHeaders http headers * @return response entity of type String */ - public ResponseEntity postOperation(final String dmiResourceUrl, final HttpHeaders httpHeaders) { + public ResponseEntity postOperation(final String dmiResourceUrl, final HttpHeaders httpHeaders) { final var httpEntity = new HttpEntity<>(configureHttpHeaders(httpHeaders)); - return restTemplate.exchange(dmiResourceUrl, HttpMethod.POST, httpEntity, String.class); + return restTemplate.exchange(dmiResourceUrl, HttpMethod.POST, httpEntity, Object.class); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java index 81c9dff402..60b44c2461 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java @@ -20,11 +20,15 @@ package org.onap.cps.ncmp.api.impl.config; +import java.util.Arrays; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @@ -42,8 +46,25 @@ public class NcmpConfiguration { private String dmiBasePath; } + /** + * Rest template bean. + * + * @param restTemplateBuilder the rest template builder + * @return rest template instance + */ @Bean + @Scope("singleton") public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) { - return restTemplateBuilder.build(); + final RestTemplate restTemplate = restTemplateBuilder.build(); + setRestTemplateMessageConverters(restTemplate); + return restTemplate; + } + + private static void setRestTemplateMessageConverters(final RestTemplate restTemplate) { + final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = + new MappingJackson2HttpMessageConverter(); + mappingJackson2HttpMessageConverter.setSupportedMediaTypes( + Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index eccb9a0987..095f677ef8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -62,7 +62,7 @@ public class DmiDataOperations extends DmiOperations { * @param dataStore data store enum * @return {@code ResponseEntity} response entity */ - public ResponseEntity getResourceDataFromDmi(final String cmHandle, + public ResponseEntity getResourceDataFromDmi(final String cmHandle, final String resourceId, final String optionsParamInQuery, final String acceptParamInHeader, @@ -93,7 +93,7 @@ public class DmiDataOperations extends DmiOperations { * @param dataType data type * @return {@code ResponseEntity} response entity */ - public ResponseEntity writeResourceDataPassThroughRunningFromDmi(final String cmHandle, + public ResponseEntity writeResourceDataPassThroughRunningFromDmi(final String cmHandle, final String resourceId, final OperationEnum operation, final String requestData, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index c582584667..f74616ab30 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -25,10 +25,14 @@ import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.models.PersistenceCmHandle; +import org.onap.cps.ncmp.api.models.YangResource; import org.onap.cps.spi.model.ModuleReference; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; @@ -55,12 +59,14 @@ public class DmiModelOperations extends DmiOperations { * @param persistenceCmHandle the persistence cm handle * @return module references */ - public ResponseEntity getModuleReferences(final PersistenceCmHandle persistenceCmHandle) { + public List getModuleReferences(final PersistenceCmHandle persistenceCmHandle) { final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() .build(); dmiRequestBody.asCmHandleProperties(persistenceCmHandle.getAdditionalProperties()); - return getResourceFromDmiWithJsonData(persistenceCmHandle.resolveDmiServiceName(MODEL), + final ResponseEntity dmiFetchModulesResponseEntity = getResourceFromDmiWithJsonData( + persistenceCmHandle.resolveDmiServiceName(MODEL), getDmiRequestBodyAsString(dmiRequestBody), persistenceCmHandle.getId(), "modules"); + return toModuleReferences((Map) dmiFetchModulesResponseEntity.getBody()); } /** @@ -68,17 +74,18 @@ public class DmiModelOperations extends DmiOperations { * * @param persistenceCmHandle the persistenceCmHandle * @param unknownModuleReferences the unknown module references - * @return yang resources + * @return yang resources as map of module name to yang(re)source */ - public ResponseEntity getNewYangResourcesFromDmi(final PersistenceCmHandle persistenceCmHandle, - final List unknownModuleReferences) { + public Map getNewYangResourcesFromDmi(final PersistenceCmHandle persistenceCmHandle, + final List unknownModuleReferences) { final String jsonDataWithDataAndCmHandleProperties = getRequestBodyToFetchYangResources( unknownModuleReferences, persistenceCmHandle.getAdditionalProperties()); - return getResourceFromDmiWithJsonData( + final ResponseEntity responseEntity = getResourceFromDmiWithJsonData( persistenceCmHandle.resolveDmiServiceName(MODEL), jsonDataWithDataAndCmHandleProperties, persistenceCmHandle.getId(), "moduleResources"); + return asModuleNameToYangResourceMap(responseEntity); } /** @@ -90,7 +97,7 @@ public class DmiModelOperations extends DmiOperations { * @param resourceName name of the resource(s) * @return {@code ResponseEntity} response entity */ - private ResponseEntity getResourceFromDmiWithJsonData(final String dmiServiceName, + private ResponseEntity getResourceFromDmiWithJsonData(final String dmiServiceName, final String jsonData, final String cmHandle, final String resourceName) { @@ -122,7 +129,6 @@ public class DmiModelOperations extends DmiOperations { } private static JsonObject toJsonObject(final List cmHandleProperties) { - //TODO Toine/Joe Double check format with existing test data final JsonObject asJsonObject = new JsonObject(); for (final PersistenceCmHandle.AdditionalProperty additionalProperty : cmHandleProperties) { asJsonObject.addProperty(additionalProperty.getName(), additionalProperty.getValue()); @@ -130,4 +136,34 @@ public class DmiModelOperations extends DmiOperations { return asJsonObject; } + private List toModuleReferences(final Map dmiFetchModulesResponseAsMap) { + final List moduleReferences = new ArrayList<>(); + + if (dmiFetchModulesResponseAsMap != null) { + final List moduleReferencesAsList = (List) dmiFetchModulesResponseAsMap.get("schemas"); + if (moduleReferencesAsList != null) { + moduleReferencesAsList.forEach(moduleReferenceAsMap -> { + final ModuleReference moduleReference = + objectMapper.convertValue(moduleReferenceAsMap, ModuleReference.class); + moduleReferences.add(moduleReference); + }); + } + } + return moduleReferences; + } + + private Map asModuleNameToYangResourceMap(final ResponseEntity responseEntity) { + final Map yangResourcesModuleNameToContentMap = new HashMap<>(); + final List> yangResourcesAsList = (List) responseEntity.getBody(); + + if (yangResourcesAsList != null) { + yangResourcesAsList.forEach(yangResourceAsMap -> { + final YangResource yangResource = + objectMapper.convertValue(yangResourceAsMap, YangResource.class); + yangResourcesModuleNameToContentMap.put(yangResource.getModuleName(), + yangResource.getYangSource()); + }); + } + return yangResourcesModuleNameToContentMap; + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy index a5c1f45d54..de60a01930 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy @@ -20,15 +20,12 @@ package org.onap.cps.ncmp.api.impl - +import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.models.PersistenceCmHandle -import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.spi.model.ModuleReference -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity import spock.lang.Specification class NetworkCmProxyDataServiceImplModelSyncSpec extends Specification { @@ -38,7 +35,7 @@ class NetworkCmProxyDataServiceImplModelSyncSpec extends Specification { def mockDmiModelOperations = Mock(DmiModelOperations) def objectUnderTest = new NetworkCmProxyDataServiceImpl(null, mockDmiModelOperations, - mockCpsModuleService, null, null, mockCpsAdminService, null) + mockCpsModuleService, null, null, mockCpsAdminService, new ObjectMapper()) def expectedDataspaceName = 'NFP-Operational' @@ -50,25 +47,24 @@ class NetworkCmProxyDataServiceImplModelSyncSpec extends Specification { cmHandleForModelSync.asAdditionalProperties(additionalProperties) } and: 'dmi operations returns some module references' - def jsonData = TestUtils.getResourceFileContent('cmHandleModules.json') - def moduleReferencesFromCmHandleAsJson = new ResponseEntity(jsonData, HttpStatus.OK) - mockDmiModelOperations.getModuleReferences(cmHandleForModelSync) >> moduleReferencesFromCmHandleAsJson + def moduleReferences = [ new ModuleReference(moduleName:'module1',revision:'1'), + new ModuleReference(moduleName:'module2',revision:'2') ] + mockDmiModelOperations.getModuleReferences(cmHandleForModelSync) >> moduleReferences and: 'CPS-Core returns list of existing module resources' mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> existingModuleResourcesInCps and: 'DMI-Plugin returns resource(s) for "new" module(s)' - def moduleResources = new ResponseEntity(sdncReponseBody, HttpStatus.OK) - mockDmiModelOperations.getNewYangResourcesFromDmi(cmHandleForModelSync, [new ModuleReference('module1', '1')]) >> moduleResources + mockDmiModelOperations.getNewYangResourcesFromDmi(cmHandleForModelSync, [new ModuleReference('module1', '1')]) >> yangResourceToContentMap when: 'module sync is triggered' objectUnderTest.syncModulesAndCreateAnchor(cmHandleForModelSync) then: 'the CPS module service is called once with the correct parameters' - 1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceName, cmHandleForModelSync.getId(), expectedYangResourceToContentMap, expectedKnownModules) + 1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceName, cmHandleForModelSync.getId(), yangResourceToContentMap, expectedKnownModules) and: 'admin service create anchor method has been called with correct parameters' 1 * mockCpsAdminService.createAnchor(expectedDataspaceName, cmHandleForModelSync.getId(), cmHandleForModelSync.getId()) where: 'the following parameters are used' - scenario | additionalProperties | existingModuleResourcesInCps | sdncReponseBody || expectedYangResourceToContentMap | expectedKnownModules | expectedJsonForAdditionalProperties - 'one unknown module' | ['name1': 'value1'] | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "some yang source"}]' || [module1: 'some yang source'] | [new ModuleReference('module2', '2')] | '{"name1":"value1"}' - 'no add. properties' | [:] | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "some yang source"}]' || [module1: 'some yang source'] | [new ModuleReference('module2', '2')] | '{}' - 'additional properties is null' | null | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "some yang source"}]' || [module1: 'some yang source'] | [new ModuleReference('module2', '2')] | '{}' - 'no unknown module' | [:] | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | '[]' || [:] | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | '{}' + scenario | additionalProperties | existingModuleResourcesInCps | yangResourceToContentMap || expectedKnownModules | expectedJsonForAdditionalProperties + 'one unknown module' | ['name1': 'value1'] | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | [module1: 'some yang source'] || [new ModuleReference('module2', '2')] | '{"name1":"value1"}' + 'no add. properties' | [:] | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | [module1: 'some yang source'] || [new ModuleReference('module2', '2')] | '{}' + 'additional properties is null' | null | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | [module1: 'some yang source'] || [new ModuleReference('module2', '2')] | '{}' + 'no unknown module' | [:] | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | [:] || [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | '{}' } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index c396a2ef26..62492710e0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -22,10 +22,6 @@ package org.onap.cps.ncmp.api.impl -import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.operations.DmiRequestBody -import org.springframework.http.HttpHeaders - import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE @@ -40,7 +36,6 @@ import org.onap.cps.api.CpsModuleService import org.onap.cps.api.CpsQueryService import org.onap.cps.ncmp.api.impl.exception.NcmpException import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.springframework.http.HttpStatus diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index 8c46178ddd..389086c770 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -46,7 +46,7 @@ class DmiRestClientSpec extends Specification { def 'DMI POST operation'() { given: 'the rest template returns a valid response entity' def mockResponseEntity = Mock(ResponseEntity) - mockRestTemplate.exchange(resourceUrl, HttpMethod.POST, _ as HttpEntity, String.class) >> mockResponseEntity + mockRestTemplate.exchange(resourceUrl, HttpMethod.POST, _ as HttpEntity, Object.class) >> mockResponseEntity when: 'POST operation is invoked' def result = objectUnderTest.postOperation(resourceUrl, new HttpHeaders()) then: 'the output of the method is equal to the output from the rest template' @@ -56,7 +56,7 @@ class DmiRestClientSpec extends Specification { def 'DMI POST operation with JSON.'() { given: 'the rest template returns a valid response entity' def mockResponseEntity = Mock(ResponseEntity) - mockRestTemplate.postForEntity(resourceUrl, _ as HttpEntity, String.class) >> mockResponseEntity + mockRestTemplate.postForEntity(resourceUrl, _ as HttpEntity, Object.class) >> mockResponseEntity when: 'POST operation is invoked' def result = objectUnderTest.postOperationWithJsonData(resourceUrl, 'json-data', new HttpHeaders()) then: 'the output of the method is equal to the output from the test template' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy index 2c4ba68f29..e1aba79a50 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy @@ -22,6 +22,8 @@ package org.onap.cps.ncmp.api.impl.config import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.web.client.RestTemplateBuilder +import org.springframework.http.MediaType +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter import org.springframework.test.context.ContextConfiguration import org.springframework.web.client.RestTemplate import spock.lang.Specification @@ -33,6 +35,8 @@ class NcmpConfigurationSpec extends Specification{ @Autowired NcmpConfiguration.DmiProperties dmiProperties + def mockRestTemplateBuilder = new RestTemplateBuilder() + def 'NcmpConfiguration Construction.'() { expect: 'the system can create an instance' new NcmpConfiguration() != null @@ -45,13 +49,14 @@ class NcmpConfigurationSpec extends Specification{ } def 'Rest Template creation.'() { - given: 'a rest template builder' - def mockRestTemplateBuilder = Mock(RestTemplateBuilder) - def expectedRestTemplate = Mock(RestTemplate) - mockRestTemplateBuilder.build() >> expectedRestTemplate when: 'a rest template is created' def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder) - then: 'the rest template from the builder is returned' - assert result == expectedRestTemplate + then: 'the rest template is returned' + assert result instanceof RestTemplate + and: 'a jackson media converter has been added' + def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1) + lastMessageConverter instanceof MappingJackson2HttpMessageConverter + and: 'the jackson media converters supports the expected media types' + lastMessageConverter.getSupportedMediaTypes() == [MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN]; } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index d9d12711fb..335bc062cc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -41,8 +41,41 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @Autowired DmiModelOperations objectUnderTest - def 'Module references for a persistence cm handle #scenario.'() { - given: 'a persistence cm handle for #cmHandleId' + def 'Retrieving module references.'() { + given: 'a persistence cm handle' + mockPersistenceCmHandleRetrieval([]) + and: 'a positive response from dmi service when it is called with the expected parameters' + def moduleReferencesAsLisOfMaps = [[moduleName:'mod1',revision:'A'],[moduleName:'mod2',revision:'X']] + def responseFromDmi = new ResponseEntity([schemas:moduleReferencesAsLisOfMaps], HttpStatus.OK) + mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", + '{"cmHandleProperties":{}}', [:]) >> responseFromDmi + when: 'get module references is called' + def result = objectUnderTest.getModuleReferences(persistenceCmHandle) + then: 'the result consists of expected module references' + assert result == [new ModuleReference(moduleName:'mod1',revision:'A'), new ModuleReference(moduleName:'mod2',revision:'X')] + } + + def 'Retrieving module references edge case: #scenario.'() { + given: 'a persistence cm handle' + mockPersistenceCmHandleRetrieval([]) + and: 'any response from dmi service when it is called with the expected parameters' + // TODO (toine): production code ignores any error code from DMI, this should be improved in future + def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT) + mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + when: 'get module references is called' + def result = objectUnderTest.getModuleReferences(persistenceCmHandle) + then: 'the result is empty' + assert result == [] + where: 'the dmi response body has the following content' + scenario | bodyAsMap + 'no modules' | [schemas:[]] + 'modules null' | [schemas:null] + 'no schema' | [something:'else'] + 'no body' | null + } + + def 'Retrieving module references, additional property handling: #scenario.'() { + given: 'a persistence cm handle' mockPersistenceCmHandleRetrieval(additionalPropertiesObject) and: 'a positive response from dmi service when it is called with tha expected parameters' def responseFromDmi = new ResponseEntity(HttpStatus.OK) @@ -51,35 +84,69 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(persistenceCmHandle) then: 'the result is the response from dmi service' - assert result == responseFromDmi - where: + assert result == [] + where: 'the following additional properties are used' scenario | additionalPropertiesObject || expectedAdditionalPropertiesInRequest 'with properties' | [sampleAdditionalProperty] || '{"prop1":"val1"}' - 'with null properties' | null || "{}" - 'without properties' | [] || "{}" + 'with null properties' | null || '{}' + 'without properties' | [] || '{}' } - def 'New yang resources from dmi using persistence cm handle #scenario.'() { - given: 'a persistence cm handle for #cmHandleId' - mockPersistenceCmHandleRetrieval(additionalPropertiesObject) + def 'Retrieving yang resources.'() { + given: 'a persistence cm handle' + mockPersistenceCmHandleRetrieval([]) and: 'a positive response from dmi service when it is called with tha expected parameters' - def responseFromDmi = new ResponseEntity(HttpStatus.OK) + def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], + [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) + def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' + mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", + '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', [:]) >> responseFromDmi + when: 'get new yang resources from dmi service' + def result = objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, newModuleReferences) + then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)' + assert result.size() == 2 + assert result.get('mod1') == 'some yang source' + assert result.get('mod2') == 'other yang source' + } + + def 'Retrieving yang resources, edge case: scenario.'() { + given: 'a persistence cm handle' + mockPersistenceCmHandleRetrieval([]) + and: 'a positive response from dmi service when it is called with tha expected parameters' + // TODO (toine): production code ignores any error code from DMI, this should be improved in future + def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT) + mockDmiRestClient.postOperationWithJsonData(*_) >> responseFromDmi + when: 'get new yang resources from dmi service' + def result = objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, newModuleReferences) + then: 'the result is empty' + assert result == [:] + where: 'the dmi response body has the following content' + scenario | responseFromDmiBody + 'empty array' | [] + 'null array' | null + } + + def 'Retrieving yang resources, additional property handling #scenario.'() { + given: 'a persistence cm handle' + mockPersistenceCmHandleRetrieval(additionalPropertiesObject) + and: 'a positive response from dmi service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":'+expectedAdditionalPropertiesInRequest+'}', [:]) >> responseFromDmi when: 'get new yang resources from dmi service' def result = objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, unknownModuleReferences) then: 'the result is the response from dmi service' - assert result == responseFromDmi - where: + assert result == [mod1:'some yang source'] + where: 'the following additional properties are used' scenario | additionalPropertiesObject | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest 'with module references and properties' | [sampleAdditionalProperty] | newModuleReferences || '{"prop1":"val1"}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' 'without module references' | [sampleAdditionalProperty] | [] || '{"prop1":"val1"}' | '' 'without properties' | [] | newModuleReferences || '{}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' } - def 'New yang resources from dmi with additional properties null'() { - given: 'a persistence cm handle for #cmHandleId' + def 'Retrieving yang resources from dmi with additional properties null.'() { + given: 'a persistence cm handle' mockPersistenceCmHandleRetrieval(null) when: 'a get new yang resources from dmi is called' objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, []) @@ -87,8 +154,8 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { thrown(NullPointerException) } - def 'Json Processing Exception'() { - given: 'a persistence cm handle for #cmHandleId' + def 'Retrieving module references with Json processing exception.'() { + given: 'a persistence cm handle' mockPersistenceCmHandleRetrieval([]) and: 'a Json processing exception occurs' spyObjectMapper.writeValueAsString(_) >> {throw (new JsonProcessingException(''))} @@ -97,7 +164,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'an ncmp exception is thrown' def exceptionThrown = thrown(NcmpException) and: 'the message indicates a parsing error' - exceptionThrown.message.toLowerCase().contains("parsing error") + exceptionThrown.message.toLowerCase().contains('parsing error') } } diff --git a/csit/plans/cps/teardown.sh b/csit/plans/cps/teardown.sh index 75f0af41fc..f1939cf4b3 100755 --- a/csit/plans/cps/teardown.sh +++ b/csit/plans/cps/teardown.sh @@ -21,6 +21,12 @@ # Branched from ccsdk/distribution to this repository Feb 23, 2021 # +echo '================================== CPS-NCMP Logs ========================' +docker logs cps-and-ncmp + +echo '================================== DMI Logs =============================' +docker logs ncmp-dmi-plugin + echo 'Stopping, Removing all running containers...' docker stop $(docker ps -aq) && docker rm $(docker ps -aq) diff --git a/csit/tests/ncmp-passthrough/ncmp-passthrough.robot b/csit/tests/ncmp-passthrough/ncmp-passthrough.robot index 51eabc87e9..1673baa601 100644 --- a/csit/tests/ncmp-passthrough/ncmp-passthrough.robot +++ b/csit/tests/ncmp-passthrough/ncmp-passthrough.robot @@ -100,7 +100,7 @@ Verify update to bookstore using passthrough-running did not remove category 02 Delete Bookstore using passthrough-running for Category 01 ${uri}= Set Variable ${ncmpBasePath}/v1/ch/PNFDemo/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=stores:bookstore/categories=01 ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} - ${response}= DELETE On Session CPS_URL ${uri} headers=${headers} data='' + ${response}= DELETE On Session CPS_URL ${uri} headers=${headers} data={} Should Be Equal As Strings ${response.status_code} 204 Verify delete to bookstore using passthrough-running removed only category 01 diff --git a/docs/release-notes.rst b/docs/release-notes.rst index da96970722..0ee8b1ba80 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -26,11 +26,11 @@ This section lists the main changes & fixes merged into master (snapshot) versio Features -------- - - `CPS-559 `_ Define response objects (schemas) in cps-ncmp - `CPS-636 `_ Update operation for datastore pass through running - `CPS-638 `_ Delete operation for datastore pass through running - `CPS-741 `_ Re sync after removing cm handles + - `CPS-777 `_ Ensure all DMI operations use POST method - `CPS-780 `_ Add examples for parameters, request and response in openapi yaml for cps-core Bug Fixes -- cgit 1.2.3-korg