diff options
Diffstat (limited to 'cps-service')
46 files changed, 506 insertions, 710 deletions
diff --git a/cps-service/pom.xml b/cps-service/pom.xml index 2564ceb989..c7a3666a07 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -29,7 +29,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.1-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -64,17 +64,17 @@ <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> + <!-- Disable SpotBug Rules --> + <dependency> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-annotations</artifactId> + </dependency> <dependency> <!-- For parsing JSON object --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> - <!-- Hazelcast provide Distributed Caches --> - <groupId>com.hazelcast</groupId> - <artifactId>hazelcast-spring</artifactId> - </dependency> - <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> @@ -144,18 +144,7 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-json-jackson</artifactId> - </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-kafka</artifactId> - </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-spring</artifactId> - </dependency> + <!-- T E S T D E P E N D E N C I E S --> <dependency> <groupId>org.codehaus.groovy</groupId> 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 f396b49e6b..b3eff8eb26 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 @@ -55,7 +55,7 @@ public interface CpsDataService { * @param anchorName anchor name * @param nodeData node data * @param observedTimestamp observedTimestamp - * @param contentType node data content type + * @param contentType JSON/XML content type */ void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp, ContentType contentType); @@ -80,7 +80,7 @@ public interface CpsDataService { * @param parentNodeXpath parent node xpath * @param nodeData node data * @param observedTimestamp observedTimestamp - * @param contentType node data content type + * @param contentType JSON/XML content type * */ void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, @@ -93,11 +93,12 @@ public interface CpsDataService { * @param dataspaceName dataspace name * @param anchorName anchor name * @param parentNodeXpath parent node xpath - * @param jsonData json data representing list element(s) + * @param nodeData node data representing list element(s) * @param observedTimestamp observedTimestamp + * @param contentType JSON/XML content type */ - void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData, - OffsetDateTime observedTimestamp); + void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + OffsetDateTime observedTimestamp, ContentType contentType); /** * Retrieves all the datanodes by XPath for given dataspace and anchor. @@ -134,7 +135,7 @@ public interface CpsDataService { * @param parentNodeXpath xpath to parent node * @param nodeData node data * @param observedTimestamp observedTimestamp - * @param contentType node data content type + * @param contentType JSON/XML content type */ void updateNodeLeaves(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, OffsetDateTime observedTimestamp, ContentType contentType); @@ -145,22 +146,24 @@ public interface CpsDataService { * @param dataspaceName dataspace name * @param anchorName anchor name * @param parentNodeXpath xpath to parent node - * @param jsonData json data + * @param nodeData node data * @param observedTimestamp observedTimestamp + * @param contentType JSON/XML content type */ - void updateDataNodeAndDescendants(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData, - OffsetDateTime observedTimestamp); + void updateDataNodeAndDescendants(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + OffsetDateTime observedTimestamp, ContentType contentType); /** * Replaces multiple existing data nodes' content including descendants in a batch operation. * * @param dataspaceName dataspace name * @param anchorName anchor name - * @param nodesJsonData map of xpath and node JSON data + * @param nodeDataPerXPath map of xpath and node JSON/XML data * @param observedTimestamp observedTimestamp + * @param contentType JSON/XML content type */ - void updateDataNodesAndDescendants(String dataspaceName, String anchorName, Map<String, String> nodesJsonData, - OffsetDateTime observedTimestamp); + void updateDataNodesAndDescendants(String dataspaceName, String anchorName, Map<String, String> nodeDataPerXPath, + OffsetDateTime observedTimestamp, ContentType contentType); /** * Replaces list content by removing all existing elements and inserting the given new elements as json @@ -319,4 +322,18 @@ public interface CpsDataService { Map<String, String> yangResourcesNameToContentMap, String targetData, FetchDescendantsOption fetchDescendantsOption); + + + /** + * Validates JSON or XML data by parsing it using the schema associated to an anchor within the given dataspace. + * Validation is performed without persisting the data. + * + * @param dataspaceName the name of the dataspace where the anchor is located. + * @param anchorName the name of the anchor used to validate the data. + * @param parentNodeXpath the xpath of the parent node where the data is to be validated. + * @param nodeData the JSON or XML data to be validated. + * @param contentType the content type of the data (e.g., JSON or XML). + */ + void validateData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + ContentType contentType); } diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java index bdd361458e..931209c998 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java @@ -155,4 +155,37 @@ public interface CpsModuleService { Collection<ModuleReference> identifyNewModuleReferences( Collection<ModuleReference> moduleReferencesToCheck); + /** + * Retrieves module references based on the provided dataspace name, anchor name and attribute filters + * for both parent and child fragments. + + * This method constructs and executes a SQL query to find module references from a database, using + * the specified `dataspaceName`, `anchorName` and two sets of attribute filters: one for parent fragments + * and one for child fragments. The method applies these filters to identify the appropriate fragments + * and schema sets, and then retrieves the corresponding module references. + + * The SQL query is dynamically built based on the provided attribute filters: + * - The `parentAttributes` map is used to filter the parent fragments. The entries in this map are + * converted into a WHERE clause for the parent fragments. + * - The `childAttributes` map is used to filter the child fragments. This is applied to the child fragments + * after filtering the parent fragments. + * + * @param dataspaceName the name of the dataspace to filter on. It is used to locate the relevant dataspace + * in the database. + * @param anchorName the name of the anchor to filter on. It is used to locate the relevant anchor within + * the dataspace. + * @param parentAttributes a map of attributes to filter parent fragments. Each entry in this map represents + * an attribute key-value pair used in the WHERE clause for parent fragments. + * @param childAttributes a map of attributes to filter child fragments. Each entry in this map represents + * an attribute key-value pair used in the WHERE clause for child fragments. + * @return a collection of {@link ModuleReference} objects that match the given criteria. Each + * {@code ModuleReference} contains information about a module's name and revision. + * @implNote The method assumes that both `parentAttributes` and `childAttributes` maps contain at least + * one entry. The first entry from `parentAttributes` is used to filter parent fragments, + * and the first entry from `childAttributes` is used to filter child fragments. + */ + Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName, + final Map<String, String> parentAttributes, + final Map<String, String> childAttributes); + } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java index c31e51b174..5ca0fe63d4 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java @@ -23,10 +23,10 @@ package org.onap.cps.api.impl; import java.util.Collection; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsAnchorService; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Service; @Service 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 5a48428772..b1b545be68 100644 --- 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 @@ -42,6 +42,7 @@ import org.onap.cps.api.CpsDeltaService; import org.onap.cps.cpspath.parser.CpsPathUtil; import org.onap.cps.events.CpsDataUpdateEventsService; import org.onap.cps.events.model.Data.Operation; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataValidationException; @@ -49,7 +50,6 @@ import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.model.DeltaReport; -import org.onap.cps.spi.utils.CpsValidator; import org.onap.cps.utils.ContentType; import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; @@ -64,6 +64,7 @@ import org.springframework.stereotype.Service; public class CpsDataServiceImpl implements CpsDataService { private static final String ROOT_NODE_XPATH = "/"; + private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = ""; private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L; private static final String NO_DATA_NODES = "No data nodes."; @@ -119,12 +120,13 @@ public class CpsDataServiceImpl implements CpsDataService { @Override @Timed(value = "cps.data.service.list.element.save", description = "Time taken to save list elements") - public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String jsonData, final OffsetDateTime observedTimestamp) { + public void saveListElements(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final String nodeData, + final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> listElementDataNodeCollection = - buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON); + buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType); if (isRootNodeXpath(parentNodeXpath)) { cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection); } else { @@ -251,12 +253,12 @@ public class CpsDataServiceImpl implements CpsDataService { @Timed(value = "cps.data.service.datanode.descendants.update", description = "Time taken to update a data node and descendants") public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final String jsonData, - final OffsetDateTime observedTimestamp) { + final String parentNodeXpath, final String nodeData, + final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = - buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON); + buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @@ -265,13 +267,13 @@ public class CpsDataServiceImpl implements CpsDataService { @Timed(value = "cps.data.service.datanode.descendants.batch.update", description = "Time taken to update a batch of data nodes and descendants") public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, - final Map<String, String> nodesJsonData, - final OffsetDateTime observedTimestamp) { + final Map<String, String> nodeDataPerXPath, + final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodesJsonData); + final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodeDataPerXPath, contentType); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); - nodesJsonData.keySet().forEach(nodeXpath -> + nodeDataPerXPath.keySet().forEach(nodeXpath -> sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp)); } @@ -357,6 +359,14 @@ public class CpsDataServiceImpl implements CpsDataService { sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp); } + @Override + public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final String nodeData, final ContentType contentType) { + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH : + CpsPathUtil.getNormalizedXpath(parentNodeXpath); + yangParser.validateData(contentType, nodeData, anchor, xpath); + } private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor, final Collection<DataNode> sourceDataNodes) { @@ -399,8 +409,7 @@ public class CpsDataServiceImpl implements CpsDataService { private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) { final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size()); for (final DataNode dataNode: dataNodes) { - final String prefix = prefixResolver - .getPrefix(anchor.getDataspaceName(), anchor.getName(), dataNode.getXpath()); + final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath()); final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); prefixToDataNodes.add(prefixToDataNode); } @@ -408,11 +417,12 @@ public class CpsDataServiceImpl implements CpsDataService { } private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor, - final Map<String, String> nodesJsonData) { + final Map<String, String> nodesJsonData, + final ContentType contentType) { final Collection<DataNode> dataNodes = new ArrayList<>(); for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) { dataNodes.addAll(buildDataNodesWithParentNodeXpath(anchor, nodeJsonData.getKey(), - nodeJsonData.getValue(), ContentType.JSON)); + nodeJsonData.getValue(), contentType)); } return dataNodes; } @@ -421,7 +431,8 @@ public class CpsDataServiceImpl implements CpsDataService { final String nodeData, final ContentType contentType) { if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, ""); + final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, + anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); @@ -449,7 +460,7 @@ public class CpsDataServiceImpl implements CpsDataService { if (isRootNodeXpath(xpath)) { final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, - yangResourcesNameToContentMap, ""); + yangResourcesNameToContentMap, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java index a7f5da4874..6bccf2a865 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java @@ -26,9 +26,9 @@ package org.onap.cps.api.impl; import java.util.Collection; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsDataspaceService; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.model.Dataspace; -import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Service; @Service diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java index 2f99dbf7bb..7819568ae1 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java @@ -20,7 +20,6 @@ package org.onap.cps.api.impl; - import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -179,7 +178,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { final List<DeltaReport> updatedDeltaReportEntries) { for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry: updatedLeavesAsSourceDataToTargetData.entrySet()) { - final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate() + final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace() .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); updatedDeltaReportEntries.add(updatedDataForDeltaReport); } @@ -195,7 +194,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { for (final Map.Entry<String, DataNode> entry: xpathToAddedNodes.entrySet()) { final String xpath = entry.getKey(); final DataNode dataNode = entry.getValue(); - final DeltaReport addedDataForDeltaReport = new DeltaReportBuilder().actionAdd().withXpath(xpath) + final DeltaReport addedDataForDeltaReport = new DeltaReportBuilder().actionCreate().withXpath(xpath) .withTargetData(dataNode.getLeaves()).build(); addedDeltaReportEntries.add(addedDataForDeltaReport); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java index e6ad9a8bb8..a600b22b61 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java @@ -30,6 +30,7 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CascadeDeleteAllowed; import org.onap.cps.spi.CpsModulePersistenceService; import org.onap.cps.spi.exceptions.SchemaSetInUseException; @@ -37,7 +38,6 @@ import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.spi.model.SchemaSet; -import org.onap.cps.spi.utils.CpsValidator; import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder; import org.onap.cps.yang.YangTextSchemaSourceSet; import org.springframework.stereotype.Service; @@ -171,6 +171,17 @@ public class CpsModuleServiceImpl implements CpsModuleService { return cpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck); } + @Timed(value = "cps.module.service.module.reference.query", + description = "Time taken to query list of module references") + @Override + public Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, + final String anchorName, + final Map<String, String> parentAttributes, + final Map<String, String> childAttributes) { + return cpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, + childAttributes); + } + private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) { return CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED == cascadeDeleteAllowed; } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java index 1d7a7ceeb0..d1c98986e6 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java @@ -25,11 +25,11 @@ import io.micrometer.core.annotation.Timed; import java.util.Collection; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsQueryService; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.PaginationOption; import org.onap.cps.spi.model.DataNode; -import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Service; @Service diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java index 4fdae5a307..8b85dfca32 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java @@ -27,8 +27,8 @@ import io.micrometer.core.instrument.Metrics; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import lombok.RequiredArgsConstructor; +import org.onap.cps.impl.utils.CpsValidator; import org.onap.cps.spi.CpsModulePersistenceService; -import org.onap.cps.spi.utils.CpsValidator; import org.onap.cps.yang.YangTextSchemaSourceSet; import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; import org.springframework.cache.annotation.CacheConfig; diff --git a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java deleted file mode 100644 index efe19c6cb7..0000000000 --- a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (C) 2022-2023 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.cache; - -import com.hazelcast.config.MapConfig; -import com.hazelcast.map.IMap; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Core infrastructure of the hazelcast distributed cache for anchor data config use cases. - */ -@Configuration -public class AnchorDataCacheConfig extends HazelcastCacheConfig { - - private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig"); - - /** - * Distributed instance of anchor data cache that contains module prefix by anchor name as properties. - * - * @return configured map of anchor data cache - */ - @Bean - public IMap<String, AnchorDataCacheEntry> anchorDataCache() { - return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig).getMap("anchorDataCache"); - } -} diff --git a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java deleted file mode 100644 index 41adbdd5dc..0000000000 --- a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.cache; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -public class AnchorDataCacheEntry implements Serializable { - - private static final long serialVersionUID = 2111243947810370698L; - - private Map<String, Serializable> properties = new HashMap<>(); - - public Object getProperty(final String propertyName) { - return properties.get(propertyName); - } - - public void setProperty(final String propertyName, final Serializable value) { - properties.put(propertyName, value); - } - - public boolean hasProperty(final String propertyName) { - return properties.containsKey(propertyName); - } -} diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java deleted file mode 100644 index 418de9b17a..0000000000 --- a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (C) 2023-2024 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.cache; - -import com.hazelcast.config.Config; -import com.hazelcast.config.MapConfig; -import com.hazelcast.config.NamedConfig; -import com.hazelcast.config.QueueConfig; -import com.hazelcast.config.RestEndpointGroup; -import com.hazelcast.config.SetConfig; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; - -/** - * Core infrastructure of the hazelcast distributed cache. - */ -@Slf4j -public class HazelcastCacheConfig { - - @Value("${hazelcast.cluster-name}") - protected String clusterName; - - @Value("${hazelcast.mode.kubernetes.enabled}") - protected boolean cacheKubernetesEnabled; - - @Value("${hazelcast.mode.kubernetes.service-name}") - protected String cacheKubernetesServiceName; - - protected HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName, - final NamedConfig namedConfig) { - return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig)); - } - - private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) { - final Config config = new Config(instanceName); - if (namedConfig instanceof MapConfig) { - config.addMapConfig((MapConfig) namedConfig); - } - if (namedConfig instanceof QueueConfig) { - config.addQueueConfig((QueueConfig) namedConfig); - } - if (namedConfig instanceof SetConfig) { - config.addSetConfig((SetConfig) namedConfig); - } - - config.setClusterName(clusterName); - config.setClassLoader(org.onap.cps.spi.model.Dataspace.class.getClassLoader()); - exposeClusterInformation(config); - updateDiscoveryMode(config); - return config; - } - - protected static MapConfig createMapConfig(final String configName) { - final MapConfig mapConfig = new MapConfig(configName); - mapConfig.setBackupCount(1); - return mapConfig; - } - - protected static QueueConfig createQueueConfig(final String configName) { - final QueueConfig commonQueueConfig = new QueueConfig(configName); - commonQueueConfig.setBackupCount(1); - return commonQueueConfig; - } - - protected static SetConfig createSetConfig(final String configName) { - final SetConfig commonSetConfig = new SetConfig(configName); - commonSetConfig.setBackupCount(1); - return commonSetConfig; - } - - protected void updateDiscoveryMode(final Config config) { - if (cacheKubernetesEnabled) { - log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName); - config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true) - .setProperty("service-name", cacheKubernetesServiceName); - } - } - - protected void exposeClusterInformation(final Config config) { - config.getNetworkConfig().getRestApiConfig().setEnabled(true) - .enableGroups(RestEndpointGroup.HEALTH_CHECK, RestEndpointGroup.CLUSTER_READ); - } - -} diff --git a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java index 8023fbfb25..46384b5933 100644 --- a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java +++ b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java @@ -44,7 +44,7 @@ public class EventsPublisher<T> { /** * KafkaTemplate for legacy (non-cloud) events. - * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717. + * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717. */ private final KafkaTemplate<String, T> legacyKafkaEventTemplate; @@ -73,7 +73,7 @@ public class EventsPublisher<T> { /** * Generic Event publisher. - * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717. + * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717. * * @param topicName valid topic name * @param eventKey message key diff --git a/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/impl/utils/CpsValidator.java index ceb75c09b2..fd677eb2d2 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java +++ b/cps-service/src/main/java/org/onap/cps/impl/utils/CpsValidator.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.spi.utils; +package org.onap.cps.impl.utils; import org.onap.cps.spi.PaginationOption; diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java index eeaaa47991..793f38e4bc 100755 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java @@ -153,4 +153,20 @@ public interface CpsModulePersistenceService { Collection<ModuleReference> identifyNewModuleReferences( Collection<ModuleReference> moduleReferencesToCheck); + /** + * Retrieves module references based on the specified dataspace, anchor, and attribute filters. + + * Constructs and executes a SQL query to find module references by applying filters for parent and child fragments. + * Uses `parentAttributes` for filtering parent fragments and `childAttributes` for filtering child fragments. + * + * @param dataspaceName the name of the dataspace to filter on. + * @param anchorName the name of the anchor to filter on. + * @param parentAttributes a map of attributes for filtering parent fragments. + * @param childAttributes a map of attributes for filtering child fragments. + * @return a collection of {@link ModuleReference} objects matching the criteria. + */ + Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName, + final Map<String, String> parentAttributes, + final Map<String, String> childAttributes); + } diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index 9859acdf0e..de57914527 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -204,18 +204,17 @@ public class DataNodeBuilder { private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode, final NormalizedNode normalizedNode) { - if (normalizedNode instanceof ChoiceNode) { - addChoiceNode(currentDataNode, (ChoiceNode) normalizedNode); - } else if (normalizedNode instanceof DataContainerNode) { - addYangContainer(currentDataNode, (DataContainerNode) normalizedNode); - } else if (normalizedNode instanceof MapNode) { - addDataNodeForEachListElement(currentDataNode, (MapNode) normalizedNode); - } else if (normalizedNode instanceof ValueNode) { - final ValueNode<NormalizedNode> valuesNode = (ValueNode) normalizedNode; - addYangLeaf(currentDataNode, valuesNode.getIdentifier().getNodeType().getLocalName(), - (Serializable) valuesNode.body()); - } else if (normalizedNode instanceof LeafSetNode) { - addYangLeafList(currentDataNode, (LeafSetNode<?>) normalizedNode); + if (normalizedNode instanceof ChoiceNode choiceNode) { + addChoiceNode(currentDataNode, choiceNode); + } else if (normalizedNode instanceof DataContainerNode dataContainerNode) { + addYangContainer(currentDataNode, dataContainerNode); + } else if (normalizedNode instanceof MapNode mapNode) { + addDataNodeForEachListElement(currentDataNode, mapNode); + } else if (normalizedNode instanceof ValueNode<?> valueNode) { + addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(), + (Serializable) valueNode.body()); + } else if (normalizedNode instanceof LeafSetNode<?> leafSetNode) { + addYangLeafList(currentDataNode, leafSetNode); } else { log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass()); } @@ -243,7 +242,7 @@ public class DataNodeBuilder { private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) { final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); - List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body()) + List<?> leafListValues = (leafSetNode.body()) .stream() .map(NormalizedNode::body) .collect(Collectors.toList()); diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java index 34715e70b9..c6270a41d2 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java @@ -32,9 +32,9 @@ import lombok.Setter; @JsonInclude(JsonInclude.Include.NON_NULL) public class DeltaReport { - public static final String ADD_ACTION = "add"; + public static final String CREATE_ACTION = "create"; public static final String REMOVE_ACTION = "remove"; - public static final String UPDATE_ACTION = "update"; + public static final String REPLACE_ACTION = "replace"; DeltaReport() {} diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java index 1e151eeb2d..a7e7aae215 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java @@ -48,8 +48,8 @@ public class DeltaReportBuilder { return this; } - public DeltaReportBuilder actionAdd() { - this.action = DeltaReport.ADD_ACTION; + public DeltaReportBuilder actionCreate() { + this.action = DeltaReport.CREATE_ACTION; return this; } @@ -58,8 +58,8 @@ public class DeltaReportBuilder { return this; } - public DeltaReportBuilder actionUpdate() { - this.action = DeltaReport.UPDATE_ACTION; + public DeltaReportBuilder actionReplace() { + this.action = DeltaReport.REPLACE_ACTION; return this; } diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java index 35dc7347b2..c3097cc552 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java +++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation. + * Copyright (C) 2022-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,53 +20,25 @@ package org.onap.cps.utils; -import com.hazelcast.map.IMap; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.impl.YangTextSchemaSourceSetCache; -import org.onap.cps.cache.AnchorDataCacheEntry; import org.onap.cps.cpspath.parser.CpsPathPrefixType; import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.cpspath.parser.CpsPathUtil; import org.onap.cps.spi.model.Anchor; import org.onap.cps.yang.YangTextSchemaSourceSet; -import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor public class PrefixResolver { - private static final long ANCHOR_DATA_CACHE_TTL_SECS = TimeUnit.HOURS.toSeconds(1); - - private static final String CACHE_ENTRY_PROPERTY_NAME = "prefixPerContainerName"; - - private final CpsAnchorService cpsAnchorService; - private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; - private final IMap<String, AnchorDataCacheEntry> anchorDataCache; - - /** - * Get the module prefix for the given xpath for a dataspace and anchor name. - * - * @param dataspaceName the name of the dataspace - * @param anchorName the name of the anchor the xpath belongs to - * @param xpath the xpath to prefix a prefix for - * @return the prefix of the module the top level element of given xpath - */ - public String getPrefix(final String dataspaceName, final String anchorName, final String xpath) { - final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - return getPrefix(anchor, xpath); - } - /** * Get the module prefix for the given xpath under the given anchor. * @@ -75,57 +47,25 @@ public class PrefixResolver { * @return the prefix of the module the top level element of given xpath */ public String getPrefix(final Anchor anchor, final String xpath) { - final Map<String, String> prefixPerContainerName = getPrefixPerContainerName(anchor); - return getPrefixForTopContainer(prefixPerContainerName, xpath); - } - - private Map<String, String> getPrefixPerContainerName(final Anchor anchor) { - if (anchorDataCache.containsKey(anchor.getName())) { - final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchor.getName()); - if (anchorDataCacheEntry.hasProperty(CACHE_ENTRY_PROPERTY_NAME)) { - return (Map) anchorDataCacheEntry.getProperty(CACHE_ENTRY_PROPERTY_NAME); - } - } - return createAndCachePrefixPerContainerNameMap(anchor); - } - - private String getPrefixForTopContainer(final Map<String, String> prefixPerContainerName, - final String xpath) { final CpsPathQuery cpsPathQuery = CpsPathUtil.getCpsPathQuery(xpath); - if (cpsPathQuery.getCpsPathPrefixType() == CpsPathPrefixType.ABSOLUTE) { - final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0); - if (prefixPerContainerName.containsKey(topLevelContainerName)) { - return prefixPerContainerName.get(topLevelContainerName); - } + if (cpsPathQuery.getCpsPathPrefixType() != CpsPathPrefixType.ABSOLUTE) { + return ""; } - return ""; - } + final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0); - private Map<String, String> createAndCachePrefixPerContainerNameMap(final Anchor anchor) { final YangTextSchemaSourceSet yangTextSchemaSourceSet = - yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName()); + yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName()); final SchemaContext schemaContext = yangTextSchemaSourceSet.getSchemaContext(); - final Map<QNameModule, String> prefixPerQNameModule = new HashMap<>(schemaContext.getModules().size()); - for (final Module module : schemaContext.getModules()) { - prefixPerQNameModule.put(module.getQNameModule(), module.getPrefix()); - } - final HashMap<String, String> prefixPerContainerName = new HashMap<>(); - for (final DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) { - if (dataSchemaNode instanceof DataNodeContainer) { - final String containerName = dataSchemaNode.getQName().getLocalName(); - final String prefix = prefixPerQNameModule.get(dataSchemaNode.getQName().getModule()); - prefixPerContainerName.put(containerName, prefix); - } - } - cachePrefixPerContainerNameMap(anchor.getName(), prefixPerContainerName); - return prefixPerContainerName; - } - private void cachePrefixPerContainerNameMap(final String anchorName, - final Serializable prefixPerContainerName) { - final AnchorDataCacheEntry anchorDataCacheEntry = new AnchorDataCacheEntry(); - anchorDataCacheEntry.setProperty(CACHE_ENTRY_PROPERTY_NAME, prefixPerContainerName); - anchorDataCache.put(anchorName, anchorDataCacheEntry, ANCHOR_DATA_CACHE_TTL_SECS, TimeUnit.SECONDS); + return schemaContext.getChildNodes().stream() + .filter(DataNodeContainer.class::isInstance) + .map(SchemaNode::getQName) + .filter(qname -> qname.getLocalName().equals(topLevelContainerName)) + .findFirst() + .map(QName::getModule) + .flatMap(schemaContext::findModule) + .map(Module::getPrefix) + .orElse(""); } } diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java index dc23c6bc4a..168e0999d5 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java @@ -21,6 +21,9 @@ package org.onap.cps.utils; +import static org.onap.cps.utils.YangParserHelper.VALIDATE_AND_PARSE; +import static org.onap.cps.utils.YangParserHelper.VALIDATE_ONLY; + import io.micrometer.core.annotation.Timed; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -57,11 +60,12 @@ public class YangParser { final String parentNodeXpath) { final SchemaContext schemaContext = getSchemaContext(anchor); try { - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper + .parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); } catch (final DataValidationException e) { invalidateCache(anchor); } - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); } /** @@ -78,7 +82,31 @@ public class YangParser { final Map<String, String> yangResourcesNameToContentMap, final String parentNodeXpath) { final SchemaContext schemaContext = getSchemaContext(yangResourcesNameToContentMap); - return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE); + } + + /** + * Parses data to validate it, using the schema context for given anchor. + * + * @param anchor the anchor used for node data validation + * @param parentNodeXpath the xpath of the parent node + * @param nodeData JSON or XML data string to validate + * @param contentType the content type of the data (e.g., JSON or XML) + * @throws DataValidationException if validation fails + */ + public void validateData(final ContentType contentType, + final String nodeData, + final Anchor anchor, + final String parentNodeXpath) { + final SchemaContext schemaContext = getSchemaContext(anchor); + try { + yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY); + } catch (final DataValidationException e) { + invalidateCache(anchor); + log.error("Data validation failed for anchor: {}, xpath: {}, details: {}", anchor, parentNodeXpath, + e.getMessage()); + } + yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY); } private SchemaContext getSchemaContext(final Anchor anchor) { diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java index 5cadd29368..5612945ea9 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java @@ -22,6 +22,7 @@ package org.onap.cps.utils; import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.StringReader; import java.net.URISyntaxException; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLInputFactory; @@ -70,6 +72,9 @@ public class YangParserHelper { static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; static final String DATA_ROOT_NODE_TAG_NAME = "data"; + static final String DATA_VALIDATION_FAILURE_MESSAGE = "Data Validation Failed"; + static final boolean VALIDATE_ONLY = true; + static final boolean VALIDATE_AND_PARSE = false; /** * Parses data into NormalizedNode according to given schema context. @@ -83,11 +88,20 @@ public class YangParserHelper { public ContainerNode parseData(final ContentType contentType, final String nodeData, final SchemaContext schemaContext, - final String parentNodeXpath) { + final String parentNodeXpath, + final boolean validateOnly) { if (contentType == ContentType.JSON) { - return parseJsonData(nodeData, schemaContext, parentNodeXpath); + final ContainerNode validatedAndParsedJson = parseJsonData(nodeData, schemaContext, parentNodeXpath); + if (validateOnly) { + return null; + } + return validatedAndParsedJson; + } + final NormalizedNodeResult normalizedNodeResult = parseXmlData(nodeData, schemaContext, parentNodeXpath); + if (validateOnly) { + return null; } - return parseXmlData(nodeData, schemaContext, parentNodeXpath); + return buildContainerNodeFormNormalizedNodeResult(normalizedNodeResult); } private ContainerNode parseJsonData(final String jsonData, @@ -120,18 +134,15 @@ public class YangParserHelper { try (jsonParserStream) { jsonParserStream.parse(jsonReader); - } catch (final IOException | JsonSyntaxException exception) { - throw new DataValidationException( - "Failed to parse json data: " + jsonData, exception.getMessage(), exception); - } catch (final IllegalStateException | IllegalArgumentException exception) { + } catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) { throw new DataValidationException( - "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception - .getMessage(), exception); + DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse json data. " + exception.getMessage(), exception); } return dataContainerNodeBuilder.build(); } - private ContainerNode parseXmlData(final String xmlData, + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Problem originates in 3PP code") + private NormalizedNodeResult parseXmlData(final String xmlData, final SchemaContext schemaContext, final String parentNodeXpath) { final XMLInputFactory factory = XMLInputFactory.newInstance(); @@ -168,12 +179,17 @@ public class YangParserHelper { } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException | ParserConfigurationException | TransformerException exception) { throw new DataValidationException( - "Failed to parse xml data: " + xmlData, exception.getMessage(), exception); + DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse xml data: " + exception.getMessage(), exception); } + return normalizedNodeResult; + } + + private ContainerNode buildContainerNodeFormNormalizedNodeResult(final NormalizedNodeResult normalizedNodeResult) { + final DataContainerChild dataContainerChild = - (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); + (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); final YangInstanceIdentifier.NodeIdentifier nodeIdentifier = - new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); + new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build(); } @@ -185,12 +201,12 @@ public class YangParserHelper { private static Map<String, Object> getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, final SchemaContext schemaContext) { - final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); + final List<String> xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), new ArrayList<>()); } - private static String[] xpathToNodeIdSequence(final String xpath) { + private static List<String> xpathToNodeIdSequence(final String xpath) { try { return CpsPathUtil.getXpathNodeIdSequence(xpath); } catch (final PathParsingException pathParsingException) { @@ -200,17 +216,16 @@ public class YangParserHelper { } private static Map<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( - final String[] xpathNodeIdSequence, + final List<String> xpathNodeIdSequence, final Collection<? extends DataSchemaNode> dataSchemaNodes, final Collection<QName> dataSchemaNodeIdentifiers) { - final String currentXpathNodeId = xpathNodeIdSequence[0]; + final String currentXpathNodeId = xpathNodeIdSequence.get(0); final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream() .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName())) .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName()); - if (xpathNodeIdSequence.length <= 1) { - final Map<String, Object> dataSchemaNodeAndIdentifiers = - new HashMap<>(); + if (xpathNodeIdSequence.size() <= 1) { + final Map<String, Object> dataSchemaNodeAndIdentifiers = new HashMap<>(); dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); return dataSchemaNodeAndIdentifiers; @@ -221,13 +236,11 @@ public class YangParserHelper { ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), dataSchemaNodeIdentifiers); } - throw schemaNodeNotFoundException(xpathNodeIdSequence[1]); + throw schemaNodeNotFoundException(xpathNodeIdSequence.get(1)); } - private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) { - final String[] nextXpathNodeIdSequence = new String[xpathNodeIdSequence.length - 1]; - System.arraycopy(xpathNodeIdSequence, 1, nextXpathNodeIdSequence, 0, nextXpathNodeIdSequence.length); - return nextXpathNodeIdSequence; + private static List<String> getNextLevelXpathNodeIdSequence(final List<String> xpathNodeIdSequence) { + return xpathNodeIdSequence.subList(1, xpathNodeIdSequence.size()); } private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy index e58a5024b5..ccf943a470 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy @@ -20,11 +20,11 @@ package org.onap.cps.api.impl +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsAdminPersistenceService import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.utils.CpsValidator import spock.lang.Specification class CpsAnchorServiceImplSpec extends Specification { 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 4e5807ec84..8c208a1cf8 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 @@ -23,14 +23,15 @@ package org.onap.cps.api.impl -import com.fasterxml.jackson.databind.ObjectMapper import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender +import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDeltaService import org.onap.cps.events.CpsDataUpdateEventsService +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.ConcurrencyException @@ -40,12 +41,11 @@ import org.onap.cps.spi.exceptions.SessionManagerException import org.onap.cps.spi.exceptions.SessionTimeoutException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType -import org.onap.cps.utils.YangParser -import org.onap.cps.utils.YangParserHelper import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.utils.PrefixResolver +import org.onap.cps.utils.YangParser +import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder @@ -53,6 +53,7 @@ import org.slf4j.LoggerFactory import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Shared import spock.lang.Specification + import java.time.OffsetDateTime import static org.onap.cps.events.model.Data.Operation.DELETE @@ -132,8 +133,8 @@ class CpsDataServiceImplSpec extends Specification { where: 'given parameters' scenario | invalidData | contentType || expectedMessage 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes' - 'invalid json' | '{invalid json' | ContentType.JSON || 'Failed to parse json data' - 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data' + 'invalid json' | '{invalid json' | ContentType.JSON || 'Data Validation Failed' + 'invalid xml' | '<invalid xml' | ContentType.XML || 'Data Validation Failed' } def 'Saving list element data fragment under Root node.'() { @@ -141,7 +142,7 @@ class CpsDataServiceImplSpec extends Specification { setupSchemaSetMocks('bookstore.yang') when: 'save data method is invoked with list element json data' def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp) + objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp, ContentType.JSON) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, { dataNodeCollection -> @@ -169,12 +170,11 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) } - def 'Saving list element data fragment under existing node.'() { + def 'Saving list element data fragment under existing JSON/XML node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') - when: 'save data method is invoked with list element json data' - def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp) + when: 'save data method is invoked with list element data' + objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree', { dataNodeCollection -> @@ -187,16 +187,23 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + where: + data | contentType + '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON + '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML } - def 'Saving empty list element data fragment.'() { + def 'Saving empty list element data fragment for JSON/XML data.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') when: 'save data method is invoked with an empty list' - def jsonData = '{"branch": []}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp) + objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType) then: 'invalid data exception is thrown' thrown(DataValidationException) + where: + data | contentType + '{"branch": []}' | ContentType.JSON + '<test-tree><branch></branch></test-tree>' | ContentType.XML } def 'Get all data nodes #scenario.'() { @@ -362,11 +369,11 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) } - def 'Replace data node using singular data node: #scenario.'() { + def 'Replace data node using singular JSON data node: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath' - objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp) + objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp, ContentType.JSON) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) @@ -379,30 +386,63 @@ class CpsDataServiceImplSpec extends Specification { 'json list' | '/test-tree' | '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"] } - def 'Replace data node using multiple data nodes: #scenario.'() { + def 'Replace data node using singular XML data node: #scenario.'() { + given: 'schema set for given anchor and dataspace references test-tree model' + setupSchemaSetMocks('test-tree.yang') + when: 'replace data method is invoked with XML data #xmlData and parent node xpath #parentNodeXpath' + objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, xmlData, observedTimestamp, ContentType.XML) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, + { dataNode -> dataNode.xpath == expectedNodeXpath }) + and: 'the CpsValidator is called on the dataspaceName and AnchorName' + 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + where: 'following parameters were used' + scenario | parentNodeXpath | xmlData || expectedNodeXpath + 'level 2 node' | '/test-tree' | '<branch><name>Name</name></branch>' || ['/test-tree/branch[@name=\'Name\']'] + 'xml list' | '/test-tree' | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Name1</name></branch>' + '<branch><name>Name2</name></branch></test-tree>' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"] + } + + def 'Replace data node using multiple JSON data nodes: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') when: 'replace data method is invoked with a map of xpaths and json data' - objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp) + objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodeDataPerXPath, observedTimestamp, ContentType.JSON) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'following parameters were used' - scenario | nodesJsonData || expectedNodeXpath + scenario | nodeDataPerXPath || expectedNodeXpath 'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}'] || ["/test-tree", "/test-tree/branch[@name='Name']"] 'level 2 node' | ['/test-tree' : '{"branch": [{"name":"Name"}]}', '/test-tree/branch[@name=\'Name\']':'{"nest":{"name":"nestName"}}'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"] 'json list' | ['/test-tree' : '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}'] || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"] } + def 'Replace data node using multiple XML data nodes: #scenario.'() { + given: 'schema set for given anchor and dataspace references test-tree model' + setupSchemaSetMocks('test-tree.yang') + when: 'replace data method is invoked with a map of xpaths and XML data' + objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodeXmlDataPerXPath, observedTimestamp, ContentType.XML) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, + { dataNode -> dataNode.xpath == expectedNodeXpath }) + and: 'the CpsValidator is called on the dataspaceName and AnchorName' + 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + where: 'following parameters were used' + scenario | nodeXmlDataPerXPath || expectedNodeXpath + 'top level node' | ['/test-tree': '<branch><name>Name</name></branch>'] || ["/test-tree/branch[@name='Name']"] + 'level 2 node' | ['/test-tree': '<branch><name>Name</name></branch>', '/test-tree/branch[@name=\'Name\']': '<nest><name>nestName</name></nest>'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"] + 'xml list' | ['/test-tree': '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Name1</name></branch>' + '<branch><name>Name2</name></branch></test-tree>'] || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"] + } + def 'Replace data node with concurrency exception in persistence layer.'() { given: 'the persistence layer throws an concurrency exception' def originalException = new ConcurrencyException('message', 'details') mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException } setupSchemaSetMocks('test-tree.yang') when: 'attempt to replace data node' - objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp) + objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp, ContentType.JSON) then: 'the same exception is thrown up' def thrownUp = thrown(ConcurrencyException) assert thrownUp == originalException @@ -506,6 +546,20 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor2, '/', DELETE, observedTimestamp) } + def "Validating #scenario when dry run is enabled."() { + given: 'schema set for given anchors and dataspace references bookstore model' + setupSchemaSetMocks('bookstore.yang') + when: 'validating the data with the given parameters' + objectUnderTest.validateData(dataspaceName, anchorName, parentNodeXpath, data,contentType) + then: 'the appropriate yang parser method is invoked with correct parameters' + yangParser.validateData(contentType, data, anchor, xpath) + where: 'the following parameters were used' + scenario | parentNodeXpath | xpath | contentType | data + 'JSON data with root node xpath' | '/' | '' | ContentType.JSON | '{"bookstore":{"bookstore-name":"Easons"}}' + 'JSON data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.JSON | '{"bookstore-name":"Easons"}' + 'XML data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.XML | '<bookstore-name>Easons</bookstore-name>' + } + def 'Start session.'() { when: 'start session method is called' objectUnderTest.startSession() diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy index 8e17594bd1..ac7d4c0aa7 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy @@ -20,9 +20,9 @@ package org.onap.cps.api.impl +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsAdminPersistenceService import org.onap.cps.spi.model.Dataspace -import org.onap.cps.spi.utils.CpsValidator import spock.lang.Specification class CpsDataspaceServiceImplSpec extends Specification { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy index 42d75f3eab..f12afe61ec 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy @@ -51,8 +51,8 @@ class CpsDeltaServiceImplSpec extends Specification{ def 'Get delta between data nodes for ADDED data'() { when: 'attempt to get delta between 2 data nodes' def result = objectUnderTest.getDeltaReports([], targetDataNodeWithLeafData) - then: 'the delta report contains expected "add" action' - assert result[0].action.equals('add') + then: 'the delta report contains expected "create" action' + assert result[0].action.equals('create') and: 'the delta report contains expected xpath' assert result[0].xpath == '/parent' and: 'the delta report contains no source data' @@ -68,12 +68,12 @@ class CpsDeltaServiceImplSpec extends Specification{ when: 'attempt to get delta between 2 data nodes' def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) then: 'the delta report contains expected details for parent node' - assert result[0].action.equals('update') + assert result[0].action.equals('replace') assert result[0].xpath == '/parent' assert result[0].sourceData == ['parent-leaf': 'parent-payload'] assert result[0].targetData == ['parent-leaf': 'parent-payload-updated'] and: 'the delta report contains expected details for child node' - assert result[1].action.equals('update') + assert result[1].action.equals('replace') assert result[1].xpath == '/parent/child' assert result[1].sourceData == ['child-leaf': 'child-payload'] assert result[1].targetData == ['child-leaf': 'child-payload-updated'] @@ -82,8 +82,8 @@ class CpsDeltaServiceImplSpec extends Specification{ def 'Delta report between leaves, #scenario'() { when: 'attempt to get delta between 2 data nodes' def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) - then: 'the delta report contains expected "update" action' - assert result[0].action.equals('update') + then: 'the delta report contains expected "replace" action' + assert result[0].action.equals('replace') and: 'the delta report contains expected xpath' assert result[0].xpath == '/parent' and: 'the delta report contains expected source and target data' @@ -100,7 +100,7 @@ class CpsDeltaServiceImplSpec extends Specification{ def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() { when: 'attempt to get delta between 2 data nodes' def result = objectUnderTest.getDeltaReports(sourceDataNodeWithoutLeafData, targetDataNodeWithoutLeafData) - then: 'the delta report contains "update" action with right data' + then: 'the delta report is empty' assert result.isEmpty() } } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy index ad8c54bf27..1831506563 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy @@ -23,19 +23,15 @@ package org.onap.cps.api.impl -import org.onap.cps.api.CpsAnchorService - -import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED -import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED - import org.onap.cps.TestUtils +import org.onap.cps.api.CpsAnchorService +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.exceptions.DuplicatedYangResourceException import org.onap.cps.spi.exceptions.ModelValidationException import org.onap.cps.spi.exceptions.SchemaSetInUseException -import org.onap.cps.spi.model.ModuleDefinition -import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.spi.model.Anchor +import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference import org.onap.cps.spi.model.SchemaSet import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder @@ -43,6 +39,9 @@ import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import spock.lang.Specification +import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED +import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED + class CpsModuleServiceImplSpec extends Specification { def mockCpsModulePersistenceService = Mock(CpsModulePersistenceService) @@ -238,6 +237,23 @@ class CpsModuleServiceImplSpec extends Specification { 1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck) } + def 'Get module references when queried by attributes'() { + given: 'a valid dataspace name and anchor name' + def dataspaceName = 'someDataspace' + def anchorName = 'someAnchor' + and: 'a set of parent attributes and child attributes used for filtering' + def parentAttributes = ['some-property-key1': 'some-property-val1'] + def childAttributes = ['some-property-key2': 'some-property-val2'] + and: 'a list of expected module references returned by the persistence service' + def expectedModuleReferences = [new ModuleReference(moduleName: 'some-name', revision: 'some-revision')] + mockCpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes) >> expectedModuleReferences + when: 'the method is invoked to retrieve module references by attributes' + def actualModuleReferences = objectUnderTest.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes) + then: 'the retrieved module references should match the expected module references' + assert actualModuleReferences == expectedModuleReferences + } + + def 'Getting module definitions with module name'() { given: 'module persistence service returns module definitions for module name' def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ] diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy index 1ad5017919..3b10669ddb 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy @@ -21,10 +21,10 @@ package org.onap.cps.api.impl +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.PaginationOption -import org.onap.cps.spi.utils.CpsValidator import spock.lang.Specification class CpsQueryServiceImplSpec extends Specification { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 9e55e8f10a..9f3456280e 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -28,13 +28,13 @@ import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
import org.onap.cps.events.CpsDataUpdateEventsService
+import org.onap.cps.impl.utils.CpsValidator
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
import org.onap.cps.spi.model.Anchor
-import org.onap.cps.spi.utils.CpsValidator
+import org.onap.cps.utils.ContentType
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
-import org.onap.cps.utils.ContentType
import org.onap.cps.utils.YangParser
import org.onap.cps.utils.YangParserHelper
import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
@@ -171,6 +171,6 @@ class E2ENetworkSliceSpec extends Specification { expect: 'schema context is built with no exception indicating the schema set being valid '
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
and: 'data is parsed with no exception indicating the model match'
- new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null
+ new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '', false) != null
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy index a9f50ee5b0..189e28521b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy @@ -22,6 +22,7 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder @@ -34,8 +35,6 @@ import org.springframework.cache.annotation.EnableCaching import org.springframework.cache.caffeine.CaffeineCacheManager import org.springframework.test.context.ContextConfiguration import spock.lang.Specification -import org.onap.cps.spi.utils.CpsValidator - @SpringBootTest @EnableCaching diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy deleted file mode 100644 index 010308c42f..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy +++ /dev/null @@ -1,80 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 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.cache - -import com.hazelcast.config.Config -import com.hazelcast.core.Hazelcast -import com.hazelcast.map.IMap -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Specification - -@SpringBootTest -@ContextConfiguration(classes = [AnchorDataCacheConfig]) -class AnchorDataCacheConfigSpec extends Specification { - - @Autowired - private IMap<String, AnchorDataCacheEntry> anchorDataCache - - def 'Embedded (hazelcast) cache for Anchor Data.'() { - expect: 'system is able to create an instance of the Anchor data cache' - assert null != anchorDataCache - and: 'there is at least 1 instance' - assert Hazelcast.allHazelcastInstances.size() > 0 - and: 'anchorDataCache is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCpsCore') - } - - def 'Verify configs for Distributed Caches'(){ - given: 'the Anchor Data Cache config' - def anchorDataCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config - def anchorDataCacheMapConfig = anchorDataCacheConfig.mapConfigs.get('anchorDataCacheMapConfig') - expect: 'system created instance with correct config' - assert anchorDataCacheConfig.clusterName == 'cps-and-ncmp-test-caches' - assert anchorDataCacheMapConfig.backupCount == 1 - assert anchorDataCacheMapConfig.asyncBackupCount == 0 - } - - def 'Verify deployment network configs for Distributed Caches'() { - given: 'the Anchor Data Cache config' - def anchorDataCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config.networkConfig - expect: 'system created instance with correct config' - assert anchorDataCacheNetworkConfig.join.autoDetectionConfig.enabled - assert !anchorDataCacheNetworkConfig.join.kubernetesConfig.enabled - } - - def 'Verify network config'() { - given: 'Synchronization config object and test configuration' - def objectUnderTest = new AnchorDataCacheConfig() - def testConfig = new Config() - when: 'kubernetes properties are enabled' - objectUnderTest.cacheKubernetesEnabled = true - objectUnderTest.cacheKubernetesServiceName = 'test-service-name' - and: 'method called to update the discovery mode' - objectUnderTest.updateDiscoveryMode(testConfig) - then: 'applied properties are reflected' - assert testConfig.networkConfig.join.kubernetesConfig.enabled - assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' - - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy deleted file mode 100644 index f38b45d17c..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy +++ /dev/null @@ -1,40 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.cache - - -import spock.lang.Specification - -class AnchorDataCacheEntrySpec extends Specification { - - def objectUnderTest = new AnchorDataCacheEntry() - - def 'Anchor Data Cache Properties Management.'() { - when: 'a property named "sample" is added to the cache' - objectUnderTest.setProperty('sample', 123) - then: 'the cache has that property' - assert objectUnderTest.hasProperty('sample') - and: 'the value is correct' - assert objectUnderTest.getProperty('sample') == 123 - and: 'the cache does not have an an object called "something else"' - assert objectUnderTest.hasProperty('something else') == false - } -} diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy deleted file mode 100644 index 022cd74ea6..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 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.cache - -import com.hazelcast.config.Config -import com.hazelcast.config.RestEndpointGroup -import spock.lang.Specification - -class HazelcastCacheConfigSpec extends Specification { - - def objectUnderTest = new HazelcastCacheConfig() - - def 'Create Hazelcast instance with a #scenario'() { - given: 'a cluster name' - objectUnderTest.clusterName = 'my cluster' - when: 'an hazelcast instance is created (name has to be unique)' - def result = objectUnderTest.createHazelcastInstance(scenario, config) - then: 'the instance is created and has the correct name' - assert result.name == scenario - and: 'if applicable it has a map config with the expected name' - if (expectMapConfig) { - assert result.config.mapConfigs.values()[0].name == 'my map config' - } else { - assert result.config.mapConfigs.isEmpty() - } - and: 'if applicable it has a queue config with the expected name' - if (expectQueueConfig) { - assert result.config.queueConfigs.values()[0].name == 'my queue config' - } else { - assert result.config.queueConfigs.isEmpty() - } - and: 'if applicable it has a set config with the expected name' - if (expectSetConfig) { - assert result.config.setConfigs.values()[0].name == 'my set config' - } else { - assert result.config.setConfigs.isEmpty() - } - where: 'the following configs are used' - scenario | config || expectMapConfig | expectQueueConfig | expectSetConfig - 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false | false - 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true | false - 'Set Config' | HazelcastCacheConfig.createSetConfig('my set config') || false | false | true - } - - def 'Verify Hazelcast Cluster Information'() { - given: 'a test configuration' - def testConfig = new Config() - when: 'cluster information is exposed' - objectUnderTest.exposeClusterInformation(testConfig) - then: 'REST api configs are enabled' - assert testConfig.networkConfig.restApiConfig.enabled - and: 'only health check and cluster read are enabled' - def enabledGroups = testConfig.networkConfig.restApiConfig.enabledGroups - assert enabledGroups.size() == 2 - assert enabledGroups.containsAll([RestEndpointGroup.CLUSTER_READ, RestEndpointGroup.HEALTH_CHECK]) - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index e305abee86..f028d5d5d9 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -35,6 +35,7 @@ class DataNodeBuilderSpec extends Specification { def objectUnderTest = new DataNodeBuilder() def yangParserHelper = new YangParserHelper() + def validateAndParse = false def expectedLeavesByXpathMap = [ '/test-tree' : [], @@ -60,7 +61,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('test-tree.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -80,7 +81,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse) when: 'the container node is converted to a data node with parent node xpath defined' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -96,7 +97,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node ' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -129,7 +130,7 @@ class DataNodeBuilderSpec extends Specification { def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']" and: 'the json data fragment parsed into container node object for given parent node xpath' def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse) when: 'the container node is converted to a data node with given parent node xpath' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() then: 'the resulting data node represents a child of augmentation node' @@ -144,7 +145,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data fragment parsed into container node object' def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -162,7 +163,7 @@ class DataNodeBuilderSpec extends Specification { and: 'parent node xpath referencing parent of list element' def parentNodeXpath = '/test-tree' and: 'the json data fragment (list element) parsed into container node object' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse) when: 'the container node is converted to a data node collection' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() def resultXpaths = result.collect { it.getXpath() } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy index e19d120421..00b5499c2f 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DeltaReportBuilderSpec.groovy @@ -24,20 +24,20 @@ import spock.lang.Specification class DeltaReportBuilderSpec extends Specification{ - def 'Generating delta report with for add action'() { + def 'Generating delta report for "create" action'() { when: 'delta report is generated' def result = new DeltaReportBuilder() - .actionAdd() + .actionCreate() .withXpath('/xpath') .withTargetData(['data':'leaf-data']) .build() - then: 'the delta report contains the "add" action with expected target data' - assert result.action == 'add' + then: 'the delta report contains the "create" action with expected target data' + assert result.action == 'create' assert result.xpath == '/xpath' assert result.targetData == ['data': 'leaf-data'] } - def 'Generating delta report with attributes for remove action'() { + def 'Generating delta report with attributes for "remove" action'() { when: 'delta report is generated' def result = new DeltaReportBuilder() .actionRemove() diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy index 8cbd493550..09d45b92c0 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy @@ -42,7 +42,7 @@ class JsonObjectMapperSpec extends Specification { def contentMap = new JsonSlurper().parseText(new String(content)) and: 'the parsed content is as expected' assert contentMap.'test:bookstore'.'bookstore-name' == 'Chapters/Easons' - where: 'the following data stores are used' + where: 'the following content types are used' type << ['String', 'bytes'] } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy index b975de6555..13b042f1ae 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. * ================================================================================ @@ -22,11 +22,8 @@ package org.onap.cps.utils -import com.hazelcast.map.IMap import org.onap.cps.TestUtils -import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.impl.YangTextSchemaSourceSetCache -import org.onap.cps.cache.AnchorDataCacheEntry import org.onap.cps.spi.model.Anchor import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder @@ -34,13 +31,9 @@ import spock.lang.Specification class PrefixResolverSpec extends Specification { - def mockCpsAnchorService = Mock(CpsAnchorService) - def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) - def mockAnchorDataCache = Mock(IMap<String, AnchorDataCacheEntry>) - - def objectUnderTest = new PrefixResolver(mockCpsAnchorService, mockYangTextSchemaSourceSetCache, mockAnchorDataCache) + def objectUnderTest = new PrefixResolver(mockYangTextSchemaSourceSetCache) def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) @@ -48,29 +41,16 @@ class PrefixResolverSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - def setup() { - given: 'an anchor for the test-tree model' - def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor') - and: 'the system can get this anchor' - mockCpsAnchorService.getAnchor('testDataspace', 'testAnchor') >> anchor - and: 'the schema source cache contains the schema context for the test-tree module' - mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext - } + def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor') def 'get xpath prefix using node schema context'() { + given: 'the schema source cache contains the schema context for the test-tree module' + mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext + mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet when: 'the prefix of the yang module is retrieved' - def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', xpath) + def result = objectUnderTest.getPrefix(anchor, xpath) then: 'the expected prefix is returned' result == expectedPrefix - and: 'the cache is updated for the given anchor with a map of prefixes per top level container (just one one this case)' - 1 * mockAnchorDataCache.put('testAnchor',_ , _ ,_) >> { args -> { - def prefixPerContainerName = args[1].getProperty("prefixPerContainerName") - assert prefixPerContainerName.size() == 1 - assert prefixPerContainerName.get('test-tree') == 'tree' - } - } - and: 'schema source cache is used (i.e. need to build schema context)' - 1 * mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet where: 'the following scenarios are applied' xpath || expectedPrefix '/test-tree' || 'tree' @@ -82,37 +62,4 @@ class PrefixResolverSpec extends Specification { '/not-defined' || '' } - def 'get prefix with populated anchor data cache with #scenario cache entry'() { - given: 'anchor data cache is populated for the anchor with a prefix for top level container named #cachedTopLevelContainerName' - def anchorDataCacheEntry = new AnchorDataCacheEntry() - def prefixPerContainerName = [(cachedTopLevelContainerName): 'cachedPrefix'] - anchorDataCacheEntry.setProperty('prefixPerContainerName',prefixPerContainerName) - mockAnchorDataCache.containsKey('testAnchor') >> true - mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry - when: 'the prefix of the yang module is retrieved' - def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree') - then: 'the expected prefix is returned' - result == expectedPrefix - and: 'schema source cache is not used (i.e. no need to build schema context)' - 0 * mockYangTextSchemaSourceSetCache.get(*_) - where: 'the following scenarios are applied' - scenario | cachedTopLevelContainerName || expectedPrefix - 'matching' | 'test-tree' || 'cachedPrefix' - 'non-matching' | 'other' || '' - } - - def 'get prefix with other (non relevant) data in anchor data cache'() { - given: 'anchor data cache is populated with non relevant other property' - def anchorDataCacheEntry = new AnchorDataCacheEntry() - anchorDataCacheEntry.setProperty('something else', 'does not matter') - mockAnchorDataCache.containsKey('testAnchor') >> true - mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry - when: 'the prefix of the yang module is retrieved' - def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree') - then: 'the expected prefix is returned' - result == 'tree' - and: 'schema source cache is used (i.e. need to build schema context)' - 1 * mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet - } - } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy index 073383113d..e1490c28ab 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy @@ -30,6 +30,8 @@ import spock.lang.Specification class YangParserHelperSpec extends Specification { def objectUnderTest = new YangParserHelper() + def validateOnly = true + def validateAndParse = false def 'Parsing a valid multicontainer Json String.'() { given: 'a yang model (file)' @@ -38,7 +40,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'the json data is parsed' - def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '') + def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) then: 'a ContainerNode holding collection of normalized nodes is returned' result.body().getAt(index) instanceof NormalizedNode == true then: 'qualified name of children created is as expected' @@ -56,7 +58,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'the data is parsed' - NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '') + NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '', validateAndParse) then: 'the result is a normalized node of the correct type' if (revision) { result.identifier.nodeType == QName.create(namespace, revision, localName) @@ -74,7 +76,7 @@ class YangParserHelperSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() when: 'invalid data is parsed' - objectUnderTest.parseData(contentType, invalidData, schemaContext, '') + objectUnderTest.parseData(contentType, invalidData, schemaContext, '', validateAndParse) then: 'an exception is thrown' thrown(DataValidationException) where: 'the following invalid data is provided' @@ -92,7 +94,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'json string is parsed' - def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath) + def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath, validateAndParse) then: 'a ContainerNode holding collection of normalized nodes is returned' result.body().getAt(0) instanceof NormalizedNode == true then: 'result represents a node of expected type' @@ -112,7 +114,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'json string is parsed' - objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath) + objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath, validateAndParse) then: 'expected exception is thrown' thrown(DataValidationException) where: @@ -129,7 +131,7 @@ class YangParserHelperSpec extends Specification { def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() when: 'malformed json string is parsed' - objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '') + objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '', validateAndParse) then: 'an exception is thrown' thrown(DataValidationException) where: 'the following malformed json is provided' @@ -145,7 +147,7 @@ class YangParserHelperSpec extends Specification { and: 'some json data with space in the array elements' def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json') when: 'that json data is parsed' - objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '') + objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '', validateAndParse) then: 'no exception thrown' noExceptionThrown() } @@ -162,5 +164,22 @@ class YangParserHelperSpec extends Specification { 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories'] } + def 'Validating #scenario xpath String.'() { + given: 'a data model (file) is provided' + def fileData = TestUtils.getResourceFileContent(contentFile) + and: 'the schema context is built for that data model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'the data is parsed to be validated' + objectUnderTest.parseData(contentType, fileData, schemaContext, parentNodeXpath, validateOnly) + then: 'no exception is thrown' + noExceptionThrown() + where: + scenario | parentNodeXpath | contentFile | contentType + 'JSON without parent node' | '' | 'bookstore.json' | ContentType.JSON + 'JSON with parent node' | '/bookstore' | 'bookstore-categories-data.json' | ContentType.JSON + 'XML without parent node' | '' | 'bookstore.xml' | ContentType.XML + 'XML with parent node' | '/bookstore' | 'bookstore-categories-data.xml' | ContentType.XML + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy index 18d0502e30..6c52becbe1 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy @@ -26,7 +26,6 @@ import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.Anchor import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode import org.opendaylight.yangtools.yang.model.api.SchemaContext import spock.lang.Specification @@ -47,6 +46,8 @@ class YangParserSpec extends Specification { def containerNodeFromYangUtils = Mock(ContainerNode) def noParent = '' + def validateOnly = true + def validateAndParse = false def setup() { mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet @@ -55,7 +56,7 @@ class YangParserSpec extends Specification { def 'Parsing data.'() { given: 'the yang parser (utility) always returns a container node' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils when: 'parsing some json data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'the schema source set for the correct dataspace and schema set is retrieved form the cache' @@ -68,7 +69,7 @@ class YangParserSpec extends Specification { def 'Parsing data with exception on first attempt.'() { given: 'the yang parser throws an exception on the first attempt only' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils when: 'attempt to parse some data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'the cache is cleared for the correct dataspace and schema' @@ -79,7 +80,7 @@ class YangParserSpec extends Specification { def 'Parsing data with exception on all attempts.'() { given: 'the yang parser always throws an exception' - mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) } when: 'attempt to parse some data' objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) then: 'a data validation exception is thrown' @@ -94,9 +95,46 @@ class YangParserSpec extends Specification { when: 'parsing some json data' def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent) then: 'the yang parser helper always returns a container node' - 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils and: 'the result is the same container node as return from yang utils' assert result == containerNodeFromYangUtils } + def 'Validating #scenario data using Yang parser with cache retrieval.'() { + given: 'the yang parser (utility) is set up and schema context is available' + mockYangParserHelper.parseData(contentType, 'some json', mockSchemaContext, noParent, validateOnly) + when: 'attempt to parse data with no parent node xpath' + objectUnderTest.validateData(contentType, 'some json or xml data', anchor, noParent) + then: 'the correct schema set is retrieved from the cache for the dataspace and schema' + 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet + and: 'no cache entries are removed during validation' + 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_) + where: + scenario | contentType + 'JSON' | ContentType.JSON + 'XML' | ContentType.XML + } + + def 'Validating data when parsing fails on first attempt and recovers.'() { + given: 'the Yang parser throws an exception on the first attempt but succeeds on the second' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } >> null + when: 'attempting to parse JSON data' + objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent) + then: 'the cache is cleared for the correct dataspace and schema after the first failure' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + and: 'no exceptions are thrown after the second attempt' + noExceptionThrown() + } + + def 'Validating data with repeated parsing failures leading to exception.'() { + given: 'the yang parser throws an exception on the first attempt only' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } + when: 'attempting to parse JSON data' + objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the cache is cleared for the correct dataspace and schema after the failure' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + } + } diff --git a/cps-service/src/test/resources/application.yml b/cps-service/src/test/resources/application.yml index b666885f22..be71d37d2a 100644 --- a/cps-service/src/test/resources/application.yml +++ b/cps-service/src/test/resources/application.yml @@ -1,6 +1,6 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021 Bell Canada. -# Modification Copyright (C) 2022 Nordix Foundation. +# Modification Copyright (C) 2022-2024 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -41,11 +41,3 @@ spring: logging: level: org.apache.kafka: ERROR - -# Custom Hazelcast Config. -hazelcast: - cluster-name: "cps-and-ncmp-test-caches" - mode: - kubernetes: - enabled: false - service-name: "cps-and-ncmp-service" diff --git a/cps-service/src/test/resources/bookstore-categories-data.json b/cps-service/src/test/resources/bookstore-categories-data.json new file mode 100644 index 0000000000..7dc22b17f7 --- /dev/null +++ b/cps-service/src/test/resources/bookstore-categories-data.json @@ -0,0 +1,49 @@ +{ + "categories": [ + { + "code": "01/1", + "name": "SciFi", + "books": [ + { + "authors": [ + "Iain M. Banks" + ], + "lang": "en/it", + "price": "895", + "pub_year": "1994", + "title": "Feersum Endjinn/Endjinn Feersum" + }, + { + "authors": [ + "Ursula K. Le Guin", + "Joe Haldeman", + "Orson Scott Card", + "david Brin", + "Rober Silverberg", + "Dan Simmons", + "Greg Bear" + ], + "lang": "en", + "price": "1099", + "pub_year": "1999", + "title": "Far Horizons" + } + ] + }, + { + "name": "kids", + "code": "02", + "books": [ + { + "authors": [ + "Philip Pullman" + ], + "lang": "en", + "price": "699", + "pub_year": "1995", + "title": "The Golden Compass" + } + ] + } + ] +}
\ No newline at end of file diff --git a/cps-service/src/test/resources/bookstore-categories-data.xml b/cps-service/src/test/resources/bookstore-categories-data.xml new file mode 100644 index 0000000000..c8592c1f90 --- /dev/null +++ b/cps-service/src/test/resources/bookstore-categories-data.xml @@ -0,0 +1,14 @@ +<?xml version='1.0' encoding='UTF-8'?> +<categories> + <code>1</code> + <name>SciFi</name> + <books> + <title>2001: A Space Odyssey</title> + <lang>en</lang> + <authors> + Iain M. Banks + </authors> + <pub_year>1994</pub_year> + <price>895</price> + </books> +</categories>
\ No newline at end of file diff --git a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang index 32517398a3..2dabd79e8e 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang @@ -36,7 +36,7 @@ module cps-cavsta-onap-internal { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef Tac { diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang index c16a682512..2401409443 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang @@ -34,7 +34,7 @@ module cps-ran-inventory { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef Mcc { diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang index 5fd292a99d..3223b15e65 100644 --- a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang +++ b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang @@ -43,14 +43,14 @@ module cps-ran-schema-model { description "Added support for OOF PCI SON Use case"; reference - "https://wiki.onap.org/display/DW/CPS+APIs"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16456851/CPS+APIs"; } revision 2021-01-28 { description "CPS RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef usageState { diff --git a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang index 5065659307..a4612e73fb 100755 --- a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang +++ b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang @@ -43,7 +43,7 @@ module ran-network { description "RAN Network YANG Model for ONAP/O-RAN POC"; reference - "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; + "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin"; } typedef usageState { |