diff options
46 files changed, 2069 insertions, 748 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index 07e6cf9663..4e042e97de 100644 --- a/checkstyle/pom.xml +++ b/checkstyle/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>checkstyle</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <properties> <nexusproxy>https://nexus.onap.org</nexusproxy> @@ -34,6 +34,18 @@ <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.8.2</version> + </plugin> + </plugins> + </pluginManagement> + </build> + <distributionManagement> <repository> <id>ecomp-releases</id> diff --git a/cps-application/pom.xml b/cps-application/pom.xml index 50b06b2e69..193599ff9d 100755 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-bom/pom.xml b/cps-bom/pom.xml index 3e5f70d774..e46892695e 100644 --- a/cps-bom/pom.xml +++ b/cps-bom/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-bom</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <packaging>pom</packaging> <description>This artifact contains dependencyManagement declarations of all published CPS components.</description> @@ -37,6 +37,18 @@ <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.8.2</version> + </plugin> + </plugins> + </pluginManagement> + </build> + <distributionManagement> <repository> <id>ecomp-releases</id> diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index f04213d439..d3a033f109 100755 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -18,13 +18,13 @@ ============LICENSE_END========================================================= --> <project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-dependencies</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <packaging>pom</packaging> <name>${project.groupId}:${project.artifactId}</name> @@ -48,6 +48,18 @@ <mapstruct.version>1.4.2.Final</mapstruct.version> </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.8.2</version> + </plugin> + </plugins> + </pluginManagement> + </build> + <distributionManagement> <repository> <id>ecomp-releases</id> @@ -66,14 +78,14 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> - <version>2.5.5</version> + <version>2.6.4</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> - <version>2020.0.2</version> + <version>2021.0.1</version> <type>pom</type> <scope>import</scope> </dependency> @@ -181,16 +193,6 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-api</artifactId> - <version>2.17.1</version> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-to-slf4j</artifactId> - <version>2.17.1</version> - </dependency> - <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> diff --git a/cps-events/pom.xml b/cps-events/pom.xml index b9b399c950..9bd9588271 100644 --- a/cps-events/pom.xml +++ b/cps-events/pom.xml @@ -24,7 +24,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 97305cfe98..6a700c3e12 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index fe061eaed0..573c76e4a8 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -26,7 +26,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> 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 d942d26c88..7f4c18f371 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 @@ -3,6 +3,7 @@ * Copyright (C) 2021 highstreet technologies GmbH * Modifications Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +27,7 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import java.util.Collection; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.spi.model.ModuleReference; @@ -38,8 +40,9 @@ public interface NetworkCmProxyDataService { * Registration of New CM Handles. * * @param dmiPluginRegistration Dmi Plugin Registration + * @return dmiPluginRegistrationResponse */ - void updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration); + DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration); /** * Get resource data for data store pass-through operational 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 76d4cef9e8..c3369d8439 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 @@ -3,7 +3,7 @@ * Copyright (C) 2021 highstreet technologies GmbH * Modifications Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021 Bell Canada + * Modifications Copyright (C) 2021-2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMES import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; -import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; @@ -53,11 +53,14 @@ import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations; import org.onap.cps.ncmp.api.impl.operations.DmiOperations; import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandlesList; +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; -import org.onap.cps.spi.exceptions.DataValidationException; +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.HttpStatus; @@ -92,22 +95,22 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private static final String NO_TOPIC = null; @Override - public void updateDmiRegistrationAndSyncModule(final DmiPluginRegistration dmiPluginRegistration) { + public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( + final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); - try { - if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) { - parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration); - } - if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) { - parseAndUpdateCmHandlesInDmiRegistration(dmiPluginRegistration); - } - parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration); - } catch (final JsonProcessingException | DataNodeNotFoundException e) { - final String errorMessage = String.format( - "Error occurred while processing the CM-handle registration request, caused by : [%s]", - e.getMessage()); - throw new DataValidationException(errorMessage, e.getMessage(), e); + final var dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); + dmiPluginRegistrationResponse.setRemovedCmHandles( + parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles())); + if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) { + dmiPluginRegistrationResponse.setCreatedCmHandles( + parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)); } + if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) { + dmiPluginRegistrationResponse.setUpdatedCmHandles( + networkCmProxyDataServicePropertyHandler + .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles())); + } + return dmiPluginRegistrationResponse; } @Override @@ -162,6 +165,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService /** * Retrieve cm handle details for a given cm handle. + * * @param cmHandleId cm handle identifier * @return cm handle details */ @@ -203,14 +207,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService * THis method registers a cm handle and initiates modules sync. * * @param dmiPluginRegistration dmi plugin registration information. - * @throws JsonProcessingException thrown if json is malformed or missing. + * @return cm-handle registration response for create cm-handle requests. */ - public void parseAndCreateCmHandlesInDmiRegistrationAndSyncModules( - final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException { - final YangModelCmHandlesList createdYangModelCmHandlesList = - getUpdatedYangModelCmHandlesList(dmiPluginRegistration, - dmiPluginRegistration.getCreatedCmHandles()); - registerAndSyncNewCmHandles(createdYangModelCmHandlesList); + public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules( + final DmiPluginRegistration dmiPluginRegistration) { + return dmiPluginRegistration.getCreatedCmHandles().stream() + .map(cmHandle -> + YangModelCmHandle.toYangModelCmHandle( + dmiPluginRegistration.getDmiPlugin(), + dmiPluginRegistration.getDmiDataPlugin(), + dmiPluginRegistration.getDmiModelPlugin(), cmHandle) + ) + .map(this::registerAndSyncNewCmHandle) + .collect(Collectors.toList()); } private static Object handleResponse(final ResponseEntity<?> responseEntity, @@ -224,27 +233,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } } - private void parseAndUpdateCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { - networkCmProxyDataServicePropertyHandler.updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()); - } - - private YangModelCmHandlesList getUpdatedYangModelCmHandlesList( - final DmiPluginRegistration dmiPluginRegistration, - final List<NcmpServiceCmHandle> updatedCmHandles) { - return YangModelCmHandlesList.toYangModelCmHandlesList( - dmiPluginRegistration.getDmiPlugin(), - dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), - updatedCmHandles); - } - - private void registerAndSyncNewCmHandles(final YangModelCmHandlesList yangModelCmHandlesList) { - final String cmHandleJsonData = jsonObjectMapper.asJsonString(yangModelCmHandlesList); - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, + private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) { + try { + final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}", + jsonObjectMapper.asJsonString(yangModelCmHandle)); + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, cmHandleJsonData, NO_TIMESTAMP); - - for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandlesList.getYangModelCmHandles()) { syncModulesAndCreateAnchor(yangModelCmHandle); + return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId()); + } catch (final AlreadyDefinedException alreadyDefinedException) { + return CmHandleRegistrationResponse.createFailureResponse( + yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); + } catch (final Exception exception) { + return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception); } } @@ -253,24 +254,37 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService createAnchor(yangModelCmHandle); } - private void parseAndRemoveCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { - for (final String cmHandle : dmiPluginRegistration.getRemovedCmHandles()) { + protected List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration( + final List<String> tobeRemovedCmHandles) { + final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = + new ArrayList<>(tobeRemovedCmHandles.size()); + for (final String cmHandle : tobeRemovedCmHandles) { try { - attemptToDeleteSchemaSetWithCascade(cmHandle); + deleteSchemaSetWithCascade(cmHandle); cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP); - } catch (final DataNodeNotFoundException e) { - log.warn("Datanode {} not deleted message {}", cmHandle, e.getMessage()); + cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandle)); + } catch (final DataNodeNotFoundException dataNodeNotFoundException) { + log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", + cmHandle, dataNodeNotFoundException.getMessage()); + cmHandleRegistrationResponses.add(CmHandleRegistrationResponse + .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST)); + } catch (final Exception exception) { + log.error("Unable to de-register cm-handleIdd : {} , caused by : {}", + cmHandle, exception.getMessage()); + cmHandleRegistrationResponses.add( + CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception)); } } + return cmHandleRegistrationResponses; } - private void attemptToDeleteSchemaSetWithCascade(final String schemaSetName) { + private void deleteSchemaSetWithCascade(final String schemaSetName) { try { cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, CASCADE_DELETE_ALLOWED); - } catch (final Exception e) { - log.warn("Schema set {} delete failed, reason {}", schemaSetName, e.getMessage()); + } catch (final SchemaSetNotFoundException schemaSetNotFoundException) { + log.warn("Schema set {} does not exist or already deleted", schemaSetName); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java index ca2f578f46..c838a752ec 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,15 +29,19 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP; import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; @@ -61,23 +66,31 @@ public class NetworkCmProxyDataServicePropertyHandler { * * @param ncmpServiceCmHandles collection of ncmpServiceCmHandles */ - public void updateCmHandleProperties(final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) - throws DataNodeNotFoundException { + public List<CmHandleRegistrationResponse> updateCmHandleProperties( + final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) { + final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(); for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) { + final String cmHandle = ncmpServiceCmHandle.getCmHandleID(); try { - final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, - ncmpServiceCmHandle.getCmHandleID()); + final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandle); final DataNode existingCmHandleDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); processUpdates(existingCmHandleDataNode, ncmpServiceCmHandle); + cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandle)); } catch (final DataNodeNotFoundException e) { log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", - ncmpServiceCmHandle.getCmHandleID(), - e.getMessage()); - throw e; + cmHandle, e.getMessage()); + cmHandleRegistrationResponses.add(CmHandleRegistrationResponse + .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST)); + } catch (final Exception exception) { + log.error("Unable to update dataNode for cmHandleId : {} , caused by : {}", + cmHandle, exception.getMessage()); + cmHandleRegistrationResponses.add( + CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception)); } } + return cmHandleRegistrationResponses; } private void processUpdates(final DataNode existingCmHandleDataNode, final NcmpServiceCmHandle incomingCmHandle) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java index 47062b3545..e46b9e3da5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java @@ -21,6 +21,8 @@ package org.onap.cps.ncmp.api.impl.yangmodels; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Strings; import java.util.ArrayList; @@ -41,6 +43,7 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; @Getter @Setter @NoArgsConstructor +@JsonInclude(Include.NON_NULL) public class YangModelCmHandle { private String id; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java deleted file mode 100644 index 261a0181cb..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-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.yangmodels; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import lombok.Getter; -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; - -@Getter -public class YangModelCmHandlesList { - - @JsonProperty("cm-handles") - private final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(); - - /** - * Create a YangModelCmHandleList given all service names and a collection of cmHandles. - * @param dmiServiceName the dmi service name - * @param dmiDataServiceName the dmi data service name - * @param dmiModelServiceName the dmi model service name - * @param ncmpServiceCmHandles cm handles rest model - * @return instance of YangModelCmHandleList - */ - public static YangModelCmHandlesList toYangModelCmHandlesList(final String dmiServiceName, - final String dmiDataServiceName, - final String dmiModelServiceName, - final Collection<NcmpServiceCmHandle> - ncmpServiceCmHandles) { - final YangModelCmHandlesList yangModelCmHandlesList = new YangModelCmHandlesList(); - for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) { - final YangModelCmHandle yangModelCmHandle = - YangModelCmHandle.toYangModelCmHandle( - dmiServiceName, - dmiDataServiceName, - dmiModelServiceName, - ncmpServiceCmHandle); - yangModelCmHandlesList.add(yangModelCmHandle); - } - return yangModelCmHandlesList; - } - - /** - * Add a yangModelCmHandle. - * - * @param yangModelCmHandle the yangModelCmHandle to add - */ - public void add(final YangModelCmHandle yangModelCmHandle) { - yangModelCmHandles.add(yangModelCmHandle); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java new file mode 100644 index 0000000000..e183ed114b --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java @@ -0,0 +1,86 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.models; + +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@Builder +public class CmHandleRegistrationResponse { + + private final String cmHandle; + private final Status status; + private RegistrationError registrationError; + private String errorText; + + /** + * Creates a failure response based on exception. + * + * @param cmHandle cmHandle + * @param exception exception + * @return CmHandleRegistrationResponse + */ + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) { + return CmHandleRegistrationResponse.builder() + .cmHandle(cmHandle) + .status(Status.FAILURE) + .registrationError(RegistrationError.UNKNOWN_ERROR) + .errorText(exception.getMessage()).build(); + } + + /** + * Creates a failure response based on registration error. + * + * @param cmHandle cmHandle + * @param registrationError registrationError + * @return CmHandleRegistrationResponse + */ + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, + final RegistrationError registrationError) { + return CmHandleRegistrationResponse.builder().cmHandle(cmHandle) + .status(Status.FAILURE) + .registrationError(registrationError) + .errorText(registrationError.errorText) + .build(); + } + + public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) { + return CmHandleRegistrationResponse.builder().cmHandle(cmHandle) + .status(Status.SUCCESS).build(); + } + + public enum Status { + SUCCESS, FAILURE; + } + + @RequiredArgsConstructor + public enum RegistrationError { + UNKNOWN_ERROR("00", "Unknown error"), + CM_HANDLE_ALREADY_EXIST("01", "cm-handle already exists"), + CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist"); + + public final String errorCode; + public final String errorText; + + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java new file mode 100644 index 0000000000..ce2f3e66aa --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java @@ -0,0 +1,33 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.models; + +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class DmiPluginRegistrationResponse { + private List<CmHandleRegistrationResponse> createdCmHandles; + private List<CmHandleRegistrationResponse> updatedCmHandles; + private List<CmHandleRegistrationResponse> removedCmHandles; +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index e410463afa..e7c1d0560d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +21,8 @@ package org.onap.cps.ncmp.api.impl -import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService @@ -29,14 +30,20 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { @@ -57,102 +64,54 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockYangModelCmHandleRetriever = Mock(YangModelCmHandleRetriever) def noTimestamp = null + def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def 'Register or re-register a DMI Plugin for the given cm-handle(s) with #scenario process.'() { - given: 'a registration' - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server') - ncmpServiceCmHandle.cmHandleID = '123' - ncmpServiceCmHandle.dmiProperties = [dmiProp1: 'dmiValue1', dmiProp2: 'dmiValue2'] - ncmpServiceCmHandle.publicProperties = [publicProp1: 'publicValue1', publicProp2: 'publicValue2' ] - dmiPluginRegistration.createdCmHandles = createdCmHandles - dmiPluginRegistration.updatedCmHandles = updatedCmHandles - dmiPluginRegistration.removedCmHandles = removedCmHandles - def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,' + - '"additional-properties":[{"name":"dmiProp1","value":"dmiValue1"},{"name":"dmiProp2","value":"dmiValue2"}],' + - '"public-properties":[{"name":"publicProp1","value":"publicValue1"},{"name":"publicProp2","value":"publicValue2"}]' + - '}]}' - when: 'registration is updated and modules are synced' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'save list elements is invoked with the expected parameters' - expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', - '/dmi-registry', expectedJsonData, noTimestamp) - and: 'update data node leaves is called with correct parameters' - expectedCallsToUpdateCmHandleProperty * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(updatedCmHandles) - and: 'delete schema set is invoked with the correct parameters' - expectedCallsToDeleteSchemaSetAndListElement * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'cmHandle001', CASCADE_DELETE_ALLOWED) - and: 'delete list or list element is invoked with the correct parameters' - expectedCallsToDeleteSchemaSetAndListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin', - 'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp) - where: - scenario | createdCmHandles | updatedCmHandles | removedCmHandles || expectedCallsToSaveNode | expectedCallsToDeleteSchemaSetAndListElement | expectedCallsToUpdateCmHandleProperty - 'create' | [ncmpServiceCmHandle] | [] | [] || 1 | 0 | 0 - 'update' | [] | [ncmpServiceCmHandle] | [] || 0 | 0 | 1 - 'delete' | [] | [] | cmHandlesArray || 0 | 1 | 0 - 'create, update and delete' | [ncmpServiceCmHandle] | [ncmpServiceCmHandle] | cmHandlesArray || 1 | 1 | 1 - 'no valid data' | [] | [] | [] || 0 | 0 | 0 + def 'DMI Registration: Create, Update & Delete operations are processed in the right order'() { + given: 'a registration with operations of all three types' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setRemovedCmHandles(['cmhandle-2']) + when: 'registration is processed' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + // Spock validated invocation order between multiple then blocks + then: 'cm-handles are removed first' + 1 * objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) + then: 'cm-handles are created' + 1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) + then: 'cm-handles are updated' + 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) } - def 'Register a DMI Plugin for the given cm-handle(s) without DMI properties.'() { - given: 'a registration without cm-handle properties' - NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server') - ncmpServiceCmHandle.cmHandleID = '123' - ncmpServiceCmHandle.dmiProperties = Collections.emptyMap() - ncmpServiceCmHandle.publicProperties = Collections.emptyMap() - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[],"public-properties":[]}]}' - when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'save list elements is invoked with the expected parameters' - 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', - '/dmi-registry', expectedJsonData, noTimestamp) - } + def 'DMI Registration: Response from all operations types are in response'() { + given: 'a registration with operations of all three types' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setRemovedCmHandles(['cmhandle-2']) + and: 'update cm-handles can be processed successfully' + def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')] + mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> updateResponses + and: 'create cm-handles can be processed successfully' + def createdResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-1')] + objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) >> createdResponses + and: 'delete cm-handles can be processed successfully' + def removeResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-3')] + objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) >> removeResponses + when: 'registration is processed' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'response has values from all operations' + response.getRemovedCmHandles() == removeResponses + response.getCreatedCmHandles() == createdResponses + response.getUpdatedCmHandles() == updateResponses - def 'Register a DMI Plugin for a given cm-handle(s) with JSON processing errors during process.'() { - given: 'a registration without cm-handle properties ' - NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin') - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - and: 'an json processing exception occurs' - spiedJsonObjectMapper.asJsonString(_) >> { throw (new JsonProcessingException('')) } - when: 'registration is updated and modules are synced' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a data validation exception is thrown' - thrown(DataValidationException) - } - def 'Register a DMI Plugin for the given cm-handle(s) with no data found during delete process.'() { - given: 'a registration without cm-handle properties ' - NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin') - dmiPluginRegistration.removedCmHandles = ['some cm handle'] - and: 'an json processing exception occurs during delete process' - mockCpsDataService.deleteListOrListElement(*_) >> { throw (new DataNodeNotFoundException('','')) } - when: 'registration is updated and modules are synced' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'no exception is thrown' - noExceptionThrown() } - def 'Register a DMI Plugin for the given cm-handle(s) with no schema set found during delete process.'() { - given: 'a registration' - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server') - dmiPluginRegistration.removedCmHandles = cmHandlesArray - and: 'an exception occurs during delete schema set process' - mockCpsModuleService.deleteSchemaSet(_,_,_) >> { throw (new Exception('')) } - when: 'registration is updated and modules are synced' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'delete list or list element is still called' - 1 * mockCpsDataService.deleteListOrListElement(_,_,_,_) - } - - def 'Dmi plugin registration with #scenario'() { + def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() { given: 'a registration ' - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin, - dmiDataPlugin:dmiDataPlugin) + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'update registration and sync module is called with correct DMI plugin information' objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) @@ -165,11 +124,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'data & model using same service' | '' | 'service1' | 'service1' } - def 'Invalid DMI plugin registration with #scenario'() { + def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() { given: 'a registration ' - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin, - dmiDataPlugin:dmiDataPlugin) + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'registration is called with incorrect DMI plugin information' objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) @@ -179,37 +137,251 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { and: 'registration is not called' 0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration) where: - scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails - 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' - 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' - 'null DMI plugins' | null | null | null || 'No DMI plugin service names' - 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and model Plugin'| 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' - 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' - 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' + scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails + 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' + 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' + 'null DMI plugins' | null | null | null || 'No DMI plugin service names' + 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' + 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' + 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' + } + + def 'Create CM-Handle Successfully: #scenario.'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a successful response is received' + response.getCreatedCmHandles().size() == 1 + with(response.getCreatedCmHandles().get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + and: 'save list elements is invoked with the expected parameters' + interaction { + def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}""" + 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', + '/dmi-registry', expectedJsonData, noTimestamp) + } + then: 'model sync is invoked with expected parameters' + 1 * objectUnderTest.syncModulesAndCreateAnchor(_) >> { YangModelCmHandle yangModelCmHandle -> + { + assert yangModelCmHandle.id == 'cmhandle' + assert yangModelCmHandle.dmiServiceName == 'my-server' + assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getPublicProperties()) == expectedPublicProperties + assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getDmiProperties()) == expectedDmiProperties + + } + } + where: + scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties + 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' + 'with only public properties' | [:] | ['public-key': 'public-value'] || '[]' | '[{"name":"public-key","value":"public-value"}]' + 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]' + 'without dmi & public properties' | [:] | [:] || '[]' | '[]' + } - def 'Exception thrown on CM-Handle registration update request'() { - given: 'a CM-handle registration' - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() - and: 'dmi plugin registration input update request' - def dmiPluginReg = new DmiPluginRegistration(); - dmiPluginReg.dmiPlugin = 'onap.dmap.plugin'; - dmiPluginReg.updatedCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'unknownHandle')] - and: 'update data node leaves is unable to find data node' - mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } - when: 'update dmi registration is called' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginReg) - then: 'data validation exception is thrown' - def exceptionThrown = thrown(DataValidationException.class) - assert exceptionThrown.getDetails().contains('DataNode not found') + def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() { + given: 'a registration with three cm-handles to be created' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles: [new NcmpServiceCmHandle(cmHandleID: 'cmhandle1'), + new NcmpServiceCmHandle(cmHandleID: 'cmhandle2'), + new NcmpServiceCmHandle(cmHandleID: 'cmhandle3')]) + and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' + mockCpsDataService.saveListElements(_, _, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {} + when: 'registration is updated to create cm-handles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a response is received for all cm-handles' + response.getCreatedCmHandles().size() == 3 + and: '1st and 3rd cm-handle are created successfully' + with(response.getCreatedCmHandles().get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle1' + } + with(response.getCreatedCmHandles().get(2)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle3' + } + and: '2nd cm-handle creation fails' + with(response.getCreatedCmHandles().get(1)) { + assert it.status == Status.FAILURE + assert it.registrationError == UNKNOWN_ERROR + assert it.errorText == 'Failed' + assert it.cmHandle == 'cmhandle2' + } + } + + def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')] + and: 'cm-handler registration fails: #scenario' + mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception } + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + response.getCreatedCmHandles().size() == 1 + with(response.getCreatedCmHandles().get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.registrationError == expectedError + assert it.errorText == expectedErrorText + } + and: 'model-sync is not invoked' + 0 * objectUnderTest.syncModulesAndCreateAnchor(_) + where: + scenario | exception || expectedError | expectedErrorText + 'cm-handle already exist' | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' + 'unknown exception while registering cm-handle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + } + + def 'Create CM-Handle Error Handling: Model Sync fails'() { + given: 'objects under test without disabled model sync' + def objectUnderTest = getObjectUnderTest() + and: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')] + and: 'cm-handler models sync fails' + objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') } + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + response.getCreatedCmHandles().size() == 1 + with(response.getCreatedCmHandles().get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.registrationError == UNKNOWN_ERROR + assert it.errorText == 'Model-Sync failed' + } + and: 'cm-handle is registered' + 1 * mockCpsDataService.saveListElements(*_) + } + + def 'Update CM-Handle: Update Operation Response is added to the response'() { + given: 'a registration to update CmHandles' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + updatedCmHandles: [{}]) + and: 'cm-handle updates can be processed successfully' + def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST)] + mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the response contains updateOperationResponse' + assert response.getUpdatedCmHandles().size() == 3 + assert response.getUpdatedCmHandles().containsAll(updateOperationResponse) + } + + def 'Remove CmHandle Successfully: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: '#scenario' + mockCpsModuleService.deleteSchemaSet(_, 'cmhandle', CASCADE_DELETE_ALLOWED) >> + { if (!schemaSetExist) { throw new SchemaSetNotFoundException("", "") } } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'delete list or list element is called' + 1 * mockCpsDataService.deleteListOrListElement(_, _, _, _) + and: 'successful response is received' + assert response.getRemovedCmHandles().size() == 1 + with(response.getRemovedCmHandles().get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + where: + scenario | schemaSetExist + 'schema-set exists and can be deleted successfully' | true + 'schema-set does not exist' | false + } + + def 'Remove CmHandle: All cm-handles delete requests are processed'() { + given: 'a registration with three cm-handles to be deleted' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) + and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' + mockCpsDataService.deleteListOrListElement(_, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {} + when: 'registration is updated to delete cmhandles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a response is received for all cm-handles' + response.getRemovedCmHandles().size() == 3 + and: '1st and 3rd cm-handle deletes successfully' + with(response.getRemovedCmHandles().get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle1' + } + with(response.getRemovedCmHandles().get(2)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle3' + } + and: '2nd cm-handle deletion fails' + with(response.getRemovedCmHandles().get(1)) { + assert it.status == Status.FAILURE + assert it.registrationError == UNKNOWN_ERROR + assert it.errorText == 'Failed' + assert it.cmHandle == 'cmhandle2' + } + } + + def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'schema set deletion failed with unknown error' + mockCpsModuleService.deleteSchemaSet(_, _, _) >> { throw new RuntimeException('Failed') } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'no exception is thrown' + noExceptionThrown() + and: 'cm-handle is not deleted' + 0 * mockCpsDataService.deleteListOrListElement(_, _, _, _) + and: 'a failure response is received' + assert response.getRemovedCmHandles().size() == 1 + with(response.getRemovedCmHandles().get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.errorText == 'Failed' + assert it.registrationError == UNKNOWN_ERROR + } + } + + def 'Remove CmHandle Error Handling: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'cm-handle deletion throws exception' + mockCpsDataService.deleteListOrListElement(_, _, _, _) >> { throw deleteListElementException } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'no exception is thrown' + noExceptionThrown() + and: 'a failure response is received' + assert response.getRemovedCmHandles().size() == 1 + with(response.getRemovedCmHandles().get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.registrationError == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | deleteListElementException | expectedError | expectedErrorText + 'cm-handle does not exist' | new DataNodeNotFoundException("", "", "") | CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist' + 'an unexpected exception' | new RuntimeException("Failed") | UNKNOWN_ERROR | 'Failed' } def getObjectUnderTestWithModelSyncDisabled() { - def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations, - mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler,mockYangModelCmHandleRetriever)) + def objectUnderTest = getObjectUnderTest() objectUnderTest.syncModulesAndCreateAnchor(*_) >> null return objectUnderTest } + + def getObjectUnderTest() { + return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations, + mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever)) + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy index 9b8d4ada56..f6264f4921 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +21,15 @@ package org.onap.cps.ncmp.api.impl +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status + import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification @@ -117,12 +122,53 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { given: 'cm handles request' def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: [:], dmiProperties: [:])] and: 'data node cannot be found' - mockCpsDataService.getDataNode(*_) >> { throw new DataNodeNotFoundException(dataspaceName, anchorName, cmHandleXpath) } + mockCpsDataService.getDataNode(*_) >> { throw exception } when: 'update data node leaves is called using correct parameters' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'data validation exception is thrown' - def exceptionThrown = thrown(DataValidationException.class) - assert exceptionThrown.getMessage().contains('DataNode not found') + def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'one failed registration response' + response.size() == 1 + and: 'it has expected error details' + with(response.get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.registrationError == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | exception || expectedError | expectedErrorText + 'cmhandle does not exist' | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist' + 'unexpected error' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + } + + def 'Multiple update operations in a single request'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] + and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' + mockCpsDataService.getDataNode(*_) >> cmHandleDataNode >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNode + when: 'update data node leaves is called using correct parameters' + def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'response has 3 values' + cmHandleResponseList.size() == 3 + and: 'the 1st and 3rd requests were processed successfully' + with(cmHandleResponseList.get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + with(cmHandleResponseList.get(2)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + and: 'the 2nd request failed with correct error code' + with(cmHandleResponseList.get(1)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.registrationError == CM_HANDLE_DOES_NOT_EXIST + assert it.errorText == "cm-handle does not exist" + } + then: 'the replace list method is called twice' + 2 * mockCpsDataService.replaceListContent(*_) } def convertToProperties(expectedPropertiesAfterUpdateAsMap) { @@ -133,4 +179,5 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { })) return properties } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy new file mode 100644 index 0000000000..c5ef2f446d --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy @@ -0,0 +1,68 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.models + +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError +import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status +import spock.lang.Specification + +class CmHandleRegistrationResponseSpec extends Specification { + + def 'Successful CmHandle Registration Response'() { + when: 'CMHandle response is created' + def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle') + then: 'a success response is returned' + with(cmHandleRegistrationResponse) { + assert it.cmHandle == 'cmHandle' + assert it.status == Status.SUCCESS + } + and: 'error details are null' + cmHandleRegistrationResponse.registrationError == null + cmHandleRegistrationResponse.errorText == null + } + + def 'Failed Cm Handle Registration Response: for unexpected exception'() { + when: 'CMHandle response is created for an unexpected exception' + def cmHandleRegistrationResponse = + CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error')) + then: 'the response is created with expected value' + with(cmHandleRegistrationResponse) { + assert it.registrationError == RegistrationError.UNKNOWN_ERROR + assert it.cmHandle == 'cmHandle' + assert errorText == 'unexpected error' + } + } + + def 'Failed Cm Handle Registration Response: for known error'() { + when: 'CMHandle response is created for known error' + def cmHandleRegistrationResponse = + CmHandleRegistrationResponse.createFailureResponse('cmHandle', RegistrationError.CM_HANDLE_ALREADY_EXIST) + then: 'the response is created with expected value' + with(cmHandleRegistrationResponse) { + assert it.registrationError == RegistrationError.CM_HANDLE_ALREADY_EXIST + assert it.cmHandle == 'cmHandle' + assert it.status == Status.FAILURE + assert errorText == RegistrationError.CM_HANDLE_ALREADY_EXIST.errorText + } + + } + +} diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index e03dce3db2..b76c63c6f7 100755 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> @@ -115,7 +115,7 @@ <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> - <version>2.3.3.RELEASE</version> + <version>2.6.4</version> <executions> <execution> <goals> diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml index c8b88e8aa0..514784cb32 100644 --- a/cps-path-parser/pom.xml +++ b/cps-path-parser/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index 5a21957cd4..6019197269 100755 --- a/cps-rest/pom.xml +++ b/cps-rest/pom.xml @@ -28,7 +28,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml index 6e92894fca..98a392a5c2 100644 --- a/cps-ri/pom.xml +++ b/cps-ri/pom.xml @@ -26,7 +26,7 @@ <parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.0.0-SNAPSHOT</version>
+ <version>3.1.0-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 78862d7233..bb3c2d07d4 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -56,6 +56,7 @@ import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.repository.AnchorRepository; import org.onap.cps.spi.repository.DataspaceRepository; import org.onap.cps.spi.repository.FragmentRepository; +import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -73,6 +74,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private final JsonObjectMapper jsonObjectMapper; + private final SessionManager sessionManager; + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})"; private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE = Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$"); @@ -199,6 +202,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService .collect(Collectors.toUnmodifiableList()); } + @Override + public String startSession() { + return sessionManager.startSession(); + } + + @Override + public void closeSession(final String sessionId) { + sessionManager.closeSession(sessionId); + } + private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set<String> ancestorXpath = new HashSet<>(); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java new file mode 100644 index 0000000000..eb535ecc37 --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java @@ -0,0 +1,86 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-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.spi.utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionException; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; +import org.onap.cps.spi.entities.AnchorEntity; +import org.onap.cps.spi.entities.DataspaceEntity; +import org.onap.cps.spi.entities.SchemaSetEntity; +import org.onap.cps.spi.entities.YangResourceEntity; +import org.springframework.stereotype.Component; + +@Component +public class SessionManager { + + private static SessionFactory sessionFactory; + private static Map<String, Session> sessionMap = new HashMap<>(); + + private synchronized void buildSessionFactory() { + if (sessionFactory == null) { + sessionFactory = new Configuration().configure("hibernate.cfg.xml") + .addAnnotatedClass(AnchorEntity.class) + .addAnnotatedClass(DataspaceEntity.class) + .addAnnotatedClass(SchemaSetEntity.class) + .addAnnotatedClass(YangResourceEntity.class) + .buildSessionFactory(); + } + } + + /** + * Starts a session which allows use of locks and batch interaction with the persistence service. + * + * @return Session ID string + */ + public String startSession() { + buildSessionFactory(); + final Session session = sessionFactory.openSession(); + final String sessionId = UUID.randomUUID().toString(); + sessionMap.put(sessionId, session); + session.beginTransaction(); + return sessionId; + } + + /** + * Close session. + * + * @param sessionId session ID + */ + public void closeSession(final String sessionId) { + try { + final Session currentSession = sessionMap.get(sessionId); + currentSession.getTransaction().commit(); + currentSession.close(); + } catch (final NullPointerException e) { + throw new SessionException(String.format("Session with session ID %s does not exist", sessionId)); + } catch (final HibernateException e) { + throw new SessionException(String.format("Unable to close session with session ID %s", sessionId)); + } + sessionMap.remove(sessionId); + } + +}
\ No newline at end of file diff --git a/cps-ri/src/main/resources/hibernate.cfg.xml b/cps-ri/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000000..98e6cfc5b7 --- /dev/null +++ b/cps-ri/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE hibernate-configuration PUBLIC + "-//Hibernate/Hibernate Configuration DTD 3.0//EN" + "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> + +<hibernate-configuration> + <session-factory> + <property name="hibernate.connection.driver_class">org.postgresql.Driver</property> + <property name="hibernate.connection.url">jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb</property> + <property name="hibernate.connection.username">${DB_USERNAME}</property> + <property name="hibernate.connection.password">${DB_PASSWORD}</property> + <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL82Dialect</property> + <property name="show_sql">true</property> + <property name="hibernate.hbm2ddl.auto">update</property> + </session-factory> +</hibernate-configuration>
\ No newline at end of file diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index 7166008ad3..c508762054 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -28,19 +28,20 @@ import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.spi.repository.AnchorRepository import org.onap.cps.spi.repository.DataspaceRepository import org.onap.cps.spi.repository.FragmentRepository +import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification - class CpsDataPersistenceServiceSpec extends Specification { def mockDataspaceRepository = Mock(DataspaceRepository) def mockAnchorRepository = Mock(AnchorRepository) def mockFragmentRepository = Mock(FragmentRepository) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockSessionManager = Mock(SessionManager) def objectUnderTest = new CpsDataPersistenceServiceImpl( - mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper) + mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager) def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() { @@ -49,67 +50,82 @@ class CpsDataPersistenceServiceSpec extends Specification { def myAnchorName = 'my-anchor' given: 'data node object' - def submittedDataNode = new DataNodeBuilder() - .withXpath(parentXpath) - .withLeaves(['leaf-name': 'leaf-value']) - .build() + def submittedDataNode = new DataNodeBuilder() + .withXpath(parentXpath) + .withLeaves(['leaf-name': 'leaf-value']) + .build() and: 'fragment to be updated' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - def fragmentEntity = new FragmentEntity() - fragmentEntity.setXpath(parentXpath) - fragmentEntity.setChildFragments(Collections.emptySet()) - return fragmentEntity - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + def fragmentEntity = new FragmentEntity() + fragmentEntity.setXpath(parentXpath) + fragmentEntity.setChildFragments(Collections.emptySet()) + return fragmentEntity + } and: 'data node is concurrently updated by another transaction' - mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } when: 'attempt to update data node' - objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) + objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) then: 'concurrency exception is thrown' - def concurrencyException = thrown(ConcurrencyException) - assert concurrencyException.getDetails().contains(myDataspaceName) - assert concurrencyException.getDetails().contains(myAnchorName) - assert concurrencyException.getDetails().contains(parentXpath) + def concurrencyException = thrown(ConcurrencyException) + assert concurrencyException.getDetails().contains(myDataspaceName) + assert concurrencyException.getDetails().contains(myAnchorName) + assert concurrencyException.getDetails().contains(parentXpath) } def 'Retrieving a data node with a property JSON value of #scenario'() { given: 'a fragment with a property JSON value of #scenario' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - new FragmentEntity(childFragments: Collections.emptySet(), - attributes: "{\"some attribute\": ${dataString}}") - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + new FragmentEntity(childFragments: Collections.emptySet(), + attributes: "{\"some attribute\": ${dataString}}") + } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', - 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'the leaf is of the correct value and data type' - def attributeValue = dataNode.leaves.get('some attribute') - assert attributeValue == expectedValue - assert attributeValue.class == expectedDataClass + def attributeValue = dataNode.leaves.get('some attribute') + assert attributeValue == expectedValue + assert attributeValue.class == expectedDataClass where: 'the following Data Type is passed' - scenario | dataString || expectedValue | expectedDataClass - 'just numbers' | '15174' || 15174 | Integer - 'number with dot' | '15174.32' || 15174.32 | Double - 'number with 0 value after dot' | '15174.0' || 15174.0 | Double - 'number with 0 value before dot' | '0.32' || 0.32 | Double - 'number higher than max int' | '2147483648' || 2147483648 | Long - 'just text' | '"Test"' || 'Test' | String - 'number with exponent' | '1.2345e5' || 1.2345e5 | Double - 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double - 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String - 'number as String' | '"12345"' || '12345' | String + scenario | dataString || expectedValue | expectedDataClass + 'just numbers' | '15174' || 15174 | Integer + 'number with dot' | '15174.32' || 15174.32 | Double + 'number with 0 value after dot' | '15174.0' || 15174.0 | Double + 'number with 0 value before dot' | '0.32' || 0.32 | Double + 'number higher than max int' | '2147483648' || 2147483648 | Long + 'just text' | '"Test"' || 'Test' | String + 'number with exponent' | '1.2345e5' || 1.2345e5 | Double + 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double + 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String + 'number as String' | '"12345"' || '12345' | String } def 'Retrieving a data node with invalid JSON'() { given: 'a fragment with invalid JSON' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') + } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'a data validation exception is thrown' - thrown(DataValidationException) + thrown(DataValidationException) } -} + def 'start session'() { + when: 'start session' + objectUnderTest.startSession() + then: 'the session manager method to start session is invoked' + 1 * mockSessionManager.startSession() + } + + def 'close session'() { + given: 'session ID' + def someSessionId = 'someSessionId' + when: 'close session method is called with session ID as parameter' + objectUnderTest.closeSession(someSessionId) + then: 'the session manager method to close session is invoked with parameter' + 1 * mockSessionManager.closeSession(someSessionId) + } +}
\ No newline at end of file diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy new file mode 100644 index 0000000000..c46092f075 --- /dev/null +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy @@ -0,0 +1,56 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-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.spi.utils + +import org.hibernate.SessionException +import org.onap.cps.spi.impl.CpsPersistenceSpecBase + +class SessionManagerIntegrationSpec extends CpsPersistenceSpecBase{ + + def objectUnderTest = new SessionManager(); + + def 'start session'() { + when: 'start session' + def result = objectUnderTest.startSession() + then: 'session ID is returned' + assert result instanceof String + objectUnderTest.closeSession(result) + } + + def 'close session'(){ + given: 'session Id from calling the start session method' + def sessionId = objectUnderTest.startSession() + when: 'close session method is called' + objectUnderTest.closeSession(sessionId) + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'close session that does not exist' (){ + given: 'session Id that does not exist' + def unknownSessionId = 'unknown session id' + when: 'close session method is called' + objectUnderTest.closeSession(unknownSessionId) + then: 'a session exception is thrown' + def thrown = thrown(SessionException) + assert thrown.message.contains(unknownSessionId) + } +} diff --git a/cps-ri/src/test/resources/hibernate.cfg.xml b/cps-ri/src/test/resources/hibernate.cfg.xml new file mode 100644 index 0000000000..fae9275ddc --- /dev/null +++ b/cps-ri/src/test/resources/hibernate.cfg.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE hibernate-configuration PUBLIC
+ "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
+ "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
+
+<hibernate-configuration>
+ <session-factory>
+ <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
+ <property name="hibernate.connection.url">${DB_URL}</property>
+ <property name="hibernate.connection.username">${DB_USERNAME}</property>
+ <property name="hibernate.connection.password">${DB_PASSWORD}</property>
+ <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL82Dialect</property>
+ <property name="show_sql">true</property>
+ <property name="hibernate.hbm2ddl.auto">none</property>
+ </session-factory>
+</hibernate-configuration>
\ No newline at end of file diff --git a/cps-service/pom.xml b/cps-service/pom.xml index 9c7031e2f8..aea122d176 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -28,7 +28,7 @@ <parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.0.0-SNAPSHOT</version>
+ <version>3.1.0-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index cdd417bd8d..35caf95153 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -174,4 +174,19 @@ public interface CpsDataService { */ void updateNodeLeavesAndExistingDescendantLeaves(String dataspaceName, String anchorName, String parentNodeXpath, String dataNodeUpdatesAsJson, OffsetDateTime observedTimestamp); + + /** + * Starts a session which allows use of locks and batch interaction with the persistence service. + * + * @return Session ID string + */ + String startSession(); + + /** + * Close session. + * + * @param sessionId session ID + * + */ + void closeSession(String sessionId); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index aae355d507..643614f4fb 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -109,6 +109,17 @@ public class CpsDataServiceImpl implements CpsDataService { } @Override + public String startSession() { + final String sessionId = cpsDataPersistenceService.startSession(); + return sessionId; + } + + @Override + public void closeSession(final String sessionId) { + cpsDataPersistenceService.closeSession(sessionId); + } + + @Override public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData); diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index fd658861c2..fdcf15bee6 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -148,4 +148,17 @@ public interface CpsDataPersistenceService { Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName, String cpsPath, FetchDescendantsOption fetchDescendantsOption); + /** + * Starts a session which allows use of locks and batch interaction with the persistence service. + * + * @return Session ID string + */ + String startSession(); + + /** + * Close session. + * + * @param sessionId session ID + */ + void closeSession(String sessionId); } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index 785788be90..eb06199d1b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -254,4 +254,20 @@ class CpsDataServiceImplSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext } + + def 'start session'() { + when: 'start session method is called' + objectUnderTest.startSession() + then: 'the persistence service method to start session is invoked' + 1 * mockCpsDataPersistenceService.startSession() + } + + def 'close session'(){ + given: 'session Id from calling the start session method' + def sessionId = objectUnderTest.startSession() + when: 'close session method is called' + objectUnderTest.closeSession(sessionId) + then: 'the persistence service method to close session is invoked' + 1 * mockCpsDataPersistenceService.closeSession(sessionId) + } } diff --git a/docs/admin-guide.rst b/docs/admin-guide.rst index 203151bf8f..135040faaf 100644 --- a/docs/admin-guide.rst +++ b/docs/admin-guide.rst @@ -49,8 +49,6 @@ CPS Log pattern Change logging level -------------------- -.. container:: ulist - - Curl command 1. Check current log level of "logging.level.org.onap.cps" if it is set to it's default value (INFO) .. code-block:: java diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 2fc8d7f338..983252f5af 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -15,27 +15,28 @@ info: x-logo: url: cps_logo.png servers: - - url: /cps/api +- url: /cps/api tags: - - name: cps-admin - description: cps Admin - - name: cps-data - description: cps Data +- name: cps-admin + description: cps Admin +- name: cps-data + description: cps Data paths: /v1/dataspaces: post: tags: - - cps-admin + - cps-admin summary: Create a dataspace description: Create a new dataspace operationId: createDataspace parameters: - - name: dataspace-name - in: query - description: dataspace-name - required: true - schema: - type: string + - name: dataspace-name + in: query + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace responses: "201": description: Created @@ -43,38 +44,130 @@ paths: text/plain: schema: type: string + example: my-resource + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + delete: + tags: + - cps-admin + summary: Delete a dataspace + description: Delete a dataspace + operationId: deleteDataspace + parameters: + - name: dataspace-name + in: query + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + responses: + "204": + description: No Content + content: {} "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/anchors: get: tags: - - cps-admin + - cps-admin summary: Get anchors description: "Read all anchors, given a dataspace" operationId: getAnchors parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace responses: "200": description: OK @@ -90,49 +183,68 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' - "404": - description: The specified resource was not found + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred post: tags: - - cps-admin + - cps-admin summary: Create an anchor description: Create a new anchor in the given dataspace operationId: createAnchor parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: schema-set-name - in: query - description: schema-set-name - required: true - schema: - type: string - - name: anchor-name - in: query - description: anchor-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: query + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set + - name: anchor-name + in: query + description: anchor-name + required: true + schema: + type: string + example: my-anchor responses: "201": description: Created @@ -140,44 +252,79 @@ paths: text/plain: schema: type: string + example: my-resource "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}: get: tags: - - cps-admin + - cps-admin summary: Get an anchor description: Read an anchor given an anchor name and a dataspace operationId: getAnchor parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor responses: "200": description: OK @@ -191,43 +338,61 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' - "404": - description: The specified resource was not found + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred delete: tags: - - cps-admin + - cps-admin summary: Delete an anchor description: Delete an anchor given an anchor name and a dataspace operationId: deleteAnchor parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor responses: "204": description: No Content @@ -238,38 +403,62 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/schema-sets: post: tags: - - cps-admin + - cps-admin summary: Create a schema set description: Create a new schema set in the given dataspace operationId: createSchemaSet parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: schema-set-name - in: query - description: schema-set-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: query + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set requestBody: content: multipart/form-data: @@ -283,44 +472,79 @@ paths: text/plain: schema: type: string + example: my-resource "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}: get: tags: - - cps-admin + - cps-admin summary: Get a schema set description: Read a schema set given a schema set name and a dataspace operationId: getSchemaSet parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: schema-set-name - in: path - description: schema-set-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: path + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set responses: "200": description: OK @@ -334,43 +558,61 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' - "404": - description: The specified resource was not found + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred delete: tags: - - cps-admin + - cps-admin summary: Delete a schema set description: Delete a schema set given a schema set name and a dataspace operationId: deleteSchemaSet parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: schema-set-name - in: path - description: schema-set-name - required: true - schema: - type: string + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: schema-set-name + in: path + description: schema-set-name + required: true + schema: + type: string + example: my-schema-set responses: "204": description: No Content @@ -381,59 +623,93 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden "409": description: Conflict content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node: get: tags: - - cps-data + - cps-data summary: Get a node description: Get a node with an option to retrieve all the children for a given anchor and dataspace operationId: getNodeByDataspaceAndAnchor parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: false - schema: - type: string - default: / - - name: include-descendants - in: query - description: include-descendants - required: false - schema: - type: boolean - default: false + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: include-descendants + in: query + description: include-descendants + required: false + schema: + type: boolean + example: false + default: false responses: "200": description: OK @@ -441,75 +717,100 @@ paths: application/json: schema: type: object - example: - child: my_child - leafList: "leafListElement1, leafListElement2" - leaf: my_leaf + examples: + dataSample: + $ref: '#/components/examples/dataSample' "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' - "404": - description: The specified resource was not found + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred x-codegen-request-body-name: xpath /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes: put: tags: - - cps-data + - cps-data summary: Replace a node with descendants description: "Replace a node with descendants for a given dataspace, anchor\ \ and a parent node xpath" operationId: replaceNode parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: false - schema: - type: string - default: / - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 requestBody: content: application/json: schema: - type: string + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' required: true responses: "200": @@ -518,64 +819,97 @@ paths: application/json: schema: type: object - example: - key: value + examples: + dataSample: + value: "" "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred post: tags: - - cps-data + - cps-data summary: Create a node description: Create a node for a given anchor and dataspace operationId: createNode parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: false - schema: - type: string - default: / - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 requestBody: content: application/json: schema: - type: string + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' required: true responses: "201": @@ -584,63 +918,191 @@ paths: text/plain: schema: type: string + example: my-resource + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "409": + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 409 + message: Conflicting request + details: The request cannot be processed as the resource is in use. + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + delete: + tags: + - cps-data + summary: Delete a data node + description: Delete a datanode for a given dataspace and anchor given a node + xpath. + operationId: deleteDataNode + parameters: + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 + responses: + "204": + description: No Content + content: {} "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred patch: tags: - - cps-data + - cps-data summary: Update node leaves description: Update a data node leaves for a given dataspace and anchor and a parent node xpath operationId: updateNodeLeaves parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: false - schema: - type: string - default: / - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 requestBody: content: application/json: schema: - type: string + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' required: true responses: "200": @@ -649,129 +1111,195 @@ paths: application/json: schema: type: object - example: - key: value + examples: + dataSample: + value: "" "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: put: tags: - - cps-data - summary: Replace list-node child element(s) under existing parent node - description: Replace list-node child elements under existing node for a given - anchor and dataspace - operationId: replaceListNodeElements + - cps-data + summary: Replace list content + description: "Replace list content under a given parent, anchor and dataspace" + operationId: replaceListContent parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: true - schema: - type: string - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: true + schema: + type: string + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 requestBody: content: application/json: schema: - type: string + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' required: true responses: "200": - description: Created + description: OK content: - text/plain: + application/json: schema: - type: string + type: object + examples: + dataSample: + value: "" "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred post: tags: - - cps-data - summary: Add list-node child element(s) under existing parent node - description: Add list-node child elements to existing node for a given anchor - and dataspace - operationId: addListNodeElements + - cps-data + summary: Add list element(s) + description: Add list element(s) to a list for a given anchor and dataspace + operationId: addListElements parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: true - schema: - type: string - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: true + schema: + type: string + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 requestBody: content: application/json: schema: - type: string + type: object + examples: + dataSample: + $ref: '#/components/examples/dataSample' required: true responses: "201": @@ -780,57 +1308,86 @@ paths: text/plain: schema: type: string + example: my-resource "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred delete: tags: - - cps-data - summary: Delete list-node child element(s) under existing parent node - description: Delete list-node child elements under existing node for a given - anchor and dataspace - operationId: deleteListNodeElements + - cps-data + summary: Delete one or all list element(s) + description: Delete one or all list element(s) for a given anchor and dataspace + operationId: deleteListOrListElement parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: xpath - in: query - description: xpath - required: true - schema: - type: string - - name: observed-timestamp - in: query - description: observed-timestamp - required: false - schema: - type: string - example: 2021-03-21T00:10:34.030-0100 + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: xpath + in: query + description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: true + schema: + type: string + examples: + container xpath: + value: /shops/bookstore + list attributes xpath: + value: "/shops/bookstore/categories[@code=1]" + - name: observed-timestamp + in: query + description: observed-timestamp + required: false + schema: + type: string + example: 2021-03-21T00:10:34.030-0100 responses: "204": description: No Content @@ -841,52 +1398,83 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + deprecated: true /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: get: tags: - - cps-query + - cps-query summary: Query data nodes description: Query data nodes for the given dataspace and anchor using CPS path operationId: getNodesByDataspaceAndAnchorAndCpsPath parameters: - - name: dataspace-name - in: path - description: dataspace-name - required: true - schema: - type: string - - name: anchor-name - in: path - description: anchor-name - required: true - schema: - type: string - - name: cps-path - in: query - description: cps-path - required: false - schema: - type: string - default: / - - name: include-descendants - in: query - description: include-descendants - required: false - schema: - type: boolean - default: false + - name: dataspace-name + in: path + description: dataspace-name + required: true + schema: + type: string + example: my-dataspace + - name: anchor-name + in: path + description: anchor-name + required: true + schema: + type: string + example: my-anchor + - name: cps-path + in: query + description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" + required: false + schema: + type: string + default: / + examples: + container cps path: + value: //bookstore + list attributes cps path: + value: "//categories[@code=1]" + - name: include-descendants + in: query + description: include-descendants + required: false + schema: + type: boolean + example: false + default: false responses: "200": description: OK @@ -894,32 +1482,49 @@ paths: application/json: schema: type: object - example: - key: value + examples: + dataSample: + $ref: '#/components/examples/dataSample' "400": description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized request + details: This request is unauthorized "403": description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' - "404": - description: The specified resource was not found + example: + status: 403 + message: Request Forbidden + details: This request is forbidden + "500": + description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred x-codegen-request-body-name: xpath components: schemas: @@ -929,29 +1534,26 @@ components: properties: status: type: string - example: "400" message: type: string - example: Dataspace not found details: type: string - example: Dataspace with name D1 does not exist. AnchorDetails: title: Anchor details by anchor Name type: object properties: name: type: string - example: my_anchor + example: my-anchor dataspaceName: type: string - example: my_dataspace + example: my-dataspace schemaSetName: type: string - example: my_schema_set + example: my-schema-set MultipartFile: required: - - file + - file type: object properties: file: @@ -960,28 +1562,40 @@ components: format: binary SchemaSetDetails: title: Schema set details by dataspace and schemasetName + required: + - moduleReferences type: object properties: dataspaceName: type: string - example: my_dataspace + example: my-dataspace moduleReferences: type: array items: $ref: '#/components/schemas/ModuleReferences' name: type: string - example: my_schema_set + example: my-schema-set ModuleReferences: title: Module reference object type: object properties: name: type: string - example: module_reference_name + example: my-module-reference-name namespace: type: string - example: module_reference_namespace + example: my-module-reference-namespace revision: type: string - example: module_reference_revision + example: my-module-reference-revision + examples: + dataSample: + value: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 1 + name: SciFi + - code: 2 + name: kids diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index 154a4411da..30896f6068 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -86,23 +86,16 @@ components: $ref: '#/components/schemas/RestInputCmHandle' updatedCmHandles: type: array - example: - cmHandle: my-cm-handle - cmHandleProperties: - add-my-property: add-property - update-my-property: updated-property - delete-my-property: ~ - publicCmHandleProperties: - add-my-property: add-property - update-my-property: updated-property - delete-my-property: ~ items: $ref: '#/components/schemas/RestInputCmHandle' removedCmHandles: type: array + example: + - my-cm-handle1 + - my-cm-handle2 + - my-cm-handle3 items: type: string - example: "[\"my-cm-handle1\",\"my-cm-handle2\",\"my-cm-handle3\"]" RestInputCmHandle: required: - cmHandle diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index b7a65632e7..5a6a600a36 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -70,6 +70,17 @@ paths: sample 3: value: options: "(depth=2,fields=book/authors)" + - name: topic + in: query + description: topic parameter in query. + required: false + allowReserved: true + schema: + type: string + examples: + sample 1: + value: + topic: my-topic-name responses: "200": description: OK @@ -184,6 +195,17 @@ paths: sample 3: value: options: "(depth=2,fields=book/authors)" + - name: topic + in: query + description: topic parameter in query. + required: false + allowReserved: true + schema: + type: string + examples: + sample 1: + value: + topic: my-topic-name responses: "200": description: OK @@ -664,7 +686,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ModuleReference' + $ref: '#/components/schemas/RestModuleReference' "400": description: Bad Request content: @@ -851,7 +873,7 @@ components: type: string details: type: string - ModuleReference: + RestModuleReference: title: Module reference details type: object properties: diff --git a/docs/deployment.rst b/docs/deployment.rst index 2f68a64ee1..46160c4f76 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -7,13 +7,13 @@ .. _deployment: CPS Deployment -============== +############## .. contents:: :depth: 2 CPS OOM Charts --------------- +============== The CPS kubernetes chart is located in the `OOM repository <https://github.com/onap/oom/tree/master/kubernetes/cps>`_. This chart includes different cps components referred as <cps-component-name> further in the document are listed below: @@ -26,7 +26,8 @@ This chart includes different cps components referred as <cps-component-name> fu Please refer to the `OOM documentation <https://docs.onap.org/projects/onap-oom/en/latest/oom_user_guide.html>`_ on how to install and deploy ONAP. Installing or Upgrading CPS Components --------------------------------------- +====================================== + The assumption is you have cloned the charts from the OOM repository into a local directory. **Step 1** Go to the cps charts and edit properties in values.yaml files to make any changes to particular cps component if required. @@ -91,7 +92,7 @@ After deploying cps, keep monitoring the cps pods until they come up. kubectl get pods -n <namespace> | grep <cps-component-name> Restarting a faulty component ------------------------------ +============================= Each cps component can be restarted independently by issuing the following command: .. code-block:: bash @@ -102,7 +103,7 @@ Each cps component can be restarted independently by issuing the following comma .. _cps_common_credentials_retrieval: Credentials Retrieval ---------------------- +===================== Application and database credentials are kept in Kubernetes secrets. They are defined as external secrets in the values.yaml file to be used across different components as : @@ -161,8 +162,9 @@ Additional Cps-Core Customizations ================================== The following table lists some properties that can be specified as Helm chart -values to configure the application to be deployed. This list is not -exhaustive. +values to configure the application to be deployed. This list is not exhaustive. + +Any spring supported property can be configured by providing in ``config.additional.<spring-supported-property-name>: value`` Example: config.additional.spring.datasource.hikari.maximumPoolSize: 30 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | Property | Description | Default Value | @@ -277,6 +279,10 @@ exhaustive. | notification.async.executor. | | | | thread-name-prefix | | | +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ +| config.additional. | Specifies number of database connections between database and application. | ``10`` | +| spring.datasource.hikari. | This property controls the maximum size that the pool is allowed to reach, | | +| maximumPoolSize | including both idle and in-use connections. | | ++---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ CPS-Core Docker Installation ============================ diff --git a/docs/index.rst b/docs/index.rst index 62ba5e8b17..eaf36466f4 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,10 @@ .. _cps-framework-doc: CPS Documentation ------------------ +################# + +CPS Core +======== .. toctree:: :maxdepth: 1 @@ -22,12 +25,12 @@ CPS Documentation deployment.rst release-notes.rst -DMI-Plugin Documentation ------------------------- +DMI-Plugin +========== * :ref:`DMI-Plugin<onap-cps-ncmp-dmi-plugin:master_index>` -CPS-Temporal Documentation --------------------------- +CPS Temporal +============ * :ref:`CPS-Temporal<onap-cps-cps-temporal:master_index>` diff --git a/docs/overview.rst b/docs/overview.rst index 4b69dd8109..cde6f6db62 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -4,7 +4,7 @@ .. _overview: CPS Overview -============ +############ The Configuration Persistence Service (CPS) is a platform component that is designed to serve as a data repository for runtime data that needs persistence. @@ -28,10 +28,10 @@ Types of data that is stored: configuration and operational parameters depending on how they are used. CPS Components --------------- +============== CPS-Core -######## +-------- This is the component of CPS which encompasses the generic storage of Yang module data. **NCMP** @@ -43,13 +43,13 @@ NCMP accesses all network Data-Model-Inventory (DMI) information via NCMP-DMI-Pl even though CPS-Core could be deployed without the NCMP extension. NCMP-DMI-Plugin -#################### +--------------- The Data-Model-Inventory (DMI) Plugin is a rest interface used to synchronize CM-Handles data between CPS and DMI through the DMI-Plugin. This is built previously from the CPS-NF-Proxy component. CPS-Temporal -############ +------------ This service is responsible to provide a time oriented perspective for operational network data. It provides features to store and retrieve sequences @@ -57,14 +57,8 @@ of configurations or states along with the associated times when they occurred or have been observed. CPS Project ------------ - -Wiki: `Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_ - -Contact Information -------------------- - -onap-discuss@lists.onap.org +=========== -Meeting details `Join <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_ -`Agenda <https://wiki.onap.org/pages/viewpage.action?pageId=111117075>`_ +* Wiki: `Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_ +* Contact Information: onap-discuss@lists.onap.org +* Meeting details: `Join <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_ & `Agenda <https://wiki.onap.org/pages/viewpage.action?pageId=111117075>`_ diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 9172e9f107..2fea4a21f1 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -5,11 +5,8 @@ .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING .. _release_notes: - - -================= CPS Release Notes -================= +################# .. contents:: :depth: 2 @@ -19,10 +16,25 @@ CPS Release Notes .. * * * JAKARTA * * * .. ======================== -Version: 3.0.0-SNAPSHOT -======================= +Version: 3.0.0 +============== + +Release Data +------------ -This section lists the main changes & fixes merged into master (snapshot) version of CPS-NCMP. This information is here to assist developers that want experiment/test using our latest code bases directly. Stability of this is not guaranteed. ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.0.0 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.0.0 Jakarta | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | 2022 March 15 | +| | | ++--------------------------------------+--------------------------------------------------------+ Features -------- @@ -33,6 +45,8 @@ Features - `CPS-741 <https://jira.onap.org/browse/CPS-741>`_ Re sync after removing cm handles - `CPS-777 <https://jira.onap.org/browse/CPS-777>`_ Ensure all DMI operations use POST method - `CPS-780 <https://jira.onap.org/browse/CPS-780>`_ Add examples for parameters, request and response in openapi yaml for cps-core + - `CPS-789 <https://jira.onap.org/browse/CPS-789>`_ CPS Data Updated Event Schema V2 to support delete operation + - `CPS-791 <https://jira.onap.org/browse/CPS-791>`_ CPS-Core sends delete notification event - `CPS-817 <https://jira.onap.org/browse/CPS-817>`_ Create Endpoint For Get Cm Handles (incl. public properties) By Name - `CPS-837 <https://jira.onap.org/browse/CPS-837>`_ Add Remove and Update properties (DMI and Public) as part of CM Handle Registration update diff --git a/jacoco-report/pom.xml b/jacoco-report/pom.xml index d42d89ab0b..d1181d367c 100644 --- a/jacoco-report/pom.xml +++ b/jacoco-report/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId>
<artifactId>cps-aggregator</artifactId>
- <version>3.0.0-SNAPSHOT</version>
+ <version>3.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cps</name>
diff --git a/releases/3.0.0-container.yaml b/releases/3.0.0-container.yaml new file mode 100644 index 0000000000..f227bdbf53 --- /dev/null +++ b/releases/3.0.0-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.0.0 +project: cps +log_dir: cps-maven-docker-stage-master/504/ +ref: a1129b696f3197fc7d8a3b63bcd84b5b2dd8874e +containers: + - name: 'cps-and-ncmp' + version: '3.0.0-20220315T180237Z' diff --git a/releases/3.0.0.yaml b/releases/3.0.0.yaml new file mode 100644 index 0000000000..60dd8116d0 --- /dev/null +++ b/releases/3.0.0.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/504/ +project: cps +version: 3.0.0 diff --git a/spotbugs/pom.xml b/spotbugs/pom.xml index 50cef487e0..df033a3f4a 100644 --- a/spotbugs/pom.xml +++ b/spotbugs/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>spotbugs</artifactId> - <version>3.0.0-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <properties> <nexusproxy>https://nexus.onap.org</nexusproxy> @@ -33,6 +33,18 @@ <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.8.2</version> + </plugin> + </plugins> + </pluginManagement> + </build> + <distributionManagement> <repository> <id>ecomp-releases</id> diff --git a/version.properties b/version.properties index 17f2daa6cf..870b994b72 100755 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021 Nordix Foundation +# Copyright (C) 2021-2022 Nordix Foundation # Modifications Copyright (C) 2022 Bell Canada. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ # because they are used in Jenkins, whose plug-in doesn't support this major=3 -minor=0 +minor=1 patch=0 base_version=${major}.${minor}.${patch} |