From ebc60f01338b318ce2d0042c568c4c5ce595cd42 Mon Sep 17 00:00:00 2001 From: seanbeirne Date: Tue, 5 Aug 2025 10:53:44 +0100 Subject: [PATCH] Introduce GET operation in ProvMnS - Moved code gen for ProvMns from ncmp-rest to ncmp-service package - Created new util class for provmns operations - Fixed legacy naming of methods Issue-ID: CPS-2703 Change-Id: I665a227e10c019d4dbc5d585cd65f5094de0d825 Signed-off-by: seanbeirne --- cps-ncmp-rest/pom.xml | 28 ------- .../org/onap/cps/ncmp/rest/controller/ProvMnS.java | 77 +++++++++--------- .../ncmp/rest/controller/ProvMnsController.java | 90 ++++++++-------------- .../ncmp/rest/util/ProvMnSParametersMapper.java | 81 +++++++++++++++++++ .../ncmp/rest/util/ProvMnsRequestParameters.java | 12 ++- .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 12 +++ .../rest/controller/ProvMnsControllerSpec.groovy | 77 ++++++++++++------ .../rest/util/ProvMnSParametersMapperSpec.groovy | 63 +++++++++++++++ .../.openapi-generator-ignore-provmns | 28 +++---- cps-ncmp-service/pom.xml | 44 +++++++++++ .../onap/cps/ncmp/impl/data/DmiDataOperations.java | 8 +- .../impl/datajobs/DmiSubJobRequestHandler.java | 2 +- .../org/onap/cps/ncmp/impl/dmi/DmiRestClient.java | 39 ++++++++-- .../impl/inventory/sync/DmiModelOperations.java | 2 +- .../ClassNameIdGetDataNodeSelectorParameter.java | 2 +- .../cps/ncmp/impl}/provmns/model/Resource.java | 3 +- .../ncmp/impl/data/DmiDataOperationsSpec.groovy | 12 +-- .../datajobs/DmiSubJobRequestHandlerSpec.groovy | 2 +- .../cps/ncmp/impl/dmi/DmiRestClientSpec.groovy | 6 +- .../inventory/sync/DmiModelOperationsSpec.groovy | 20 ++--- cps-parent/pom.xml | 2 +- .../onap/cps/integration/base/DmiDispatcher.groovy | 5 ++ .../ncmp/provmns/ProvMnSRestApiSpec.groovy | 9 ++- 23 files changed, 425 insertions(+), 199 deletions(-) create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapper.java create mode 100644 cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapperSpec.groovy rename {cps-ncmp-rest => cps-ncmp-service}/.openapi-generator-ignore-provmns (62%) rename {cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest => cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl}/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java (96%) rename {cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest => cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl}/provmns/model/Resource.java (93%) diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index b5a303209e..f3afd1dc53 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -180,34 +180,6 @@ - - ncmp-code-gen-provmns - - generate - - - https://forge.3gpp.org/rep/all/5G_APIs/-/raw/REL-18/TS28532_ProvMnS.yaml - org.onap.cps.ncmp.rest.provmns.controller - org.onap.cps.ncmp.rest.provmns.model - org.onap.cps.ncmp.rest.provmns.api - spring - false - - src/gen/java - java11 - true - true - true - false - true - - - Resource=org.onap.cps.ncmp.rest.provmns.model.Resource - ClassNameIdGetDataNodeSelectorParameter=org.onap.cps.ncmp.rest.provmns.model.ClassNameIdGetDataNodeSelectorParameter - - ${project.basedir}/.openapi-generator-ignore-provmns - - ncmp-inventory-openapi-yaml-gen diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java index 77109b58f3..f553b9630d 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java @@ -30,13 +30,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import java.util.List; +import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter; +import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdPatchDefaultResponse; +import org.onap.cps.ncmp.impl.provmns.model.ErrorResponseDefault; +import org.onap.cps.ncmp.impl.provmns.model.ErrorResponseGet; +import org.onap.cps.ncmp.impl.provmns.model.Resource; +import org.onap.cps.ncmp.impl.provmns.model.Scope; import org.onap.cps.ncmp.rest.model.ErrorMessage; -import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdGetDataNodeSelectorParameter; -import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdPatchDefaultResponse; -import org.onap.cps.ncmp.rest.provmns.model.ErrorResponseDefault; -import org.onap.cps.ncmp.rest.provmns.model.ErrorResponseGet; -import org.onap.cps.ncmp.rest.provmns.model.Resource; -import org.onap.cps.ncmp.rest.provmns.model.Scope; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -48,39 +48,6 @@ import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "ProvMnS", description = "Provisioning Management Service") public interface ProvMnS { - /** - * DELETE /{URI-LDN-first-part}/{className}={id} : Deletes one resource - * With HTTP DELETE one resource is deleted. The resources to be deleted is identified with the target URI. - * - * @param httpServletRequest (required) - * @return Success case "200 OK". This status code is returned, when the resource has been successfully deleted. - * The response body is empty. (status code 200) - * or Error case. (status code 200) - */ - @Operation( - operationId = "deleteMoi", - summary = "Deletes one resource", - description = "With HTTP DELETE one resource is deleted. " - + "The resources to be deleted is identified with the target URI.", - responses = { - @ApiResponse(responseCode = "200", - description = "Success case (\"200 OK\"). This status code is returned, " - + "when the resource has been successfully deleted. The response body is empty."), - @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) - }), - @ApiResponse(responseCode = "default", description = "Error case.", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) - }) - } - ) - @DeleteMapping( - value = "v1/**", - produces = { "application/json" } - ) - ResponseEntity deleteMoi(HttpServletRequest httpServletRequest); - - /** * GET /{URI-LDN-first-part}/{className}={id} : Reads one or multiple resources * With HTTP GET resources are read. The resources to be retrieved are identified with the target URI. @@ -283,4 +250,36 @@ public interface ProvMnS { @Valid @RequestBody Resource resource ); + /** + * DELETE /{URI-LDN-first-part}/{className}={id} : Deletes one resource + * With HTTP DELETE one resource is deleted. The resources to be deleted is identified with the target URI. + * + * @param httpServletRequest (required) + * @return Success case "200 OK". This status code is returned, when the resource has been successfully deleted. + * The response body is empty. (status code 200) + * or Error case. (status code 200) + */ + @Operation( + operationId = "deleteMoi", + summary = "Deletes one resource", + description = "With HTTP DELETE one resource is deleted. " + + "The resources to be deleted is identified with the target URI.", + responses = { + @ApiResponse(responseCode = "200", + description = "Success case (\"200 OK\"). This status code is returned, " + + "when the resource has been successfully deleted. The response body is empty."), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) + }) + } + ) + @DeleteMapping( + value = "v1/**", + produces = { "application/json" } + ) + ResponseEntity deleteMoi(HttpServletRequest httpServletRequest); + } \ No newline at end of file diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java index d7193f0b03..4891613cf6 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java @@ -24,91 +24,69 @@ package org.onap.cps.ncmp.rest.controller; import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.rest.provmns.model.ClassNameIdGetDataNodeSelectorParameter; -import org.onap.cps.ncmp.rest.provmns.model.Resource; -import org.onap.cps.ncmp.rest.provmns.model.Scope; +import org.onap.cps.ncmp.api.data.models.OperationType; +import org.onap.cps.ncmp.impl.dmi.DmiRestClient; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.models.RequiredDmiService; +import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter; +import org.onap.cps.ncmp.impl.provmns.model.Resource; +import org.onap.cps.ncmp.impl.provmns.model.Scope; +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; +import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper; import org.onap.cps.ncmp.rest.util.ProvMnsRequestParameters; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Slf4j @RestController @RequestMapping("${rest.api.provmns-base-path}") @RequiredArgsConstructor public class ProvMnsController implements ProvMnS { - /** - * Replaces a complete single resource or creates it if it does not exist. - * - * @param httpServletRequest URI request including path - * @param resource Resource representation of the resource to be created or replaced - * @return {@code ResponseEntity} The representation of the updated resource is returned in the response - * message body. - */ - @Override - public ResponseEntity putMoi(final HttpServletRequest httpServletRequest, final Resource resource) { - final ProvMnsRequestParameters provMnsRequestParameters = - ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } + private final AlternateIdMatcher alternateIdMatcher; + private final DmiRestClient dmiRestClient; + private final InventoryPersistence inventoryPersistence; + private final ProvMnSParametersMapper provMnsParametersMapper; - /** - * Reads one or multiple resources. - * - * @param httpServletRequest URI request including path - * @param scope Extends the set of targeted resources beyond the base - * resource identified with the authority and path component of - * the URI. - * @param filter Reduces the targeted set of resources by applying a filter to - * the scoped set of resource representations. Only resources - * representations for which the filter construct evaluates to - * "true" are targeted. - * @param attributes Attributes of the scoped resources to be returned. The - * value is a comma-separated list of attribute names. - * @param fields Attribute fields of the scoped resources to be returned. The - * value is a comma-separated list of JSON pointers to the - * attribute fields. - * @param dataNodeSelector dataNodeSelector object - * @return {@code ResponseEntity} The resources identified in the request for retrieval are returned - * in the response message body. - */ @Override public ResponseEntity getMoi(final HttpServletRequest httpServletRequest, final Scope scope, final String filter, final List attributes, final List fields, final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector) { final ProvMnsRequestParameters requestParameters = - ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle( + alternateIdMatcher.getCmHandleId(requestParameters.getAlternateId())); + provMnsParametersMapper.checkDataProducerIdentifier(yangModelCmHandle); + final UrlTemplateParameters urlTemplateParameters = provMnsParametersMapper.getUrlTemplateParameters(scope, + filter, attributes, + fields, dataNodeSelector, + yangModelCmHandle); + return dmiRestClient.synchronousGetOperation( + RequiredDmiService.DATA, urlTemplateParameters, OperationType.READ); } - /** - * Patches (Create, Update or Delete) one or multiple resources. - * - * @param httpServletRequest URI request including path - * @param resource Resource representation of the resource to be created or replaced - * @return {@code ResponseEntity} The updated resource representations are returned in the response message body. - */ @Override public ResponseEntity patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) { final ProvMnsRequestParameters requestParameters = - ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); + ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + public ResponseEntity putMoi(final HttpServletRequest httpServletRequest, final Resource resource) { + final ProvMnsRequestParameters provMnsRequestParameters = + ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } - /** - * Delete one or multiple resources. - * - * @param httpServletRequest URI request including path - * @return {@code ResponseEntity} The response body is empty, HTTP status returned. - */ @Override public ResponseEntity deleteMoi(final HttpServletRequest httpServletRequest) { final ProvMnsRequestParameters requestParameters = - ProvMnsRequestParameters.toProvMnsRequestParameters(httpServletRequest); + ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapper.java new file mode 100644 index 0000000000..1f7c707a2c --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapper.java @@ -0,0 +1,81 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.util; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.exceptions.NcmpException; +import org.onap.cps.ncmp.impl.dmi.DmiServiceAuthenticationProperties; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter; +import org.onap.cps.ncmp.impl.provmns.model.Scope; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProvMnSParametersMapper { + + private final DmiServiceAuthenticationProperties dmiServiceAuthenticationProperties; + + /** + * Creates a UrlTemplateParameters object containing the relevant fields for a get. + * + * @param scope Provided className parameter. + * @param filter Filter string. + * @param attributes Attributes List. + * @param fields Fields list + * @param dataNodeSelector dataNodeSelector parameter + * @param yangModelCmHandle yangModelCmHandle object for resolved alternate ID + * @return UrlTemplateParameters object. + */ + public UrlTemplateParameters getUrlTemplateParameters(final Scope scope, final String filter, + final List attributes, final List fields, + final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector, + final YangModelCmHandle yangModelCmHandle) { + + return RestServiceUrlTemplateBuilder.newInstance() + .queryParameter("scopeType", scope.getScopeType() != null + ? scope.getScopeType().getValue() : null) + .queryParameter("scopeLevel", scope.getScopeLevel() != null + ? scope.getScopeLevel().toString() : null) + .queryParameter("filter", filter) + .queryParameter("attributes", attributes != null ? attributes.toString() : null) + .queryParameter("fields", fields != null ? fields.toString() : null) + .queryParameter("dataNodeSelector", dataNodeSelector.getDataNodeSelector() != null + ? dataNodeSelector.getDataNodeSelector() : null) + .createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(), "ProvMnS"); + } + + /** + * Check if dataProducerIdentifier is empty or null, if so throw exception. + * + * @param yangModelCmHandle given yangModelCmHandle. + */ + public void checkDataProducerIdentifier(final YangModelCmHandle yangModelCmHandle) { + if (yangModelCmHandle.getDataProducerIdentifier() == null + || yangModelCmHandle.getDataProducerIdentifier().isEmpty()) { + throw new NcmpException("No data producer identifier registered for cm handle", + "Cm Handle " + yangModelCmHandle.getId() + " has empty data producer identifier"); + } + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java index 3de9a4461a..3e5d0db5cf 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java @@ -35,13 +35,23 @@ public class ProvMnsRequestParameters { private static final String PROVMNS_BASE_PATH = "ProvMnS/v\\d+/"; + /** + * Gets alternate id from combining URI-LDN-First-Part, className and Id. + * + * @return String of Alternate Id. + */ + public String getAlternateId() { + return uriLdnFirstPart + "/" + className + "=" + id; + } + /** * Converts HttpServletRequest to ProvMnsRequestParameters. * * @param httpServletRequest HttpServletRequest object containing the path * @return ProvMnsRequestParameters object containing parsed parameters */ - public static ProvMnsRequestParameters toProvMnsRequestParameters(final HttpServletRequest httpServletRequest) { + public static ProvMnsRequestParameters extractProvMnsRequestParameters( + final HttpServletRequest httpServletRequest) { final String uriPath = (String) httpServletRequest.getAttribute( "org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping"); diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index f1f4c9195d..3a3ef953a7 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -35,7 +35,9 @@ import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade +import org.onap.cps.ncmp.impl.dmi.DmiRestClient import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException import org.onap.cps.ncmp.rest.util.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper @@ -45,6 +47,7 @@ import org.onap.cps.api.exceptions.AlreadyDefinedException import org.onap.cps.api.exceptions.CpsException import org.onap.cps.api.exceptions.DataNodeNotFoundException import org.onap.cps.api.exceptions.DataValidationException +import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper import org.onap.cps.ncmp.rest.util.RestOutputCmHandleMapper import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -111,6 +114,15 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean RestOutputCmHandleMapper mockRestOutputCmHandleMapper = Mock() + @SpringBean + ProvMnSParametersMapper provMnSParametersMapper = Mock() + + @SpringBean + AlternateIdMatcher alternateIdMatcher = Mock() + + @SpringBean + DmiRestClient dmiRestClient = Mock() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy index d87dc806b3..4b468e008e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy @@ -21,13 +21,22 @@ package org.onap.cps.ncmp.rest.controller import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.rest.provmns.model.ResourceOneOf +import jakarta.servlet.ServletException +import org.onap.cps.ncmp.impl.dmi.DmiRestClient +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher +import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf +import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity import org.springframework.test.web.servlet.MockMvc import spock.lang.Specification @@ -39,6 +48,18 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder @WebMvcTest(ProvMnsController) class ProvMnsControllerSpec extends Specification { + @SpringBean + ProvMnSParametersMapper provMnSParametersMapper = new ProvMnSParametersMapper() + + @SpringBean + AlternateIdMatcher alternateIdMatcher = Mock() + + @SpringBean + InventoryPersistence inventoryPersistence = Mock() + + @SpringBean + DmiRestClient dmiRestClient = Mock() + @Autowired MockMvc mvc @@ -47,37 +68,45 @@ class ProvMnsControllerSpec extends Specification { @Value('${rest.api.provmns-base-path}') def provMnSBasePath - def 'Get Resource Data from provmns interface.'() { + def 'Get Resource Data from provmns interface #scenario.'() { given: 'resource data url' - def getUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" + def getUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"+path + and: 'request classes return correct information' + inventoryPersistence.getYangModelCmHandle("cm-1") >> new YangModelCmHandle(dmiServiceName: "someDmiService", dataProducerIdentifier: 'someUriLdnFirstPart/someClassName=someId') + alternateIdMatcher.getCmHandleId("someUriLdnFirstPart/someClassName=someId") >> "cm-1" + dmiRestClient.synchronousGetOperation(*_) >> new ResponseEntity(HttpStatusCode.valueOf(200)) when: 'get data resource request is performed' def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response - then: 'response status is Not Implemented (501)' - assert response.status == HttpStatus.NOT_IMPLEMENTED.value() + then: 'response status is OK (200)' + assert response.status == HttpStatus.OK.value() + where: + scenario | path + 'with no query params' | '' + 'with query params' | '?attributes=[test,query,param]' } - def 'Put Resource Data from provmns interface.'() { + def 'Patch Resource Data from provmns interface.'() { given: 'resource data url' - def putUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" + def patchUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId" and: 'an example resource json object' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) - when: 'put data resource request is performed' - def response = mvc.perform(put(putUrl) - .contentType(MediaType.APPLICATION_JSON) + when: 'patch data resource request is performed' + def response = mvc.perform(patch(patchUrl) + .contentType(new MediaType('application', 'json-patch+json')) .content(jsonBody)) .andReturn().response then: 'response status is Not Implemented (501)' assert response.status == HttpStatus.NOT_IMPLEMENTED.value() } - def 'Patch Resource Data from provmns interface.'() { + def 'Put Resource Data from provmns interface.'() { given: 'resource data url' - def patchUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" + def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId" and: 'an example resource json object' def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) - when: 'patch data resource request is performed' - def response = mvc.perform(patch(patchUrl) - .contentType(new MediaType('application', 'json-patch+json')) + when: 'put data resource request is performed' + def response = mvc.perform(put(putUrl) + .contentType(MediaType.APPLICATION_JSON) .content(jsonBody)) .andReturn().response then: 'response status is Not Implemented (501)' @@ -86,19 +115,23 @@ class ProvMnsControllerSpec extends Specification { def 'Delete Resource Data from provmns interface.'() { given: 'resource data url' - def deleteUrl = "$provMnSBasePath/v1/A=1/B=2/C=3" + def deleteUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId" when: 'delete data resource request is performed' def response = mvc.perform(delete(deleteUrl)).andReturn().response then: 'response status is Not Implemented (501)' assert response.status == HttpStatus.NOT_IMPLEMENTED.value() } - def 'Get Resource Data from provmns interface with query param.'() { - given: 'resource data url with query parameter' - def getUrl = "$provMnSBasePath/v1/A=1/B=2/C=3?attributes=[test,query,param]" + def 'Invalid path passed in to provmns interface, #scenario'() { + given: 'an invalid path' + def url = "$provMnSBasePath/v1/" + invalidPath when: 'get data resource request is performed' - def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response - then: 'response status is Not Implemented (501)' - assert response.status == HttpStatus.NOT_IMPLEMENTED.value() + mvc.perform(get(url).contentType(MediaType.APPLICATION_JSON)) + then: 'invalid path exception is thrown' + thrown(ServletException) + where: + scenario | invalidPath + 'Missing URI-LDN-first-part' | 'someClassName=someId' + 'Missing ClassName and Id' | 'someUriLdnFirstPart/' } } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapperSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapperSpec.groovy new file mode 100644 index 0000000000..ee8a961838 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/ProvMnSParametersMapperSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.cps.ncmp.rest.util + +import org.onap.cps.ncmp.api.exceptions.NcmpException +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter +import org.onap.cps.ncmp.impl.provmns.model.Scope +import spock.lang.Specification + +class ProvMnSParametersMapperSpec extends Specification{ + + def objectUnderTest = new ProvMnSParametersMapper() + + def 'Extract url template parameters for GET'() { + when:'a set of given parameters from a call are passed in' + def result = objectUnderTest.getUrlTemplateParameters(new Scope(scopeLevel: 1, scopeType: 'BASE_ALL'), + 'some-filter', ['some-attribute'], ['some-field'], new ClassNameIdGetDataNodeSelectorParameter(dataNodeSelector: 'some-dataSelector'), + new YangModelCmHandle(dmiServiceName: 'some-dmi-service')) + then:'verify object has been mapped correctly' + result.urlVariables().get('filter') == 'some-filter' + } + + def 'Data Producer Identifier validation.'() { + given:'a yangModelCmHandle' + def yangModelCmHandle = new YangModelCmHandle(dataProducerIdentifier: 'some-dataProducer-ID') + when:'a yangModelCmHandle is passed in' + def result = objectUnderTest.checkDataProducerIdentifier(yangModelCmHandle) + then: 'no exception thrown for yangModelCmHandle when a data producer is present' + noExceptionThrown() + } + + def 'Data Producer Identifier validation with #scenario.'() { + given:'a yangModelCmHandle' + def yangModelCmHandle = new YangModelCmHandle(dataProducerIdentifier: dataProducerId) + when:'a data producer identifier is checked' + def result = objectUnderTest.checkDataProducerIdentifier(yangModelCmHandle) + then: 'exception thrown' + thrown(NcmpException) + where: + scenario | dataProducerId + 'null' | null + 'blank' | '' + + } +} diff --git a/cps-ncmp-rest/.openapi-generator-ignore-provmns b/cps-ncmp-service/.openapi-generator-ignore-provmns similarity index 62% rename from cps-ncmp-rest/.openapi-generator-ignore-provmns rename to cps-ncmp-service/.openapi-generator-ignore-provmns index 2c7ad93b7e..826c8b1371 100644 --- a/cps-ncmp-rest/.openapi-generator-ignore-provmns +++ b/cps-ncmp-service/.openapi-generator-ignore-provmns @@ -1,17 +1,17 @@ # Ignore generation of all the models for ProvMns -target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/*.java -target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/api/*.java +target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/*.java +target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/api/*.java # Allow generation of the below model for ProvMns -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ClassNameIdPatchDefaultResponse.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponseDefault.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponseDefaultOtherProblemsInner.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponseGet.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponseGetOtherProblemsInner.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponsePatch.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ErrorResponsePatchOtherProblemsInner.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/PatchItem.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/PatchOperation.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ResourceOneOf.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/Scope.java -!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/rest/provmns/model/ScopeType.java \ No newline at end of file +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ClassNameIdPatchDefaultResponse.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponseDefault.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponseDefaultOtherProblemsInner.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponseGet.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponseGetOtherProblemsInner.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponsePatch.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ErrorResponsePatchOtherProblemsInner.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/PatchItem.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/PatchOperation.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ResourceOneOf.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/Scope.java +!target/generated-sources/openapi/src/gen/java/org/onap/cps/ncmp/impl/provmns/model/ScopeType.java \ No newline at end of file diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 10595adc37..5b8e9a7111 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -129,5 +129,49 @@ jakarta.servlet jakarta.servlet-api + + io.swagger.core.v3 + swagger-annotations + + + + + + org.openapitools + openapi-generator-maven-plugin + 7.12.0 + + + ncmp-code-gen-provmns + + generate + + + https://forge.3gpp.org/rep/all/5G_APIs/-/raw/REL-18/TS28532_ProvMnS.yaml + org.onap.cps.ncmp.impl.provmns.controller + org.onap.cps.ncmp.impl.provmns.model + org.onap.cps.ncmp.impl.provmns.api + spring + false + + src/gen/java + java11 + true + true + true + false + true + + + Resource=org.onap.cps.ncmp.impl.provmns.model.Resource + ClassNameIdGetDataNodeSelectorParameter=org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter + + ${project.basedir}/.openapi-generator-ignore-provmns + + + + + + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java index 9b0dc9f88e..3f49aff3b8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java @@ -103,7 +103,7 @@ public class DmiDataOperations { final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(cmResourceAddress .datastoreName(), yangModelCmHandle, cmResourceAddress.resourceIdentifier(), options, topic); - return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody, READ, + return dmiRestClient.asynchronousPostOperation(DATA, urlTemplateParameters, jsonRequestBody, READ, authorization); } @@ -127,7 +127,7 @@ public class DmiDataOperations { final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters( PASSTHROUGH_OPERATIONAL.getDatastoreName(), yangModelCmHandle, "/", options, null); - return dmiRestClient.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody, READ, + return dmiRestClient.synchronousPostOperation(DATA, urlTemplateParameters, jsonRequestBody, READ, DmiRestClient.NO_AUTHORIZATION); } @@ -192,7 +192,7 @@ public class DmiDataOperations { final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters( PASSTHROUGH_RUNNING.getDatastoreName(), yangModelCmHandle, resourceId, null, null); - return dmiRestClient.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, jsonRequestBody, + return dmiRestClient.synchronousPostOperation(DATA, urlTemplateParameters, jsonRequestBody, operationType, authorization); } @@ -284,7 +284,7 @@ public class DmiDataOperations { final List dmiDataOperations = entry.getValue(); final String dmiDataOperationRequestAsJsonString = createDmiDataOperationRequestAsJsonString(dmiDataOperations); - return dmiRestClient.asynchronousPostOperationWithJsonData(DATA, urlTemplateParameters, + return dmiRestClient.asynchronousPostOperation(DATA, urlTemplateParameters, dmiDataOperationRequestAsJsonString, READ, authorization) .then() .onErrorResume(DmiClientRequestException.class, dmiClientRequestException -> { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java index a8edbd1b0e..fd9707eb06 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java @@ -73,7 +73,7 @@ public class DmiSubJobRequestHandler { final UrlTemplateParameters urlTemplateParameters = getUrlTemplateParameters(dataJobMetadata.destination(), producerKey); - final ResponseEntity responseEntity = dmiRestClient.synchronousPostOperationWithJsonData( + final ResponseEntity responseEntity = dmiRestClient.synchronousPostOperation( RequiredDmiService.DATA, urlTemplateParameters, jsonObjectMapper.asJsonString(subJobWriteRequest), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java index ceed4899bc..b0e2b776ab 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java @@ -34,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.OperationType; import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException; import org.onap.cps.ncmp.impl.models.RequiredDmiService; +import org.onap.cps.ncmp.impl.provmns.model.Resource; import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.beans.factory.annotation.Qualifier; @@ -77,14 +78,13 @@ public class DmiRestClient { * @return ResponseEntity containing the response from the DMI. * @throws DmiClientRequestException If there is an error during the DMI request. */ - public ResponseEntity synchronousPostOperationWithJsonData(final RequiredDmiService requiredDmiService, - final UrlTemplateParameters - urlTemplateParameters, - final String requestBodyAsJsonString, - final OperationType operationType, - final String authorization) { + public ResponseEntity synchronousPostOperation(final RequiredDmiService requiredDmiService, + final UrlTemplateParameters urlTemplateParameters, + final String requestBodyAsJsonString, + final OperationType operationType, + final String authorization) { final Mono> responseEntityMono = - asynchronousPostOperationWithJsonData(requiredDmiService, + asynchronousPostOperation(requiredDmiService, urlTemplateParameters, requestBodyAsJsonString, operationType, @@ -103,7 +103,7 @@ public class DmiRestClient { * @param authorization The authorization token to be added to the request headers. * @return A Mono emitting the response entity containing the server's response. */ - public Mono> asynchronousPostOperationWithJsonData(final RequiredDmiService + public Mono> asynchronousPostOperation(final RequiredDmiService requiredDmiService, final UrlTemplateParameters urlTemplateParameters, @@ -120,6 +120,29 @@ public class DmiRestClient { .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName())); } + /** + * Sends a synchronous (blocking) GET operation to the DMI. + * + * @param requiredDmiService Determines if the required service is for a data or model operation. + * @param urlTemplateParameters The DMI resource URL template with variables. + * @param operationType The type of operation being executed (for error reporting only). + * @return ResponseEntity containing the response from the DMI. + * @throws DmiClientRequestException If there is an error during the DMI request. + */ + public ResponseEntity synchronousGetOperation(final RequiredDmiService requiredDmiService, + final UrlTemplateParameters + urlTemplateParameters, + final OperationType operationType) { + return getWebClient(requiredDmiService) + .get() + .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) + .retrieve() + .toEntity(Resource.class) + .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName())) + .block(); + } + /** * Retrieves the health status of the DMI plugin. * This method performs an HTTP GET request to the DMI health check endpoint specified by the URL template diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java index 4843b8e978..bf7e80ad0e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java @@ -121,7 +121,7 @@ public class DmiModelOperations { .variablePathSegment("cmHandleId", cmHandle) .fixedPathSegment(resourceName) .createUrlTemplateParameters(dmiServiceName, dmiServiceAuthenticationProperties.getDmiBasePath()); - return dmiRestClient.synchronousPostOperationWithJsonData(MODEL, urlTemplateParameters, jsonRequestBody, READ, + return dmiRestClient.synchronousPostOperation(MODEL, urlTemplateParameters, jsonRequestBody, READ, null); } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java similarity index 96% rename from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java rename to cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java index 4b4975fd2d..64772a5e45 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/ClassNameIdGetDataNodeSelectorParameter.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.rest.provmns.model; +package org.onap.cps.ncmp.impl.provmns.model; import lombok.Getter; import lombok.Setter; diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/Resource.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/Resource.java similarity index 93% rename from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/Resource.java rename to cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/Resource.java index ab85bab1a2..9041e54ca2 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/Resource.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/provmns/model/Resource.java @@ -18,10 +18,11 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.rest.provmns.model; +package org.onap.cps.ncmp.impl.provmns.model; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf; /** * This interface serves as a replacement for the generated Resource class, which has dependencies on the NRM-related diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy index fa8a346caa..b7942dd293 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -90,7 +90,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = Mono.just(new ResponseEntity('{some-key:some-value}', HttpStatus.OK)) def expectedUrlTemplateWithVariables = getExpectedUrlTemplateWithVariables(expectedOptions, expectedDataStore) def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' - mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get resource data is invoked' def cmResourceAddress = new CmResourceAddress(expectedDataStore.datastoreName, cmHandleId, resourceIdentifier) def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, expectedOptions, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block() @@ -117,11 +117,11 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = Mono.just(new ResponseEntity(HttpStatus.ACCEPTED)) def expectedUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/data?requestId={requestId}&topic={topic}', ['requestId': 'requestId', 'topic': 'my-topic-name']) def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' - mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, _, READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, _, READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get resource data for group of cm handles is invoked' objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) then: 'the post operation was called with the expected URL and JSON request body' - 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrlTemplateWithVariables, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) + 1 * mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) } def 'Execute (async) data operation from DMI service with Exception.'() { @@ -134,7 +134,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def actualDataOperationCloudEvent = null eventsProducer.sendCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } and: 'a DMI client request exception is thrown when DMI service is called' - mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } + mockDmiRestClient.asynchronousPostOperation(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } when: 'attempt to get resource data for group of cm handles is invoked' objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) then: 'the event contains the expected error details' @@ -153,7 +153,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def responseFromDmi = new ResponseEntity(HttpStatus.OK) def expectedTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}&options={options}', ['resourceIdentifier': '/', 'datastore': 'ncmp-datastore:passthrough-operational', 'cmHandleId': cmHandleId, 'options': OPTIONS_PARAM]) def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' - mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedTemplateWithVariables, expectedJson, READ, null) >> responseFromDmi + mockDmiRestClient.synchronousPostOperation(DATA, expectedTemplateWithVariables, expectedJson, READ, null) >> responseFromDmi when: 'get resource data is invoked' def result = objectUnderTest.getAllResourceDataFromDmi(cmHandleId, NO_REQUEST_ID, OPTIONS_PARAM) then: 'the result is the response from the DMI service' @@ -168,7 +168,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def expectedUrlTemplateParameters = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}', ['resourceIdentifier': resourceIdentifier, 'datastore': 'ncmp-datastore:passthrough-running', 'cmHandleId': cmHandleId]) def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' def responseFromDmi = new ResponseEntity(HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrlTemplateParameters, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperation(DATA, expectedUrlTemplateParameters, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi when: 'write resource method is invoked' def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) then: 'the result is the response from the DMI service' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy index bc4bb1395a..b10c96ed5b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandlerSpec.groovy @@ -31,7 +31,7 @@ class DmiSubJobRequestHandlerSpec extends Specification { def responseAsKeyValuePairs = [subJobId:'my-sub-job-id'] def responseEntity = new ResponseEntity<>(responseAsKeyValuePairs, HttpStatus.OK) def expectedJson = '{"destination":"d1","dataAcceptType":"t1","dataContentType":"t2","dataProducerId":"prod1","dataJobId":"some-job-id","data":[{"path":"p","op":"operation","moduleSetTag":"tag","value":null,"operationId":"o1"}]}' - mockDmiRestClient.synchronousPostOperationWithJsonData(RequiredDmiService.DATA, _, expectedJson, OperationType.CREATE, authorization) >> responseEntity + mockDmiRestClient.synchronousPostOperation(RequiredDmiService.DATA, _, expectedJson, OperationType.CREATE, authorization) >> responseEntity when: 'sending request to DMI invoked' objectUnderTest.sendRequestsToDmi(authorization, dataJobId, dataJobMetadata, dmiWriteOperationsPerProducerKey) then: 'the result contains the expected sub-job id' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy index 261d11c09c..cd77592e43 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy @@ -78,7 +78,7 @@ class DmiRestClientSpec extends Specification { mockDataServicesWebClient.post() >> mockRequestBody mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT)) when: 'POST operation is invoked fro Data Service' - def response = objectUnderTest.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) + def response = objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) then: 'the output of the method is equal to the output from the test template' assert response.statusCode == HttpStatus.I_AM_A_TEAPOT assert response.body == 'from Data service' @@ -89,7 +89,7 @@ class DmiRestClientSpec extends Specification { mockModelServicesWebClient.post() >> mockRequestBody mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT)) when: 'POST operation is invoked for Model Service' - def response = objectUnderTest.synchronousPostOperationWithJsonData(MODEL, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) + def response = objectUnderTest.synchronousPostOperation(MODEL, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) then: 'the output of the method is equal to the output from the test template' assert response.statusCode == HttpStatus.I_AM_A_TEAPOT assert response.body == 'from Model service' @@ -100,7 +100,7 @@ class DmiRestClientSpec extends Specification { mockDataServicesWebClient.post() >> mockRequestBody mockResponse.toEntity(Object.class) >> Mono.error(exceptionType) when: 'POST operation is invoked' - objectUnderTest.synchronousPostOperationWithJsonData(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) + objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) then: 'a http client exception is thrown' def thrown = thrown(DmiClientRequestException) and: 'the exception has the relevant details from the error response' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy index 4ebfb32a78..6811ed4c8b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy @@ -58,7 +58,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { 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.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi + mockDmiRestClient.synchronousPostOperation(MODEL, expectedModulesUrlTemplateWithVariables, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG) then: 'the result consists of expected module references' @@ -71,7 +71,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { 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.synchronousPostOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperation(*_) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG) then: 'the result is empty' @@ -89,7 +89,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(additionalProperties) and: 'a positive response from DMI service when it is called with tha expected parameters' def responseFromDmi = new ResponseEntity(HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables, + mockDmiRestClient.synchronousPostOperation(MODEL, expectedModulesUrlTemplateWithVariables, '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG) @@ -108,7 +108,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { 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.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables, + mockDmiRestClient.synchronousPostOperation(MODEL, expectedModuleResourcesUrlTemplateWithVariables, '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, newModuleReferences) @@ -124,7 +124,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { 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.synchronousPostOperationWithJsonData(*_) >> responseFromDmi + mockDmiRestClient.synchronousPostOperation(*_) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, newModuleReferences) then: 'the result is empty' @@ -140,7 +140,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(additionalProperties) and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables, + mockDmiRestClient.synchronousPostOperation(MODEL, expectedModuleResourcesUrlTemplateWithVariables, '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' @@ -158,7 +158,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval([], moduleSetTag) and: 'a positive response from DMI service when it is called with the expected moduleSetTag' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables, + mockDmiRestClient.synchronousPostOperation(MODEL, expectedModuleResourcesUrlTemplateWithVariables, '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, moduleSetTag, newModuleReferences) @@ -179,7 +179,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'no resources are returned' assert result == [:] and: 'no request is sent to DMI' - 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) + 0 * mockDmiRestClient.synchronousPostOperation(*_) } def 'Retrieving yang resources from DMI with null DMI properties.'() { @@ -197,7 +197,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { when: 'get module references is called' objectUnderTest.getModuleReferences(yangModelCmHandle, 'NEW-TAG') then: 'a request was sent to DMI with the NEW module set tag in the body' - 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> { args -> + 1 * mockDmiRestClient.synchronousPostOperation(*_) >> { args -> def requestBodyAsJson = args[2] as String assert requestBodyAsJson.contains('"moduleSetTag":"NEW-TAG"') return new ResponseEntity([schemas: [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']]], HttpStatus.OK) @@ -210,7 +210,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { when: 'get new yang resources from DMI service' objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, 'NEW-TAG', newModuleReferences) then: 'a request was sent to DMI with the NEW module set tag in the body' - 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> { args -> + 1 * mockDmiRestClient.synchronousPostOperation(*_) >> { args -> def requestBodyAsJson = args[2] as String assert requestBodyAsJson.contains('"moduleSetTag":"NEW-TAG"') return new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index a0046f174e..3ceb359e56 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -448,10 +448,10 @@ org/onap/cps/rest/model/* org/onap/cps/cpspath/parser/antlr4/* org/onap/cps/ncmp/rest/model/* - org/onap/cps/ncmp/rest/provmns/model/* org/onap/cps/**/*MapperImpl.class org/onap/cps/ncmp/rest/stub/* org/onap/cps/policyexecutor/stub/model/* + org/onap/cps/ncmp/impl/provmns/model/* **/pom.xml diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy index 556495e1ea..828c374a9b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -105,6 +105,11 @@ class DmiDispatcher extends Dispatcher { case ~'^/dmi/v1/cmwriteJob(.*)$': return mockWriteJobResponse(request) + // provmns endpoint + case ~'^/ProvMnS/v1(.*)$': + dmiResourceDataUrl = request.path + return mockResponseWithBody(HttpStatus.OK, '{}') + default: throw new IllegalArgumentException('Mock DMI does not implement endpoint ' + request.path) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy index 474a480a43..f3e4cf4e4e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy @@ -21,7 +21,7 @@ package org.onap.cps.integration.functional.ncmp.provmns import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.onap.cps.ncmp.rest.provmns.model.ResourceOneOf +import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf import org.springframework.http.MediaType import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete @@ -34,9 +34,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ def 'Get Resource Data from provmns interface.'() { + given: 'a registered cm handle' + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'A=1/B=2/C=3') expect: 'not implemented response on GET endpoint' mvc.perform(get("/ProvMnS/v1/A=1/B=2/C=3")) - .andExpect(status().isNotImplemented()) + .andExpect(status().is2xxSuccessful()) + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') } def 'Put Resource Data from provmns interface.'() { -- 2.16.6