From 1f2e66e720678f00682e6429aab7fa62251baf29 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Wed, 16 Feb 2022 21:53:41 +0530 Subject: Async: NCMP Rest impl. including Request ID generation - Based on topic api can be sync or async - RequestId is generated using UUID - Used UriComponentBuilder to populate dmi service url. - New test cases are introduced to validate service url generation. Issue-ID: CPS-828 Signed-off-by: sourabh_sourabh Change-Id: I2696d5cb012d9274b0c1265f16aa9b2369657f05 --- .../cps/ncmp/api/NetworkCmProxyDataService.java | 8 +- .../api/impl/NetworkCmProxyDataServiceImpl.java | 63 ++++++++--- .../api/impl/operations/DmiDataOperations.java | 59 ++++------ .../api/impl/operations/DmiModelOperations.java | 5 +- .../ncmp/api/impl/operations/DmiOperations.java | 32 +----- .../ncmp/api/impl/operations/DmiRequestBody.java | 1 + .../ncmp/api/impl/utils/DmiServiceUrlBuilder.java | 124 +++++++++++++++++++++ 7 files changed, 209 insertions(+), 83 deletions(-) create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java (limited to 'cps-ncmp-service/src/main/java/org/onap/cps/ncmp') diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java index 471e97e081..d942d26c88 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java @@ -49,12 +49,14 @@ public interface NetworkCmProxyDataService { * @param resourceIdentifier resource identifier * @param acceptParamInHeader accept param * @param optionsParamInQuery options query + * @param topicParamInQuery topic name for (triggering) async responses * @return {@code Object} resource data */ Object getResourceDataOperationalForCmHandle(String cmHandleId, String resourceIdentifier, String acceptParamInHeader, - String optionsParamInQuery); + String optionsParamInQuery, + String topicParamInQuery); /** * Get resource data for data store pass-through running @@ -64,12 +66,14 @@ public interface NetworkCmProxyDataService { * @param resourceIdentifier resource identifier * @param acceptParamInHeader accept param * @param optionsParamInQuery options query + * @param topicParamInQuery topic query * @return {@code Object} resource data */ Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId, String resourceIdentifier, String acceptParamInHeader, - String optionsParamInQuery); + String optionsParamInQuery, + String topicParamInQuery); /** * Write resource data for data store pass-through running 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 1762e46287..e923ce4145 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 @@ -37,9 +37,12 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; @@ -57,6 +60,7 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -81,6 +85,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private final YangModelCmHandleRetriever yangModelCmHandleRetriever; + // valid kafka topic name regex + private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" + + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); + private static final String NO_REQUEST_ID = null; + private static final String NO_TOPIC = null; + @Override public void updateDmiRegistrationAndSyncModule(final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); @@ -104,26 +114,21 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService public Object getResourceDataOperationalForCmHandle(final String cmHandleId, final String resourceIdentifier, final String acceptParamInHeader, - final String optionsParamInQuery) { - return handleResponse(dmiDataOperations.getResourceDataFromDmi( - cmHandleId, - resourceIdentifier, - optionsParamInQuery, - acceptParamInHeader, - DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL), "Not able to get resource data."); + final String optionsParamInQuery, + final String topicParamInQuery) { + + return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader, + DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery); } @Override public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId, final String resourceIdentifier, final String acceptParamInHeader, - final String optionsParamInQuery) { - return handleResponse(dmiDataOperations.getResourceDataFromDmi( - cmHandleId, - resourceIdentifier, - optionsParamInQuery, - acceptParamInHeader, - DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING), "Not able to get resource data."); + final String optionsParamInQuery, + final String topicParamInQuery) { + return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader, + DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery); } @Override @@ -297,4 +302,32 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, yangModelCmHandle.getId(), yangModelCmHandle.getId()); } -} + + private static boolean isValidTopicName(final String topicName) { + return Strings.isNotEmpty(topicName) && TOPIC_NAME_PATTERN.matcher(topicName).matches(); + } + + private Map buildDmiResponse(final String requestId) { + final Map dmiResponseMap = new HashMap<>(); + dmiResponseMap.put("requestId", requestId); + return dmiResponseMap; + } + + private Object validateTopicNameAndGetResourceData(final String cmHandleId, + final String resourceIdentifier, + final String acceptParamInHeader, + final DmiOperations.DataStoreEnum dataStore, + final String optionsParamInQuery, + final String topicParamInQuery) { + final boolean processAsynchronously = isValidTopicName(topicParamInQuery); + if (processAsynchronously) { + final String resourceDataRequestId = UUID.randomUUID().toString(); + return ResponseEntity.status(HttpStatus.OK) + .body(buildDmiResponse(resourceDataRequestId)); + } + final ResponseEntity responseEntity = dmiDataOperations.getResourceDataFromDmi( + cmHandleId, resourceIdentifier, optionsParamInQuery, acceptParamInHeader, + dataStore, NO_REQUEST_ID, NO_TOPIC); + return handleResponse(responseEntity, "Not able to get resource data."); + } +} \ No newline at end of file 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 229d4fc917..68de9d5c6b 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 @@ -23,10 +23,10 @@ package org.onap.cps.ncmp.api.impl.operations; import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ; -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; +import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.HttpHeaders; @@ -47,8 +47,8 @@ public class DmiDataOperations extends DmiOperations { public DmiDataOperations(final YangModelCmHandleRetriever cmHandlePropertiesRetriever, final JsonObjectMapper jsonObjectMapper, final NcmpConfiguration.DmiProperties dmiProperties, - final DmiRestClient dmiRestClient) { - super(cmHandlePropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient); + final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { + super(cmHandlePropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); } /** @@ -59,25 +59,31 @@ public class DmiDataOperations extends DmiOperations { * @param resourceId resource identifier * @param optionsParamInQuery options query * @param acceptParamInHeader accept parameter - * @param dataStore data store enum + * @param dataStore data store enum + * @param requestId requestId for async responses + * @param topicParamInQuery topic name for (triggering) async responses * @return {@code ResponseEntity} response entity */ public ResponseEntity getResourceDataFromDmi(final String cmHandleId, - final String resourceId, - final String optionsParamInQuery, - final String acceptParamInHeader, - final DataStoreEnum dataStore) { + final String resourceId, + final String optionsParamInQuery, + final String acceptParamInHeader, + final DataStoreEnum dataStore, + final String requestId, + final String topicParamInQuery) { final YangModelCmHandle yangModelCmHandle = - yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId); + yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId); final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() .operation(READ) + .requestId(requestId) .build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); - final var dmiResourceDataUrl = getDmiDatastoreUrlWithOptions( - yangModelCmHandle.resolveDmiServiceName(DATA), cmHandleId, resourceId, - optionsParamInQuery, dataStore); + final var dmiResourceDataUrl = dmiServiceUrlBuilder.getDmiDatastoreUrl( + dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, + topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables( + yangModelCmHandle, cmHandleId, dataStore)); final var httpHeaders = prepareHeader(acceptParamInHeader); return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, httpHeaders); } @@ -108,33 +114,10 @@ public class DmiDataOperations extends DmiOperations { dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); final String dmiUrl = - getResourceInDataStoreUrl(yangModelCmHandle.resolveDmiServiceName(DATA), - cmHandleId, resourceId, PASSTHROUGH_RUNNING); + dmiServiceUrlBuilder.getDmiDatastoreUrl(dmiServiceUrlBuilder.populateQueryParams(resourceId, + null, null), + dmiServiceUrlBuilder.populateUriVariables(yangModelCmHandle, cmHandleId, PASSTHROUGH_RUNNING)); return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, new HttpHeaders()); } - private String getResourceInDataStoreUrl(final String dmiServiceName, - final String cmHandleId, - final String resourceId, - final DataStoreEnum dataStoreEnum) { - return getCmHandleUrl(dmiServiceName, cmHandleId) - + "data" - + URL_SEPARATOR - + "ds" - + URL_SEPARATOR - + dataStoreEnum.getValue() - + "?resourceIdentifier=" - + resourceId; - } - - private String getDmiDatastoreUrlWithOptions(final String dmiServiceName, - final String cmHandleId, - final String resourceId, - final String optionsParamInQuery, - final DataStoreEnum dataStoreEnum) { - final String resourceInDataStoreUrl = getResourceInDataStoreUrl(dmiServiceName, - cmHandleId, resourceId, dataStoreEnum); - return appendOptionsQuery(resourceInDataStoreUrl, optionsParamInQuery); - } - } 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 bfe934dfd8..d79988e2e0 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 @@ -31,6 +31,7 @@ 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.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.YangResource; import org.onap.cps.spi.model.ModuleReference; @@ -53,8 +54,8 @@ public class DmiModelOperations extends DmiOperations { public DmiModelOperations(final YangModelCmHandleRetriever dmiPropertiesRetriever, final JsonObjectMapper jsonObjectMapper, final NcmpConfiguration.DmiProperties dmiProperties, - final DmiRestClient dmiRestClient) { - super(dmiPropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient); + final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { + super(dmiPropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java index 645d9799fb..75ba91b4f7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java @@ -20,17 +20,15 @@ package org.onap.cps.ncmp.api.impl.operations; -import com.google.common.base.Strings; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; +import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; -@Slf4j @RequiredArgsConstructor @Service public class DmiOperations { @@ -39,7 +37,7 @@ public class DmiOperations { public enum DataStoreEnum { PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"), PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"); - private String value; + private final String value; DataStoreEnum(final String value) { this.value = value; @@ -50,30 +48,12 @@ public class DmiOperations { protected final JsonObjectMapper jsonObjectMapper; protected final NcmpConfiguration.DmiProperties dmiProperties; protected final DmiRestClient dmiRestClient; - - static final String URL_SEPARATOR = "/"; - - String getCmHandleUrl(final String dmiServiceName, final String cmHandle) { - return dmiServiceName - + dmiProperties.getDmiBasePath() - + URL_SEPARATOR - + "v1" - + URL_SEPARATOR - + "ch" - + URL_SEPARATOR - + cmHandle - + URL_SEPARATOR; - } + protected final DmiServiceUrlBuilder dmiServiceUrlBuilder; String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) { - return getCmHandleUrl(dmiServiceName, cmHandle) + resourceName; - } - - static String appendOptionsQuery(final String url, final String optionsParamInQuery) { - if (Strings.isNullOrEmpty(optionsParamInQuery)) { - return url; - } - return url + "&options=" + optionsParamInQuery; + return dmiServiceUrlBuilder.getCmHandleUrl() + .pathSegment("{resourceName}") + .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString(); } static HttpHeaders prepareHeader(final String acceptParam) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java index d97e90cbbe..c84e4cb870 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java @@ -58,6 +58,7 @@ public class DmiRequestBody { private String data; @JsonProperty("cmHandleProperties") private Map dmiProperties; + private String requestId; /** * Set DMI Properties by converting a list of YangModelCmHandle.Property objects. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java new file mode 100644 index 0000000000..b60aac9518 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -0,0 +1,124 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * 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.api.impl.utils; + +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA; + +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.TriConsumer; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; +import org.onap.cps.ncmp.api.impl.operations.DmiOperations; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +@Component +@RequiredArgsConstructor +public class DmiServiceUrlBuilder { + + private final NcmpConfiguration.DmiProperties dmiProperties; + + /** + * This method creates the dmi service url. + * + * @param queryParams query param map as key,value pair + * @param uriVariables uri param map as key (placeholder),value pair + * @return {@code String} dmi service url as string + */ + public String getDmiDatastoreUrl(final MultiValueMap queryParams, + final Map uriVariables) { + final UriComponentsBuilder uriComponentsBuilder = getCmHandleUrl() + .pathSegment("data") + .pathSegment("ds") + .pathSegment("{dataStore}") + .queryParams(queryParams) + .uriVariables(uriVariables); + return uriComponentsBuilder.buildAndExpand().toUriString(); + } + + /** + * This method creates the dmi service url builder object with path variables. + * + * @return {@code UriComponentsBuilder} dmi service url builder object + */ + public UriComponentsBuilder getCmHandleUrl() { + return UriComponentsBuilder.newInstance() + .path("{dmiServiceName}") + .pathSegment("{dmiBasePath}") + .pathSegment("v1") + .pathSegment("ch") + .pathSegment("{cmHandle}"); + } + + /** + * This method populates uri variables. + * + * @param yangModelCmHandle get dmi service name + * @param cmHandle cm handle name for dmi registration + * @return {@code String} dmi service url as string + */ + public Map populateUriVariables(final YangModelCmHandle yangModelCmHandle, + final String cmHandle, + final DmiOperations.DataStoreEnum dataStore) { + final Map uriVariables = new HashMap<>(); + final String dmiBasePath = dmiProperties.getDmiBasePath(); + uriVariables.put("dmiServiceName", + yangModelCmHandle.resolveDmiServiceName(DATA)); + uriVariables.put("dmiBasePath", dmiBasePath); + uriVariables.put("cmHandle", cmHandle); + uriVariables.put("dataStore", dataStore.getValue()); + return uriVariables; + } + + /** + * This method is used to populate map from query params. + * + * @param resourceId unique id of response for valid topic + * @param optionsParamInQuery options into url param + * @param topicParamInQuery topic into url param + * @return all valid query params as map + */ + public MultiValueMap populateQueryParams(final String resourceId, + final String optionsParamInQuery, + final String topicParamInQuery) { + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + getQueryParamConsumer().accept("resourceIdentifier", + resourceId, queryParams); + getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams); + if (Strings.isNotEmpty(topicParamInQuery)) { + getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); + } + return queryParams; + } + + private TriConsumer> getQueryParamConsumer() { + return (paramName, paramValue, paramMap) -> { + if (Strings.isNotEmpty(paramValue)) { + paramMap.add(paramName, paramValue); + } + }; + } +} -- cgit 1.2.3-korg