aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json61
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java13
-rw-r--r--csit/tests/cps-data/cps-data.robot5
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java21
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml5
-rw-r--r--docker-compose/docker-compose.yml3
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy34
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy10
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstore.yang6
9 files changed, 141 insertions, 17 deletions
diff --git a/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json
new file mode 100644
index 0000000000..18f83ccf86
--- /dev/null
+++ b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json
@@ -0,0 +1,61 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "urn:cps:org.onap.cps:data-updated-event-schema:1.0.0",
+ "$ref": "#/definitions/CpsDataUpdatedEvent",
+ "definitions": {
+ "CpsDataUpdatedEvent": {
+ "description": "The payload for CPS data updated event.",
+ "type": "object",
+ "javaType": "org.onap.cps.events.model.CpsDataUpdatedEvent",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "observedTimestamp": {
+ "description": "The timestamp when the data has been observed. The expected format is 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'. Ex: '2020-12-01T00:00:00.000+0000' ",
+ "type": "string"
+ },
+ "dataspaceName": {
+ "description": "The name of CPS Core dataspace the data belongs to.",
+ "type": "string"
+ },
+ "schemaSetName": {
+ "description": "The name of CPS Core schema set the data adheres to.",
+ "type": "string"
+ },
+ "anchorName": {
+ "description": "The name of CPS Core anchor the data is attached to.",
+ "type": "string"
+ },
+ "operation": {
+ "description": "The operation on the data",
+ "type": "string",
+ "enum": [
+ "CREATE",
+ "UPDATE",
+ "DELETE"
+ ]
+ },
+ "xpath": {
+ "description": "xpath of the updated content",
+ "type": "string"
+ }
+ },
+ "required": [
+ "observedTimestamp",
+ "dataspaceName",
+ "schemaSetName",
+ "anchorName",
+ "operation",
+ "xpath"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "data"
+ ]
+ }
+ }
+}
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 b040af5bb4..9859acdf0e 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
@@ -2,7 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Bell Canada. All rights reserved.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2023 Nordix Foundation.
+ * Modifications Copyright (C) 2022-2024 Nordix Foundation.
* Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,6 +34,7 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.utils.YangUtils;
+import org.opendaylight.yangtools.yang.common.Ordering;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@@ -242,10 +243,14 @@ public class DataNodeBuilder {
private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) {
final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName();
- final List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
+ List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
.stream()
- .map(normalizedNode -> (normalizedNode).body())
- .collect(Collectors.toUnmodifiableList());
+ .map(NormalizedNode::body)
+ .collect(Collectors.toList());
+ if (leafSetNode.ordering() == Ordering.SYSTEM) {
+ leafListValues.sort(null);
+ }
+ leafListValues = Collections.unmodifiableList(leafListValues);
addYangLeaf(currentDataNode, leafListName, (Serializable) leafListValues);
}
diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot
index f506b28011..e83857caea 100644
--- a/csit/tests/cps-data/cps-data.robot
+++ b/csit/tests/cps-data/cps-data.robot
@@ -59,10 +59,7 @@ Get Updated Data Node by XPath
Should Be Equal As Strings ${responseJson['name']} Bigger
${length_birds}= Get Length ${responseJson['birds']}
Should Be Equal As Integers ${length_birds} 3
- ${expected_list}= Create List Pigeon Falcon Eagle
- FOR ${item_to_check} IN @{expected_list}
- Should Contain ${responseJson['birds']} ${item_to_check}
- END
+ Should Be Equal As Strings ${responseJson['birds']} ['Eagle', 'Falcon', 'Pigeon']
Get Data Node by XPath
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors/${anchorName}/node
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
index a4f7111324..772eb050c6 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
@@ -69,6 +69,15 @@ public class DmiRestStubController {
@Value("${app.ncmp.async-m2m.topic}")
private String ncmpAsyncM2mTopic;
+ @Value("${delay.module-references-delay-ms}")
+ private long moduleReferencesDelayMs;
+
+ @Value("${delay.module-resources-delay-ms}")
+ private long moduleResourcesDelayMs;
+
+ @Value("${delay.data-for-cm-handle-delay-ms}")
+ private long dataForCmHandleDelayMs;
+
private String dataOperationEventType = "org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent";
/**
@@ -82,6 +91,7 @@ public class DmiRestStubController {
@PostMapping("/v1/ch/{cmHandleId}/modules")
public ResponseEntity<String> getModuleReferences(@PathVariable final String cmHandleId,
@RequestBody final Object moduleReferencesRequest) {
+ delay(moduleReferencesDelayMs);
final String moduleResponseContent = getModuleResourceResponse(cmHandleId,
"ModuleResponse.json");
log.info("cm handle: {} requested for modules", cmHandleId);
@@ -100,6 +110,7 @@ public class DmiRestStubController {
public ResponseEntity<String> retrieveModuleResources(
@PathVariable final String cmHandleId,
@RequestBody final Object moduleResourcesReadRequest) {
+ delay(moduleResourcesDelayMs);
final String moduleResourcesResponseContent = getModuleResourceResponse(cmHandleId,
"ModuleResourcesResponse.json");
log.info("cm handle: {} requested for modules resources", cmHandleId);
@@ -121,6 +132,7 @@ public class DmiRestStubController {
final String requestId,
@RequestBody final DmiDataOperationRequest
dmiDataOperationRequest) {
+ delay(dataForCmHandleDelayMs);
try {
log.info("Request received from the NCMP to DMI Plugin: {}",
objectMapper.writeValueAsString(dmiDataOperationRequest));
@@ -199,4 +211,13 @@ public class DmiRestStubController {
return ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
ResourceLoader.CLASSPATH_URL_PREFIX + "module/ietfYang" + moduleResponseType));
}
+
+ private void delay(final long milliseconds) {
+ try {
+ Thread.sleep(milliseconds);
+ } catch (final InterruptedException e) {
+ log.error("Thread sleep interrupted: {}", e.getMessage());
+ Thread.currentThread().interrupt();
+ }
+ }
}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml
index 8e39a4e71c..de097a67b4 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/resources/application.yml
@@ -40,3 +40,8 @@ app:
ncmp:
async-m2m:
topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+
+delay:
+ module-references-delay-ms: ${MODULE_REFERENCES_DELAY_MS:100}
+ module-resources-delay-ms: ${MODULE_RESOURCES_DELAY_MS:1000}
+ data-for-cm-handle-delay-ms: ${DATA_FOR_CM_HANDLE_DELAY_MS:2500}
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index 906945de4b..9ccb64b95b 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -129,6 +129,9 @@ services:
KAFKA_BOOTSTRAP_SERVER: kafka:29092
NCMP_CONSUMER_GROUP_ID: ncmp-group
NCMP_ASYNC_M2M_TOPIC: ncmp-async-m2m
+ MODULE_REFERENCES_DELAY_MS: 100
+ MODULE_RESOURCES_DELAY_MS: 1000
+ DATA_FOR_CM_HANDLE_DELAY_MS: 2500
restart: unless-stopped
profiles:
- dmi-stub
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
index 64996536e6..f967c62037 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
@@ -423,8 +423,34 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
then: 'the updated data nodes are retrieved'
def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
and: 'the leaf values are updated as expected'
- assert result.leaves['lang'] == ['English/French']
- assert result.leaves['price'] == [100]
+ assert result[0].leaves['lang'] == 'English/French'
+ assert result[0].leaves['price'] == 100
+ cleanup:
+ restoreBookstoreDataAnchor(2)
+ }
+
+ def 'Order of leaf-list elements is preserved when "ordered-by user" is set in the YANG model.'() {
+ given: 'Updated json for bookstore data'
+ def jsonData = "{'book-store:books':{'title':'Matilda', 'authors': ['beta', 'alpha', 'gamma', 'delta']}}"
+ when: 'update is performed for leaves'
+ objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+ and: 'the updated data nodes are retrieved'
+ def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
+ then: 'the leaf-list values have expected order'
+ assert result[0].leaves['authors'] == ['beta', 'alpha', 'gamma', 'delta']
+ cleanup:
+ restoreBookstoreDataAnchor(2)
+ }
+
+ def 'Leaf-list elements are sorted when "ordered-by user" is not set in the YANG model.'() {
+ given: 'Updated json for bookstore data'
+ def jsonData = "{'book-store:books':{'title':'Matilda', 'editions': [2011, 1988, 2001, 2022, 2025]}}"
+ when: 'update is performed for leaves'
+ objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+ and: 'the updated data nodes are retrieved'
+ def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
+ then: 'the leaf-list values have natural order'
+ assert result[0].leaves['editions'] == [1988, 2001, 2011, 2022, 2025]
cleanup:
restoreBookstoreDataAnchor(2)
}
@@ -540,7 +566,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
def expectedSourceDataInParentNode = ['name':'Children']
def expectedTargetDataInParentNode = ['name':'Kids']
def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]]
- def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[2023, 1988, 2000]]]
+ def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[1988, 2000, 2023]]]
when: 'attempt to get delta between leaves of existing data nodes'
def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
def deltaReportEntities = getDeltaReportEntities(result)
@@ -555,7 +581,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
assert deltaReportEntities.get('xpaths').containsAll(["/bookstore/categories[@code='1']/books[@title='The Gruffalo']", "/bookstore/categories[@code='1']/books[@title='Matilda']"])
and: 'the delta report also has expected source and target data of child nodes'
assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode)
- //assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode) CPS-2057
+ assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
}
def getDeltaReportEntities(List<DeltaReport> deltaReport) {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
index 3807a14bc5..b7b6fa11a7 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
@@ -36,8 +36,8 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
CpsModuleService objectUnderTest
private static def originalNumberOfModuleReferences = 2 // bookstore has two modules
- private static def bookStoreModuleReference = new ModuleReference('stores','2024-01-30')
- private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-01-30', 'org:onap:cps:sample')
+ private static def bookStoreModuleReference = new ModuleReference('stores','2024-02-08')
+ private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-02-08', 'org:onap:cps:sample')
private static def bookStoreTypesModuleReference = new ModuleReference('bookstore-types','2024-01-30')
private static def bookStoreTypesModuleReferenceWithNamespace = new ModuleReference('bookstore-types','2024-01-30', 'org:onap:cps:types:sample')
static def NEW_RESOURCE_REVISION = '2023-05-10'
@@ -155,7 +155,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
def result = objectUnderTest.getModuleDefinitionsByAnchorName(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1)
then: 'the correct module definitions are returned'
assert result.size() == 2
- assert result.contains(new ModuleDefinition('stores','2024-01-30',bookstoreModelFileContent))
+ assert result.contains(new ModuleDefinition('stores','2024-02-08',bookstoreModelFileContent))
assert result.contains(new ModuleDefinition('bookstore-types','2024-01-30', bookstoreTypesFileContent))
}
@@ -165,12 +165,12 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
then: 'the correct module definitions are returned'
if (expectedNumberOfDefinitions > 0) {
assert result.size() == expectedNumberOfDefinitions
- def expectedModuleDefinition = new ModuleDefinition('stores', '2024-01-30', bookstoreModelFileContent)
+ def expectedModuleDefinition = new ModuleDefinition('stores', '2024-02-08', bookstoreModelFileContent)
assert result[0] == expectedModuleDefinition
}
where: 'following parameters are used'
scenarios | moduleName | moduleRevision || expectedNumberOfDefinitions
- 'correct module name and revision' | 'stores' | '2024-01-30' || 1
+ 'correct module name and revision' | 'stores' | '2024-02-08' || 1
'correct module name' | 'stores' | null || 1
'incorrect module name' | 'other' | null || 0
'incorrect revision' | 'stores' | '2025-11-22' || 0
diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang
index 2abde656d4..0d093ea36c 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstore.yang
+++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang
@@ -9,6 +9,11 @@ module stores {
revision-date 2024-01-30;
}
+ revision "2024-02-08" {
+ description
+ "Order of book authors is preserved";
+ }
+
revision "2024-01-30" {
description
"Extracted bookstore types";
@@ -107,6 +112,7 @@ module stores {
type string;
}
leaf-list authors {
+ ordered-by user;
type string;
}
leaf-list editions {