diff options
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 { |