Merge "Missing test for getDataNodes (ep1)"
authorLuke Gleeson <luke.gleeson@est.tech>
Tue, 22 Aug 2023 08:49:37 +0000 (08:49 +0000)
committerGerrit Code Review <gerrit@onap.org>
Tue, 22 Aug 2023 08:49:37 +0000 (08:49 +0000)
48 files changed:
.github/workflows/gerrit-verify.yaml [new file with mode: 0644]
cps-application/src/main/resources/application.yml
cps-ncmp-events/src/main/resources/schemas/cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json
cps-ncmp-rest/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumer.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper.java with 64% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/ResponseTimeoutTask.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionEvent.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionStatus.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapperSpec.groovy with 52% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy
cps-ncmp-service/src/test/resources/cmSubscriptionDmiOutEvent.json
cps-ncmp-service/src/test/resources/cmSubscriptionEvent.json [new file with mode: 0644]
cps-ncmp-service/src/test/resources/cmSubscriptionNcmpOutEvent.json
cps-ncmp-service/src/test/resources/dataOperationRequest.json
cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json [new file with mode: 0644]
cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java
cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java
cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy
docs/release-notes.rst
integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
integration-test/src/test/resources/data/tree/new-test-tree.json [new file with mode: 0644]
integration-test/src/test/resources/data/tree/new-test-tree.yang [new file with mode: 0644]
integration-test/src/test/resources/data/tree/updated-test-tree.json [new file with mode: 0644]
integration-test/src/test/resources/data/tree/updated-test-tree.yang [new file with mode: 0644]

diff --git a/.github/workflows/gerrit-verify.yaml b/.github/workflows/gerrit-verify.yaml
new file mode 100644 (file)
index 0000000..80447d3
--- /dev/null
@@ -0,0 +1,102 @@
+---
+name: Gerrit Composed rtdv3 Verify
+
+# yamllint disable-line rule:truthy
+on:
+  workflow_dispatch:
+    inputs:
+      GERRIT_BRANCH:
+        description: "Branch that change is against"
+        required: true
+        type: string
+      GERRIT_CHANGE_ID:
+        description: "The ID for the change"
+        required: true
+        type: string
+      GERRIT_CHANGE_NUMBER:
+        description: "The Gerrit number"
+        required: true
+        type: string
+      GERRIT_CHANGE_URL:
+        description: "URL to the change"
+        required: true
+        type: string
+      GERRIT_EVENT_TYPE:
+        description: "Type of Gerrit event"
+        required: true
+        type: string
+      GERRIT_PATCHSET_NUMBER:
+        description: "The patch number for the change"
+        required: true
+        type: string
+      GERRIT_PATCHSET_REVISION:
+        description: "The revision sha"
+        required: true
+        type: string
+      GERRIT_PROJECT:
+        description: "Project in Gerrit"
+        required: true
+        type: string
+      GERRIT_REFSPEC:
+        description: "Gerrit refspec of change"
+        required: true
+        type: string
+
+jobs:
+  prepare:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Clear votes
+        # yamllint disable-line rule:line-length
+        uses: lfit/gerrit-review-action@6ac4c2322b68c0120a9b516eb0421491ee1b3fdf  # v0.4
+        with:
+          host: ${{ vars.GERRIT_SERVER }}
+          username: ${{ vars.GERRIT_SSH_USER }}
+          key: ${{ secrets.GERRIT_SSH_PRIVKEY }}
+          known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }}
+          gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }}
+          gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }}
+          vote-type: clear
+          comment-only: true
+      - name: Allow replication
+        run: sleep 10s
+
+  rtd-validation:
+    needs: prepare
+    # use compose-jjb-verify from the v0.4 series of releng-reusable-workflows
+    # yamllint disable-line rule:line-length
+    uses: lfit/releng-reusable-workflows/.github/workflows/compose-rtdv3-verify@main
+    with:
+      GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }}
+      GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }}
+      GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }}
+      GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }}
+      GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }}
+      GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }}
+      GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }}
+      GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }}
+      GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }}
+    secrets:
+      RTD_TOKEN: ${{ secrets.RTD_TOKEN }}
+
+  vote:
+    if: ${{ always() }}
+    # yamllint enable rule:line-length
+    needs: [prepare, rtd-validation]
+    runs-on: ubuntu-latest
+    steps:
+      - name: Get conclusion
+        # yamllint disable-line rule:line-length
+        uses: technote-space/workflow-conclusion-action@45ce8e0eb155657ab8ccf346ade734257fd196a5  # v3.0.3
+      - name: Set vote
+        # yamllint disable-line rule:line-length
+        uses: lfit/gerrit-review-action@6ac4c2322b68c0120a9b516eb0421491ee1b3fdf  # v0.4
+        with:
+          host: ${{ vars.GERRIT_SERVER }}
+          username: ${{ vars.GERRIT_SSH_USER }}
+          key: ${{ secrets.GERRIT_SSH_PRIVKEY }}
+          known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }}
+          gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }}
+          gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }}
+          vote-type: ${{ env.WORKFLOW_CONCLUSION }}
+          comment-only: true
index a18de2a..6aefda9 100644 (file)
@@ -109,6 +109,8 @@ app:
     dmi:
         cm-events:
             topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events}
+        device-heartbeat:
+            topic: ${DMI_DEVICE_HEARTBEAT_TOPIC:dmi-device-heartbeat}
 
 
 notification:
index c08a4ea..ebbdde9 100644 (file)
@@ -14,8 +14,7 @@
           "type": "string",
           "enum": [
             "ACCEPTED",
-            "REJECTED",
-            "PENDING"
+            "REJECTED"
           ]
         },
         "details" : {
index 63c5f16..e60e174 100644 (file)
@@ -34,7 +34,7 @@
     <artifactId>cps-ncmp-rest</artifactId>
 
     <properties>
-        <minimum-coverage>0.99</minimum-coverage>
+        <minimum-coverage>1.00</minimum-coverage>
     </properties>
 
     <dependencies>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java
new file mode 100644 (file)
index 0000000..816fc50
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * ============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.ncmp.api.impl.config.embeddedcache;
+
+import com.hazelcast.collection.ISet;
+import com.hazelcast.config.SetConfig;
+import org.onap.cps.cache.HazelcastCacheConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TrustLevelCacheConfig extends HazelcastCacheConfig {
+
+    private static final SetConfig untrustworthyCmHandlesSetConfig = createSetConfig("untrustworthyCmHandlesSetConfig");
+
+    /**
+     * Untrustworthy cmhandle set instance.
+     *
+     * @return instance of distributed set of untrustworthy cmhandles.
+     */
+    @Bean
+    public ISet<String> untrustworthyCmHandlesSet() {
+        return createHazelcastInstance("untrustworthyCmHandlesSet", untrustworthyCmHandlesSetConfig).getSet(
+                "untrustworthyCmHandlesSet");
+    }
+
+
+}
index 1ac4044..9459778 100644 (file)
@@ -36,6 +36,7 @@ import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionPersistence;
 import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus;
 import org.onap.cps.ncmp.api.impl.utils.DataNodeHelper;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent;
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent;
 import org.onap.cps.spi.model.DataNode;
 import org.springframework.beans.factory.annotation.Value;
@@ -66,7 +67,7 @@ public class CmSubscriptionDmiOutEventConsumer {
      */
     @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}",
             containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
-    public void consumeSubscriptionEventResponse(
+    public void consumeDmiOutEvent(
             final ConsumerRecord<String, CloudEvent> cmSubscriptionDmiOutConsumerRecord) {
         final CloudEvent cloudEvent = cmSubscriptionDmiOutConsumerRecord.value();
         final String eventType = cmSubscriptionDmiOutConsumerRecord.value().getType();
@@ -90,7 +91,12 @@ public class CmSubscriptionDmiOutEventConsumer {
         if (createOutcomeResponse
                 && notificationFeatureEnabled
                 && hasNoPendingCmHandles(clientId, subscriptionName)) {
-            cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionDmiOutEvent, eventType);
+
+            final CmSubscriptionEvent cmSubscriptionEvent = new CmSubscriptionEvent();
+            cmSubscriptionEvent.setClientId(cmSubscriptionDmiOutEvent.getData().getClientId());
+            cmSubscriptionEvent.setSubscriptionName(cmSubscriptionDmiOutEvent.getData().getSubscriptionName());
+
+            cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent, eventType);
             forwardedSubscriptionEventCache.remove(subscriptionEventId);
         }
     }
@@ -26,43 +26,43 @@ import java.util.stream.Collectors;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Named;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.SubscriptionStatus;
+import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus;
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent;
+import org.onap.cps.ncmp.api.models.CmSubscriptionStatus;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.AdditionalInfo;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.AdditionalInfoDetail;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent;
 import org.onap.cps.spi.exceptions.DataValidationException;
 
 @Mapper(componentModel = "spring")
-public interface CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper {
+public interface CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper {
 
-    @Mapping(source = "data.subscriptionStatus", target = "data.additionalInfo",
-            qualifiedByName = "mapListOfSubscriptionStatusToAdditionalInfo")
-    CmSubscriptionNcmpOutEvent toCmSubscriptionNcmpOutEvent(CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent);
+    @Mapping(source = "cmSubscriptionStatus", target = "data.additionalInfo",
+            qualifiedByName = "mapCmSubscriptionStatusToAdditionalInfo")
+    CmSubscriptionNcmpOutEvent toCmSubscriptionNcmpOutEvent(CmSubscriptionEvent cmSubscriptionEvent);
 
     /**
      * Maps list of SubscriptionStatus to an AdditionalInfo.
      *
-     * @param subscriptionStatusList containing details
+     * @param cmSubscriptionStatusList containing details
      * @return an AdditionalInfo
      */
-    @Named("mapListOfSubscriptionStatusToAdditionalInfo")
-    default AdditionalInfo mapListOfSubscriptionStatusToAdditionalInfo(
-            final List<SubscriptionStatus> subscriptionStatusList) {
-        if (subscriptionStatusList == null || subscriptionStatusList.isEmpty()) {
-            throw new DataValidationException("Invalid subscriptionStatusList",
-                    "SubscriptionStatus list cannot be null or empty");
+    @Named("mapCmSubscriptionStatusToAdditionalInfo")
+    default AdditionalInfo mapCmSubscriptionStatusToAdditionalInfo(
+            final List<CmSubscriptionStatus> cmSubscriptionStatusList) {
+        if (cmSubscriptionStatusList == null || cmSubscriptionStatusList.isEmpty()) {
+            throw new DataValidationException("Invalid cmSubscriptionStatusList",
+                    "CmSubscriptionStatus list cannot be null or empty");
         }
 
-        final Map<String, List<SubscriptionStatus>> rejectedSubscriptionsPerDetails = getSubscriptionsPerDetails(
-                subscriptionStatusList, SubscriptionStatus.Status.REJECTED);
+        final Map<String, List<CmSubscriptionStatus>> rejectedSubscriptionsPerDetails =
+                getSubscriptionsPerDetails(cmSubscriptionStatusList, SubscriptionStatus.REJECTED);
         final Map<String, List<String>> rejectedCmHandlesPerDetails =
                 getCmHandlesPerDetails(rejectedSubscriptionsPerDetails);
         final List<AdditionalInfoDetail> rejectedCmHandles = getAdditionalInfoDetailList(rejectedCmHandlesPerDetails);
 
-
-        final Map<String, List<SubscriptionStatus>> pendingSubscriptionsPerDetails = getSubscriptionsPerDetails(
-                subscriptionStatusList, SubscriptionStatus.Status.PENDING);
+        final Map<String, List<CmSubscriptionStatus>> pendingSubscriptionsPerDetails =
+                getSubscriptionsPerDetails(cmSubscriptionStatusList, SubscriptionStatus.PENDING);
         final Map<String, List<String>> pendingCmHandlesPerDetails =
                 getCmHandlesPerDetails(pendingSubscriptionsPerDetails);
         final List<AdditionalInfoDetail> pendingCmHandles = getAdditionalInfoDetailList(pendingCmHandlesPerDetails);
@@ -74,20 +74,20 @@ public interface CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper {
         return additionalInfo;
     }
 
-    private static Map<String, List<SubscriptionStatus>> getSubscriptionsPerDetails(
-            final List<SubscriptionStatus> subscriptionStatusList, final SubscriptionStatus.Status status) {
-        return subscriptionStatusList.stream()
+    private static Map<String, List<CmSubscriptionStatus>> getSubscriptionsPerDetails(
+            final List<CmSubscriptionStatus> cmSubscriptionStatusList, final SubscriptionStatus status) {
+        return cmSubscriptionStatusList.stream()
                 .filter(subscriptionStatus -> subscriptionStatus.getStatus() == status)
-                .collect(Collectors.groupingBy(SubscriptionStatus::getDetails));
+                .collect(Collectors.groupingBy(CmSubscriptionStatus::getDetails));
     }
 
     private static Map<String, List<String>> getCmHandlesPerDetails(
-            final Map<String, List<SubscriptionStatus>> subscriptionsPerDetails) {
-        return subscriptionsPerDetails.entrySet().stream()
+            final Map<String, List<CmSubscriptionStatus>> cmSubscriptionsPerDetails) {
+        return cmSubscriptionsPerDetails.entrySet().stream()
                 .collect(Collectors.toMap(
                         Map.Entry::getKey,
                         entry -> entry.getValue().stream()
-                                .map(SubscriptionStatus::getId)
+                                .map(CmSubscriptionStatus::getId)
                                 .collect(Collectors.toList())
                 ));
     }
index 4a17495..ea2d17d 100644 (file)
@@ -43,9 +43,8 @@ import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent;
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.Data;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmHandle;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent;
 import org.springframework.beans.factory.annotation.Value;
@@ -97,12 +96,11 @@ public class CmSubscriptionNcmpInEventForwarder {
     private void findDmisAndRespond(final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent, final String eventType,
             final List<String> cmHandleTargetsAsStrings,
             final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName) {
-        final CmSubscriptionDmiOutEvent emptyCmSubscriptionDmiOutEvent =
-                new CmSubscriptionDmiOutEvent().withData(new Data());
-        emptyCmSubscriptionDmiOutEvent.getData()
-                .setSubscriptionName(cmSubscriptionNcmpInEvent.getData().getSubscription().getName());
-        emptyCmSubscriptionDmiOutEvent.getData()
-                .setClientId(cmSubscriptionNcmpInEvent.getData().getSubscription().getClientID());
+
+        final CmSubscriptionEvent cmSubscriptionEvent = new CmSubscriptionEvent();
+        cmSubscriptionEvent.setSubscriptionName(cmSubscriptionNcmpInEvent.getData().getSubscription().getName());
+        cmSubscriptionEvent.setClientId(cmSubscriptionNcmpInEvent.getData().getSubscription().getClientID());
+
         final List<String> cmHandlesThatExistsInDb =
                 dmiPropertiesPerCmHandleIdPerServiceName.entrySet().stream().map(Map.Entry::getValue).map(Map::keySet)
                         .flatMap(Set::stream).collect(Collectors.toList());
@@ -117,10 +115,10 @@ public class CmSubscriptionNcmpInEventForwarder {
                     targetCmHandlesDoesNotExistInDb);
         }
         if (dmisToRespond.isEmpty()) {
-            cmSubscriptionNcmpOutEventPublisher.sendResponse(emptyCmSubscriptionDmiOutEvent,
+            cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent,
                     "subscriptionCreatedStatus");
         } else {
-            startResponseTimeout(emptyCmSubscriptionDmiOutEvent, dmisToRespond);
+            startResponseTimeout(cmSubscriptionEvent, dmisToRespond);
             final CmSubscriptionDmiInEvent cmSubscriptionDmiInEvent =
                     cmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper.toCmSubscriptionDmiInEvent(
                             cmSubscriptionNcmpInEvent);
@@ -128,17 +126,17 @@ public class CmSubscriptionNcmpInEventForwarder {
         }
     }
 
-    private void startResponseTimeout(final CmSubscriptionDmiOutEvent emptyCmSubscriptionDmiOutEvent,
+    private void startResponseTimeout(final CmSubscriptionEvent cmSubscriptionEvent,
                                       final Set<String> dmisToRespond) {
-        final String subscriptionClientId = emptyCmSubscriptionDmiOutEvent.getData().getClientId();
-        final String subscriptionName = emptyCmSubscriptionDmiOutEvent.getData().getSubscriptionName();
+        final String subscriptionClientId = cmSubscriptionEvent.getClientId();
+        final String subscriptionName = cmSubscriptionEvent.getSubscriptionName();
         final String subscriptionEventId = subscriptionClientId + subscriptionName;
 
         forwardedSubscriptionEventCache.put(subscriptionEventId, dmisToRespond,
                 ForwardedSubscriptionEventCacheConfig.SUBSCRIPTION_FORWARD_STARTED_TTL_SECS, TimeUnit.SECONDS);
         final ResponseTimeoutTask responseTimeoutTask =
             new ResponseTimeoutTask(forwardedSubscriptionEventCache, cmSubscriptionNcmpOutEventPublisher,
-                    emptyCmSubscriptionDmiOutEvent);
+                    cmSubscriptionEvent);
 
         executorService.schedule(responseTimeoutTask, dmiResponseTimeoutInMs, TimeUnit.MILLISECONDS);
     }
index 38cc724..473538c 100644 (file)
@@ -32,7 +32,8 @@ import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionPersistence;
 import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus;
 import org.onap.cps.ncmp.api.impl.utils.DataNodeHelper;
 import org.onap.cps.ncmp.api.impl.utils.SubscriptionOutcomeCloudMapper;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent;
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent;
+import org.onap.cps.ncmp.api.models.CmSubscriptionStatus;
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
@@ -46,8 +47,8 @@ public class CmSubscriptionNcmpOutEventPublisher {
 
     private final EventsPublisher<CloudEvent> outcomeEventsPublisher;
 
-    private final CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper
-            cmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper;
+    private final CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper
+            cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper;
 
     private final SubscriptionOutcomeCloudMapper subscriptionOutcomeCloudMapper;
 
@@ -57,54 +58,48 @@ public class CmSubscriptionNcmpOutEventPublisher {
     /**
      * This is for construction of outcome message to be published for client apps.
      *
-     * @param cmSubscriptionDmiOutEvent event produced by Dmi Plugin
+     * @param cmSubscriptionEvent event produced by Dmi Plugin
      */
-    public void sendResponse(final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent, final String eventKey) {
+    public void sendResponse(final CmSubscriptionEvent cmSubscriptionEvent, final String eventType) {
         final CmSubscriptionNcmpOutEvent cmSubscriptionNcmpOutEvent =
-                formCmSubscriptionNcmpOutEvent(cmSubscriptionDmiOutEvent);
-        final String subscriptionClientId = cmSubscriptionDmiOutEvent.getData().getClientId();
-        final String subscriptionName = cmSubscriptionDmiOutEvent.getData().getSubscriptionName();
+                formCmSubscriptionNcmpOutEvent(cmSubscriptionEvent);
+        final String subscriptionClientId = cmSubscriptionEvent.getClientId();
+        final String subscriptionName = cmSubscriptionEvent.getSubscriptionName();
         final String subscriptionEventId = subscriptionClientId + subscriptionName;
         final CloudEvent subscriptionOutcomeCloudEvent =
                 subscriptionOutcomeCloudMapper.toCloudEvent(cmSubscriptionNcmpOutEvent,
-                subscriptionEventId, eventKey);
+                subscriptionEventId, eventType);
         outcomeEventsPublisher.publishCloudEvent(subscriptionOutcomeEventTopic,
                 subscriptionEventId, subscriptionOutcomeCloudEvent);
     }
 
     private CmSubscriptionNcmpOutEvent formCmSubscriptionNcmpOutEvent(
-            final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent) {
+            final CmSubscriptionEvent cmSubscriptionEvent) {
         final Map<String, Map<String, String>> cmHandleIdToStatusAndDetailsAsMap =
                 DataNodeHelper.cmHandleIdToStatusAndDetailsAsMapFromDataNode(
                         subscriptionPersistence.getCmHandlesForSubscriptionEvent(
-                                cmSubscriptionDmiOutEvent.getData().getClientId(),
-                                cmSubscriptionDmiOutEvent.getData().getSubscriptionName()));
-        final List<org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.SubscriptionStatus>
-                subscriptionStatusList =
+                                cmSubscriptionEvent.getClientId(),
+                                cmSubscriptionEvent.getSubscriptionName()));
+        final List<CmSubscriptionStatus> cmSubscriptionStatusList =
                 mapCmHandleIdStatusDetailsMapToSubscriptionStatusList(cmHandleIdToStatusAndDetailsAsMap);
-        cmSubscriptionDmiOutEvent.getData().setSubscriptionStatus(subscriptionStatusList);
-        return fromDmiOutEvent(cmSubscriptionDmiOutEvent,
+        cmSubscriptionEvent.setCmSubscriptionStatus(cmSubscriptionStatusList);
+        return fromCmSubscriptionEvent(cmSubscriptionEvent,
                 decideOnNcmpEventResponseCodeForSubscription(cmHandleIdToStatusAndDetailsAsMap));
     }
 
-    private static List<org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.SubscriptionStatus>
-        mapCmHandleIdStatusDetailsMapToSubscriptionStatusList(
+    private static List<CmSubscriptionStatus> mapCmHandleIdStatusDetailsMapToSubscriptionStatusList(
             final Map<String, Map<String, String>> cmHandleIdToStatusAndDetailsAsMap) {
         return cmHandleIdToStatusAndDetailsAsMap.entrySet()
                 .stream().map(entryset -> {
-                    final org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.SubscriptionStatus
-                            subscriptionStatus = new org.onap.cps.ncmp.events.cmsubscription1_0_0
-                            .dmi_to_ncmp.SubscriptionStatus();
+                    final CmSubscriptionStatus cmSubscriptionStatus = new CmSubscriptionStatus();
                     final String cmHandleId = entryset.getKey();
                     final Map<String, String> statusAndDetailsMap = entryset.getValue();
                     final String status = statusAndDetailsMap.get("status");
                     final String details = statusAndDetailsMap.get("details");
-                    subscriptionStatus.setId(cmHandleId);
-                    subscriptionStatus.setStatus(
-                            org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp
-                                    .SubscriptionStatus.Status.fromValue(status));
-                    subscriptionStatus.setDetails(details);
-                    return subscriptionStatus;
+                    cmSubscriptionStatus.setId(cmHandleId);
+                    cmSubscriptionStatus.setStatus(SubscriptionStatus.fromString(status));
+                    cmSubscriptionStatus.setDetails(details);
+                    return cmSubscriptionStatus;
                 }).collect(Collectors.toList());
     }
 
@@ -138,13 +133,13 @@ public class CmSubscriptionNcmpOutEventPublisher {
                 .allMatch(entryset -> entryset.containsValue(subscriptionStatus.toString()));
     }
 
-    private CmSubscriptionNcmpOutEvent fromDmiOutEvent(
-            final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent,
+    private CmSubscriptionNcmpOutEvent fromCmSubscriptionEvent(
+            final CmSubscriptionEvent cmSubscriptionEvent,
             final NcmpEventResponseCode ncmpEventResponseCode) {
 
         final CmSubscriptionNcmpOutEvent cmSubscriptionNcmpOutEvent =
-                cmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper.toCmSubscriptionNcmpOutEvent(
-                        cmSubscriptionDmiOutEvent);
+                cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.toCmSubscriptionNcmpOutEvent(
+                        cmSubscriptionEvent);
         cmSubscriptionNcmpOutEvent.getData().setStatusCode(Integer.parseInt(ncmpEventResponseCode.getStatusCode()));
         cmSubscriptionNcmpOutEvent.getData().setStatusMessage(ncmpEventResponseCode.getStatusMessage());
 
index 7f8cbf6..78b000d 100644 (file)
@@ -24,7 +24,7 @@ import com.hazelcast.map.IMap;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent;
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -32,7 +32,7 @@ public class ResponseTimeoutTask implements Runnable {
 
     private final IMap<String, Set<String>> forwardedSubscriptionEventCache;
     private final CmSubscriptionNcmpOutEventPublisher cmSubscriptionNcmpOutEventPublisher;
-    private final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent;
+    private final CmSubscriptionEvent cmSubscriptionEvent;
 
     @Override
     public void run() {
@@ -40,11 +40,11 @@ public class ResponseTimeoutTask implements Runnable {
     }
 
     private void generateTimeoutResponse() {
-        final String subscriptionClientId = cmSubscriptionDmiOutEvent.getData().getClientId();
-        final String subscriptionName = cmSubscriptionDmiOutEvent.getData().getSubscriptionName();
+        final String subscriptionClientId = cmSubscriptionEvent.getClientId();
+        final String subscriptionName = cmSubscriptionEvent.getSubscriptionName();
         final String subscriptionEventId = subscriptionClientId + subscriptionName;
         if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) {
-            cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionDmiOutEvent,
+            cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent,
                     "subscriptionCreatedStatus");
             forwardedSubscriptionEventCache.remove(subscriptionEventId);
         }
index 02de985..ba6f891 100644 (file)
@@ -261,22 +261,22 @@ public class DmiDataOperations extends DmiOperations {
             final String topicName = dataOperationResourceUrlParameters.get("topic").get(0);
             final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0);
 
-            final MultiValueMap<String, Map<NcmpEventResponseCode, List<String>>>
-                    cmHandleIdsPerResponseCodesPerOperationId = new LinkedMultiValueMap<>();
+            final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode, List<String>>>
+                    cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
 
             dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> {
                 final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream()
                         .map(CmHandle::getId).collect(Collectors.toList());
                 if (throwable.getCause() instanceof HttpClientRequestException) {
-                    cmHandleIdsPerResponseCodesPerOperationId.add(dmiDataOperationRequestBody.getOperationId(),
+                    cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
                             Map.of(NcmpEventResponseCode.UNABLE_TO_READ_RESOURCE_DATA, cmHandleIds));
                 } else {
-                    cmHandleIdsPerResponseCodesPerOperationId.add(dmiDataOperationRequestBody.getOperationId(),
+                    cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
                             Map.of(NcmpEventResponseCode.DMI_SERVICE_NOT_RESPONDING, cmHandleIds));
                 }
             });
             ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId,
-                    cmHandleIdsPerResponseCodesPerOperationId);
+                    cmHandleIdsPerResponseCodesPerOperation);
         }
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java
new file mode 100644 (file)
index 0000000..458c1b8
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  ============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.ncmp.api.impl.trustlevel;
+
+import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent;
+
+import com.hazelcast.collection.ISet;
+import io.cloudevents.CloudEvent;
+import io.cloudevents.kafka.impl.KafkaHeaders;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DeviceHeartbeatConsumer {
+
+    private final ISet<String> untrustworthyCmHandlesSet;
+
+    /**
+     * Listening the device heartbeats.
+     *
+     * @param deviceHeartbeatConsumerRecord Device Heartbeat record.
+     */
+    @KafkaListener(topics = "${app.dmi.device-heartbeat.topic}",
+            containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
+    public void heartbeatListener(final ConsumerRecord<String, CloudEvent> deviceHeartbeatConsumerRecord) {
+
+        final String cmHandleId = KafkaHeaders.getParsedKafkaHeader(deviceHeartbeatConsumerRecord.headers(), "ce_id");
+
+        final DeviceTrustLevel deviceTrustLevel =
+                toTargetEvent(deviceHeartbeatConsumerRecord.value(), DeviceTrustLevel.class);
+
+        if (deviceTrustLevel == null || deviceTrustLevel.getTrustLevel() == null) {
+            log.warn("No or Invalid trust level defined");
+            return;
+        }
+
+        if (deviceTrustLevel.getTrustLevel().equals(TrustLevel.NONE)) {
+            untrustworthyCmHandlesSet.add(cmHandleId);
+            log.debug("Added cmHandleId to untrustworthy set : {}", cmHandleId);
+        } else if (deviceTrustLevel.getTrustLevel().equals(TrustLevel.COMPLETE) && untrustworthyCmHandlesSet.contains(
+                cmHandleId)) {
+            untrustworthyCmHandlesSet.remove(cmHandleId);
+            log.debug("Removed cmHandleId from untrustworthy set : {}", cmHandleId);
+        }
+    }
+
+}
+
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java
new file mode 100644 (file)
index 0000000..2ed4e45
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  ============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.ncmp.api.impl.trustlevel;
+
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@AllArgsConstructor
+@Data
+@NoArgsConstructor
+class DeviceTrustLevel implements Serializable {
+
+    private static final long serialVersionUID = -1705715024067165212L;
+
+    private TrustLevel trustLevel;
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java
new file mode 100644 (file)
index 0000000..f4254bb
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *  ============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.ncmp.api.impl.trustlevel;
+
+public enum TrustLevel {
+    NONE, COMPLETE;
+}
\ No newline at end of file
index 2d9a51b..65cda94 100644 (file)
@@ -30,6 +30,7 @@ import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.NcmpEventResponseCode;
 import org.onap.cps.ncmp.api.impl.events.NcmpCloudEventBuilder;
+import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation;
 import org.onap.cps.ncmp.events.async1_0_0.Data;
 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
 import org.onap.cps.ncmp.events.async1_0_0.Response;
@@ -44,49 +45,48 @@ public class DataOperationEventCreator {
      *
      * @param clientTopic                              topic the client wants to use for responses
      * @param requestId                                unique identifier per request
-     * @param cmHandleIdsPerResponseCodesPerOperationId map of cm handles per operation response per response code
+     * @param cmHandleIdsPerResponseCodesPerOperation map of cm handles per operation response per response code
      * @return Cloud Event
      */
     public static CloudEvent createDataOperationEvent(final String clientTopic,
                                                       final String requestId,
-                                                      final MultiValueMap<String,
+                                                      final MultiValueMap<DmiDataOperation,
                                                               Map<NcmpEventResponseCode, List<String>>>
-                                                              cmHandleIdsPerResponseCodesPerOperationId) {
+                                                              cmHandleIdsPerResponseCodesPerOperation) {
         final DataOperationEvent dataOperationEvent = new DataOperationEvent();
-        final Data data = createPayloadFromDataOperationResponses(cmHandleIdsPerResponseCodesPerOperationId);
+        final Data data = createPayloadFromDataOperationResponses(cmHandleIdsPerResponseCodesPerOperation);
         dataOperationEvent.setData(data);
         final Map<String, String> extensions = createDataOperationExtensions(requestId, clientTopic);
         return NcmpCloudEventBuilder.builder().type(DataOperationEvent.class.getName())
                 .event(dataOperationEvent).extensions(extensions).setCloudEvent().build();
     }
 
-    private static Data createPayloadFromDataOperationResponses(final MultiValueMap<String, Map<NcmpEventResponseCode,
-            List<String>>> cmHandleIdsPerOperationIdPerResponseCode) {
+    private static Data createPayloadFromDataOperationResponses(final MultiValueMap<DmiDataOperation,
+            Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperation) {
         final Data data = new Data();
         final List<org.onap.cps.ncmp.events.async1_0_0.Response> responses = new ArrayList<>();
-        cmHandleIdsPerOperationIdPerResponseCode.entrySet().forEach(cmHandleIdsPerOperationIdPerResponseCodeEntries ->
-                cmHandleIdsPerOperationIdPerResponseCodeEntries.getValue().forEach(cmHandleIdsPerResponseCodeEntries ->
+        cmHandleIdsPerResponseCodesPerOperation.forEach((dmiDataOperation, cmHandleIdsPerResponseCodes) ->
+                cmHandleIdsPerResponseCodes.forEach(cmHandleIdsPerResponseCodeEntries ->
                         responses.addAll(createResponseFromDataOperationResponses(
-                                cmHandleIdsPerOperationIdPerResponseCodeEntries.getKey(),
-                                cmHandleIdsPerResponseCodeEntries)
-                        )));
+                                dmiDataOperation, cmHandleIdsPerResponseCodeEntries))));
         data.setResponses(responses);
         return data;
     }
 
     private static List<Response> createResponseFromDataOperationResponses(
-            final String operationId,
+            final DmiDataOperation dmiDataOperation,
             final Map<NcmpEventResponseCode, List<String>> cmHandleIdsPerResponseCodeEntries) {
         final List<org.onap.cps.ncmp.events.async1_0_0.Response> responses = new ArrayList<>();
-        cmHandleIdsPerResponseCodeEntries.entrySet()
-                .forEach(cmHandleIdsPerResponseCodeEntry -> {
-                    final Response response = new Response();
-                    response.setOperationId(operationId);
-                    response.setStatusCode(cmHandleIdsPerResponseCodeEntry.getKey().getStatusCode());
-                    response.setStatusMessage(cmHandleIdsPerResponseCodeEntry.getKey().getStatusMessage());
-                    response.setIds(cmHandleIdsPerResponseCodeEntry.getValue());
-                    responses.add(response);
-                });
+        cmHandleIdsPerResponseCodeEntries.forEach((ncmpEventResponseCode, cmHandleIds) -> {
+            final Response response = new Response();
+            response.setOperationId(dmiDataOperation.getOperationId());
+            response.setStatusCode(ncmpEventResponseCode.getStatusCode());
+            response.setStatusMessage(ncmpEventResponseCode.getStatusMessage());
+            response.setIds(cmHandleIds);
+            response.setResourceIdentifier(dmiDataOperation.getResourceIdentifier());
+            response.setOptions(dmiDataOperation.getOptions());
+            responses.add(response);
+        });
         return responses;
     }
 
index d8fb904..c455337 100644 (file)
@@ -68,8 +68,8 @@ public class ResourceDataOperationRequestUtils {
             final Collection<YangModelCmHandle> yangModelCmHandles) {
 
         final Map<String, List<DmiDataOperation>> dmiDataOperationsOutPerDmiServiceName = new HashMap<>();
-        final MultiValueMap<String, Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperationId
-                = new LinkedMultiValueMap<>();
+        final MultiValueMap<DmiDataOperation, Map<NcmpEventResponseCode,
+                List<String>>> cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
         final Set<String> nonReadyCmHandleIdsLookup = filterAndGetNonReadyCmHandleIds(yangModelCmHandles);
 
         final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName =
@@ -100,15 +100,15 @@ public class ResourceDataOperationRequestUtils {
                     }
                 }
             }
-            populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperationId,
-                    dataOperationDefinitionIn.getOperationId(), NcmpEventResponseCode.CM_HANDLES_NOT_FOUND,
-                    nonExistingCmHandleIds);
-            populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperationId,
-                    dataOperationDefinitionIn.getOperationId(), NcmpEventResponseCode.CM_HANDLES_NOT_READY,
-                    nonReadyCmHandleIds);
+            populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation,
+                    DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
+                    NcmpEventResponseCode.CM_HANDLES_NOT_FOUND, nonExistingCmHandleIds);
+            populateCmHandleIdsPerOperationIdPerResponseCode(cmHandleIdsPerResponseCodesPerOperation,
+                    DmiDataOperation.buildDmiDataOperationRequestBodyWithoutCmHandles(dataOperationDefinitionIn),
+                    NcmpEventResponseCode.CM_HANDLES_NOT_READY, nonReadyCmHandleIds);
         }
-        if (!cmHandleIdsPerResponseCodesPerOperationId.isEmpty()) {
-            publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperationId);
+        if (!cmHandleIdsPerResponseCodesPerOperation.isEmpty()) {
+            publishErrorMessageToClientTopic(topicParamInQuery, requestId, cmHandleIdsPerResponseCodesPerOperation);
         }
         return dmiDataOperationsOutPerDmiServiceName;
     }
@@ -118,16 +118,16 @@ public class ResourceDataOperationRequestUtils {
      *
      * @param clientTopic                              client given topic
      * @param requestId                                unique identifier per request
-     * @param cmHandleIdsPerResponseCodesPerOperationId list of cm handle ids per operation id with response code
+     * @param cmHandleIdsPerResponseCodesPerOperation list of cm handle ids per operation with response code
      */
     @Async
     public static void publishErrorMessageToClientTopic(final String clientTopic,
                                                          final String requestId,
-                                                         final MultiValueMap<String,
+                                                         final MultiValueMap<DmiDataOperation,
                                                                  Map<NcmpEventResponseCode, List<String>>>
-                                                                    cmHandleIdsPerResponseCodesPerOperationId) {
+                                                                    cmHandleIdsPerResponseCodesPerOperation) {
         final CloudEvent dataOperationCloudEvent = DataOperationEventCreator.createDataOperationEvent(clientTopic,
-                requestId, cmHandleIdsPerResponseCodesPerOperationId);
+                requestId, cmHandleIdsPerResponseCodesPerOperation);
         final EventsPublisher<CloudEvent> eventsPublisher = CpsApplicationContext.getCpsBean(EventsPublisher.class);
         eventsPublisher.publishCloudEvent(clientTopic, requestId, dataOperationCloudEvent);
     }
@@ -174,14 +174,14 @@ public class ResourceDataOperationRequestUtils {
                         != CmHandleState.READY).map(YangModelCmHandle::getId).collect(Collectors.toSet());
     }
 
-    private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<String,
-            Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperationId,
-                                                                        final String operationId,
+    private static void populateCmHandleIdsPerOperationIdPerResponseCode(final MultiValueMap<DmiDataOperation,
+            Map<NcmpEventResponseCode, List<String>>> cmHandleIdsPerResponseCodesPerOperation,
+                                                                        final DmiDataOperation dmiDataOperation,
                                                                         final NcmpEventResponseCode
                                                                                 ncmpEventResponseCode,
                                                                         final List<String> cmHandleIds) {
         if (!cmHandleIds.isEmpty()) {
-            cmHandleIdsPerResponseCodesPerOperationId.add(operationId, Map.of(ncmpEventResponseCode, cmHandleIds));
+            cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(ncmpEventResponseCode, cmHandleIds));
         }
     }
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionEvent.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionEvent.java
new file mode 100644 (file)
index 0000000..2a60b2a
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  ============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.ncmp.api.models;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class CmSubscriptionEvent {
+
+    @JsonProperty("clientId")
+    @NotNull
+    private String clientId;
+
+    @JsonProperty("subscriptionName")
+    @NotNull
+    private String subscriptionName;
+
+    @JsonProperty("cmSubscriptionStatus")
+    @Valid
+    @NotNull
+    private List<CmSubscriptionStatus> cmSubscriptionStatus = new ArrayList<>();
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionStatus.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionStatus.java
new file mode 100644 (file)
index 0000000..bba5607
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  ============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.ncmp.api.models;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class CmSubscriptionStatus {
+
+    @JsonProperty("id")
+    @NotNull
+    private String id;
+
+    @JsonProperty("status")
+    @NotNull
+    private SubscriptionStatus status;
+
+    @JsonProperty("details")
+    private String details;
+}
index a8a21b2..175ead8 100644 (file)
@@ -46,24 +46,22 @@ class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
     IMap<String, Set<String>> mockForwardedSubscriptionEventCache = Mock(IMap<String, Set<String>>)
     def mockSubscriptionPersistence = Mock(SubscriptionPersistenceImpl)
     def mockSubscriptionEventResponseMapper  = Mock(CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper)
-    def mockSubscriptionEventResponseOutcome = Mock(CmSubscriptionNcmpOutEventPublisher)
+    def mockCmSubscriptionNcmpOutEventPublisher = Mock(CmSubscriptionNcmpOutEventPublisher)
 
     def objectUnderTest = new CmSubscriptionDmiOutEventConsumer(mockForwardedSubscriptionEventCache,
-        mockSubscriptionPersistence, mockSubscriptionEventResponseMapper, mockSubscriptionEventResponseOutcome)
+        mockSubscriptionPersistence, mockSubscriptionEventResponseMapper, mockCmSubscriptionNcmpOutEventPublisher)
 
-    def 'Consume Subscription Event Response where all DMIs have responded'() {
-        given: 'a consumer record including cloud event having subscription response'
-            def consumerRecordWithCloudEventAndSubscriptionResponse = getConsumerRecord()
-        and: 'a subscription response event'
-            def subscriptionResponseEvent = getSubscriptionResponseEvent()
-        and: 'a subscription event response and notifications are enabled'
+    def 'Consume dmi out event where all DMIs have responded'() {
+        given: 'a consumer record including cloud event having dmi out event'
+            def dmiOutConsumerRecord = getDmiOutConsumerRecord()
+        and: 'notifications are enabled'
             objectUnderTest.notificationFeatureEnabled = notificationEnabled
         and: 'subscription model loader is enabled'
             objectUnderTest.subscriptionModelLoaderEnabled = modelLoaderEnabled
         and: 'subscription persistence service returns data node includes no pending cm handle'
             mockSubscriptionPersistence.getCmHandlesForSubscriptionEvent(*_) >> [getDataNode()]
         when: 'the valid event is consumed'
-            objectUnderTest.consumeSubscriptionEventResponse(consumerRecordWithCloudEventAndSubscriptionResponse)
+            objectUnderTest.consumeDmiOutEvent(dmiOutConsumerRecord)
         then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event'
             1 * mockForwardedSubscriptionEventCache.containsKey('SCO-9989752cm-subscription-001') >> true
             1 * mockForwardedSubscriptionEventCache.get('SCO-9989752cm-subscription-001') >> (['some-dmi-name'] as Set)
@@ -76,7 +74,7 @@ class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
         and: 'the subscription event is removed from the map'
             numberOfTimeToRemove * mockForwardedSubscriptionEventCache.remove('SCO-9989752cm-subscription-001')
         and: 'a response outcome has been created'
-            numberOfTimeToResponse * mockSubscriptionEventResponseOutcome.sendResponse(subscriptionResponseEvent, 'subscriptionCreated')
+            numberOfTimeToResponse * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(_, 'subscriptionCreated')
         where: 'the following values are used'
             scenario                                              | modelLoaderEnabled  |   notificationEnabled  ||  numberOfTimeToPersist  ||  numberOfTimeToRemove  || numberOfTimeToResponse
             'Both model loader and notification are enabled'      |    true             |     true               ||   1                     ||      1                 ||       1
@@ -85,13 +83,13 @@ class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
             'Model loader disabled and notification enabled'      |    false            |     true               ||   0                     ||      1                 ||       1
     }
 
-    def 'Consume Subscription Event Response where another DMI has not yet responded'() {
+    def 'Consume dmi out event where another DMI has not yet responded'() {
         given: 'a subscription event response and notifications are enabled'
             objectUnderTest.notificationFeatureEnabled = notificationEnabled
         and: 'subscription model loader is enabled'
             objectUnderTest.subscriptionModelLoaderEnabled = modelLoaderEnabled
         when: 'the valid event is consumed'
-            objectUnderTest.consumeSubscriptionEventResponse(getConsumerRecord())
+            objectUnderTest.consumeDmiOutEvent(getDmiOutConsumerRecord())
         then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event'
             1 * mockForwardedSubscriptionEventCache.containsKey('SCO-9989752cm-subscription-001') >> true
             1 * mockForwardedSubscriptionEventCache.get('SCO-9989752cm-subscription-001') >> (['responded-dmi', 'non-responded-dmi'] as Set)
@@ -105,7 +103,7 @@ class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
         and: 'the subscription event is not removed from the map'
             0 * mockForwardedSubscriptionEventCache.remove(_)
         and: 'a response outcome has not been created'
-            0 * mockSubscriptionEventResponseOutcome.sendResponse(*_)
+            0 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(*_)
         where: 'the following values are used'
             scenario                                              | modelLoaderEnabled  |   notificationEnabled  ||  numberOfTimeToPersist
             'Both model loader and notification are enabled'      |    true             |     true               ||   1
@@ -114,21 +112,21 @@ class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec {
             'Model loader disabled and notification enabled'      |    false            |     true               ||   0
     }
 
-    def getSubscriptionResponseEvent() {
-        def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-        return jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
+    def getDmiOutEvent() {
+        def cmSubscriptionDmiOutEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
+        return jsonObjectMapper.convertJsonString(cmSubscriptionDmiOutEventJsonData, CmSubscriptionDmiOutEvent.class)
     }
 
-    def getCloudEventHavingSubscriptionResponseEvent() {
+    def getCloudEvent() {
         return CloudEventBuilder.v1()
-            .withData(objectMapper.writeValueAsBytes(getSubscriptionResponseEvent()))
+            .withData(objectMapper.writeValueAsBytes(getDmiOutEvent()))
             .withId('some-id')
             .withType('subscriptionCreated')
             .withSource(URI.create('NCMP')).build()
     }
 
-    def getConsumerRecord() {
-        return new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', getCloudEventHavingSubscriptionResponseEvent())
+    def getDmiOutConsumerRecord() {
+        return new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', getCloudEvent())
     }
 
     def getDataNode() {
index 036bedb..b13a2ce 100644 (file)
@@ -39,8 +39,8 @@ class CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec extends Sp
     @Autowired
     JsonObjectMapper jsonObjectMapper
 
-    def 'Map subscription response event to yang model subscription event'() {
-        given: 'a Subscription Response Event'
+    def 'Map dmi out event to yang model subscription event'() {
+        given: 'a dmi out event'
             def jsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
             def testEventToMap = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionDmiOutEvent.class)
         when: 'the event is mapped to a yang model subscription'
@@ -50,11 +50,11 @@ class CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec extends Sp
         and: 'subscription name'
             assert result.subscriptionName == "cm-subscription-001"
         and: 'predicate targets cm handle size as expected'
-            assert result.predicates.targetCmHandles.size() == 4
+            assert result.predicates.targetCmHandles.size() == 2
         and: 'predicate targets cm handle ids as expected'
-            assert result.predicates.targetCmHandles.cmHandleId == ["CMHandle1", "CMHandle2", "CMHandle3", "CMHandle4"]
+            assert result.predicates.targetCmHandles.cmHandleId == ["CMHandle1", "CMHandle2"]
         and: 'the status for these targets is set to expected values'
-            assert result.predicates.targetCmHandles.status == [SubscriptionStatus.REJECTED, SubscriptionStatus.REJECTED, SubscriptionStatus.PENDING, SubscriptionStatus.PENDING]
+            assert result.predicates.targetCmHandles.status == [SubscriptionStatus.REJECTED, SubscriptionStatus.REJECTED]
     }
 
 }
\ No newline at end of file
@@ -22,8 +22,8 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.mapstruct.factory.Mappers
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.SubscriptionStatus
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent
+import org.onap.cps.ncmp.api.models.CmSubscriptionStatus
 import org.onap.cps.ncmp.utils.TestUtils
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.utils.JsonObjectMapper
@@ -33,24 +33,26 @@ import spock.lang.Specification
 
 
 @SpringBootTest(classes = [JsonObjectMapper, ObjectMapper])
-class CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapperSpec extends Specification {
+class CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec extends Specification {
 
-    CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper)
+    CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper)
 
     @Autowired
     JsonObjectMapper jsonObjectMapper
 
-    def 'Map subscription event response to subscription event outcome'() {
-        given: 'a Subscription Response Event'
-            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
-        when: 'the subscription response event is mapped to a subscription event outcome'
-            def result = objectUnderTest.toCmSubscriptionNcmpOutEvent(subscriptionResponseEvent)
-        then: 'the resulting subscription event outcome contains expected pending targets per details grouping'
+    def 'Map cm subscription event to ncmp out event'() {
+        given: 'a cm subscription event'
+            def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionEvent.json')
+            def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class)
+        when: 'cm subscription event is mapped to ncmp out event'
+            def result = objectUnderTest.toCmSubscriptionNcmpOutEvent(cmSubscriptionEvent)
+        then: 'the resulting ncmp out event contains expected pending targets per details grouping'
             def pendingCmHandleTargetsPerDetails = result.getData().getAdditionalInfo().getPending()
-            assert pendingCmHandleTargetsPerDetails.get(0).getDetails() == 'No reply from DMI yet'
-            assert pendingCmHandleTargetsPerDetails.get(0).getTargets() == ['CMHandle3', 'CMHandle4']
-        and: 'the resulting subscription event outcome contains expected rejected targets per details grouping'
+            assert pendingCmHandleTargetsPerDetails.get(0).getDetails() == 'Some other error happened'
+            assert pendingCmHandleTargetsPerDetails.get(0).getTargets() == ['CMHandle4','CMHandle5']
+            assert pendingCmHandleTargetsPerDetails.get(1).getDetails() == 'Some error causes pending'
+            assert pendingCmHandleTargetsPerDetails.get(1).getTargets() == ['CMHandle3']
+        and: 'the resulting ncmp out event contains expected rejected targets per details grouping'
             def rejectedCmHandleTargetsPerDetails = result.getData().getAdditionalInfo().getRejected()
             assert rejectedCmHandleTargetsPerDetails.get(0).getDetails() == 'Some other error message from the DMI'
             assert rejectedCmHandleTargetsPerDetails.get(0).getTargets() == ['CMHandle2']
@@ -58,28 +60,28 @@ class CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapperSpec extends Sp
             assert rejectedCmHandleTargetsPerDetails.get(1).getTargets() == ['CMHandle1']
     }
 
-    def 'Map subscription event response with null of subscription status list to subscription event outcome causes an exception'() {
-        given: 'a Subscription Response Event'
-            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
-        and: 'set subscription status list to null'
-            subscriptionResponseEvent.getData().setSubscriptionStatus(subscriptionStatusList)
-        when: 'the subscription response event is mapped to a subscription event outcome'
-            objectUnderTest.toCmSubscriptionNcmpOutEvent(subscriptionResponseEvent)
+    def 'Map cm subscription event to ncmp out event with the given scenarios causes an exception'() {
+        given: 'a cm subscription event'
+            def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionEvent.json')
+            def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class)
+        and: 'set cm subscription status with given scenarios'
+            cmSubscriptionEvent.setCmSubscriptionStatus(subscriptionStatusList)
+        when: 'cm subscription event is mapped to ncmp out event'
+            objectUnderTest.toCmSubscriptionNcmpOutEvent(cmSubscriptionEvent)
         then: 'a DataValidationException is thrown with an expected exception details'
             def exception = thrown(DataValidationException)
-            exception.details == 'SubscriptionStatus list cannot be null or empty'
+            exception.details == 'CmSubscriptionStatus list cannot be null or empty'
         where: 'the following values are used'
             scenario                            ||     subscriptionStatusList
-            'A null subscription status list'   ||      null
-            'An empty subscription status list' ||      new ArrayList<SubscriptionStatus>()
+            'A null subscription status list'   ||     null
+            'An empty subscription status list' ||     new ArrayList<CmSubscriptionStatus>()
     }
 
-    def 'Map subscription event response with subscription status list to subscription event outcome without any exception'() {
-        given: 'a Subscription Response Event'
-            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
-        when: 'the subscription response event is mapped to a subscription event outcome'
+    def 'Map cm subscription event to ncmp out event without any exception'() {
+        given: 'a cm subscription Event'
+            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionEvent.json')
+            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionEvent.class)
+        when: 'cm subscription event is mapped to ncmp out event'
             objectUnderTest.toCmSubscriptionNcmpOutEvent(subscriptionResponseEvent)
         then: 'no exception thrown'
             noExceptionThrown()
index a13fe53..fd1a83e 100644 (file)
@@ -32,6 +32,7 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent.TargetCmHandle
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.Data
@@ -74,24 +75,25 @@ class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec {
     ObjectMapper objectMapper
 
     def 'Forward valid CM create subscription and simulate timeout'() {
-        given: 'an event'
-            def jsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
-            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class)
+        given: 'a ncmp in event'
+            def ncmpInEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
+            def ncmpInEventJson = jsonObjectMapper.convertJsonString(ncmpInEventJsonData, CmSubscriptionNcmpInEvent.class)
         and: 'the InventoryPersistence returns private properties for the supplied CM Handles'
             1 * mockInventoryPersistence.getYangModelCmHandles(["CMHandle1", "CMHandle2", "CMHandle3"]) >> [
-                createYangModelCmHandleWithDmiProperty(1, 1, "shape", "circle"),
-                createYangModelCmHandleWithDmiProperty(2, 1, "shape", "square")
+                createYangModelCmHandleWithDmiProperty(1, 1,"shape","circle"),
+                createYangModelCmHandleWithDmiProperty(2, 1,"shape","square"),
+                createYangModelCmHandleWithDmiProperty(3, 2,"shape","triangle")
             ]
         and: 'the thread creation delay is reduced to 2 seconds for testing'
             objectUnderTest.dmiResponseTimeoutInMs = 2000
         and: 'a Blocking Variable is used for the Asynchronous call with a timeout of 5 seconds'
             def block = new BlockingVariable<Object>(5)
         when: 'the valid event is forwarded'
-            objectUnderTest.forwardCreateSubscriptionEvent(testEventSent, 'subscriptionCreated')
+            objectUnderTest.forwardCreateSubscriptionEvent(ncmpInEventJson, 'subscriptionCreated')
         then: 'An asynchronous call is made to the blocking variable'
             block.get()
         then: 'the event is added to the forwarded subscription event cache'
-            1 * mockForwardedSubscriptionEventCache.put("SCO-9989752cm-subscription-001", ["DMIName1"] as Set, 600, TimeUnit.SECONDS)
+            1 * mockForwardedSubscriptionEventCache.put("SCO-9989752cm-subscription-001", ["DMIName1","DMIName2"] as Set, 600, TimeUnit.SECONDS)
         and: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future'
             1 * mockSubscriptionEventPublisher.publishCloudEvent("ncmp-dmi-cm-avc-subscription-DMIName1", "SCO-9989752-cm-subscription-001-DMIName1",
                 cloudEvent -> {
@@ -101,6 +103,13 @@ class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec {
                     targets == [cmHandle2, cmHandle1]
                 }
             )
+            1 * mockSubscriptionEventPublisher.publishCloudEvent("ncmp-dmi-cm-avc-subscription-DMIName2", "SCO-9989752-cm-subscription-001-DMIName2",
+                cloudEvent -> {
+                    def targets = toTargetEvent(cloudEvent, CmSubscriptionDmiInEvent.class).getData().getPredicates().getTargets()
+                    def cmHandle3 = createCmHandle('CMHandle3', ['shape':'triangle'] as Map)
+                    targets == [cmHandle3]
+                }
+            )
         and: 'a separate thread has been created where the map is polled'
             1 * mockForwardedSubscriptionEventCache.containsKey("SCO-9989752cm-subscription-001") >> true
             1 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(*_)
@@ -109,13 +118,13 @@ class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec {
     }
 
     def 'Forward CM create subscription where target CM Handles are #scenario'() {
-        given: 'an event'
-            def jsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
-            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class)
+        given: 'a ncmp in event'
+            def ncmpInEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
+            def ncmpInEventJson = jsonObjectMapper.convertJsonString(ncmpInEventJsonData, CmSubscriptionNcmpInEvent.class)
         and: 'the target CMHandles are set to #scenario'
-            testEventSent.getData().getPredicates().setTargets(invalidTargets)
+            ncmpInEventJson.getData().getPredicates().setTargets(invalidTargets)
         when: 'the event is forwarded'
-            objectUnderTest.forwardCreateSubscriptionEvent(testEventSent, 'some-event-type')
+            objectUnderTest.forwardCreateSubscriptionEvent(ncmpInEventJson, 'some-event-type')
         then: 'an operation not supported exception is thrown'
             thrown(UnsupportedOperationException)
         where:
@@ -126,28 +135,24 @@ class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec {
     }
 
     def 'Forward valid CM create subscription where targets are not associated to any existing CMHandles'() {
-        given: 'an event'
-            def jsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
-            def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class)
-        and: 'a subscription event response'
-            def emptySubscriptionEventResponse = new CmSubscriptionDmiOutEvent().withData(new Data());
-            emptySubscriptionEventResponse.getData().setSubscriptionName('cm-subscription-001');
-            emptySubscriptionEventResponse.getData().setClientId('SCO-9989752');
-        and: 'the cm handles will be rejected'
+        given: 'a ncmp in event'
+            def ncmpInEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpInEvent.json')
+            def ncmpInEventJson = jsonObjectMapper.convertJsonString(ncmpInEventJsonData, CmSubscriptionNcmpInEvent.class)
+        and: 'the InventoryPersistence returns no private properties for the supplied CM Handles'
+            1 * mockInventoryPersistence.getYangModelCmHandles(["CMHandle1", "CMHandle2", "CMHandle3"]) >> []
+        and: 'some rejected cm handles'
             def rejectedCmHandles = [new TargetCmHandle('CMHandle1', SubscriptionStatus.REJECTED, 'Cm handle does not exist'),
                                      new TargetCmHandle('CMHandle2', SubscriptionStatus.REJECTED, 'Cm handle does not exist'),
                                      new TargetCmHandle('CMHandle3', SubscriptionStatus.REJECTED, 'Cm handle does not exist')]
         and: 'a yang model subscription event will be saved into the db with rejected cm handles'
-            def yangModelSubscriptionEvent = cmSubscriptionNcmpInEventMapper.toYangModelSubscriptionEvent(testEventSent)
+            def yangModelSubscriptionEvent = cmSubscriptionNcmpInEventMapper.toYangModelSubscriptionEvent(ncmpInEventJson)
             yangModelSubscriptionEvent.getPredicates().setTargetCmHandles(rejectedCmHandles)
-        and: 'the InventoryPersistence returns no private properties for the supplied CM Handles'
-            1 * mockInventoryPersistence.getYangModelCmHandles(["CMHandle1", "CMHandle2", "CMHandle3"]) >> []
         and: 'the thread creation delay is reduced to 2 seconds for testing'
             objectUnderTest.dmiResponseTimeoutInMs = 2000
         and: 'a Blocking Variable is used for the Asynchronous call with a timeout of 5 seconds'
             def block = new BlockingVariable<Object>(5)
         when: 'the valid event is forwarded'
-            objectUnderTest.forwardCreateSubscriptionEvent(testEventSent, 'subscriptionCreatedStatus')
+            objectUnderTest.forwardCreateSubscriptionEvent(ncmpInEventJson, 'subscriptionCreatedStatus')
         then: 'the event is not added to the forwarded subscription event cache'
             0 * mockForwardedSubscriptionEventCache.put("SCO-9989752cm-subscription-001", ["DMIName1", "DMIName2"] as Set)
         and: 'the event is not being forwarded with the CMHandle private properties and does not provides a valid listenable future'
@@ -170,11 +175,11 @@ class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec {
             0 * mockForwardedSubscriptionEventCache.containsKey("SCO-9989752cm-subscription-001") >> true
             0 * mockForwardedSubscriptionEventCache.get(_)
         and: 'the subscription id is removed from the event cache map returning the asynchronous blocking variable'
-            0 * mockForwardedSubscriptionEventCache.remove("SCO-9989752cm-subscription-001") >> { block.set(_) }
-        and: 'the persistence service save target cm handles of the yang model subscription event as rejected '
+            0 * mockForwardedSubscriptionEventCache.remove("SCO-9989752cm-subscription-001") >> {block.set(_)}
+        and: 'the persistence service save target cm handles of the yang model subscription event as rejected'
             1 * mockSubscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent)
         and: 'subscription outcome has been sent'
-            1 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(emptySubscriptionEventResponse, 'subscriptionCreatedStatus')
+            1 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(_, 'subscriptionCreatedStatus')
     }
 
     static def createYangModelCmHandleWithDmiProperty(id, dmiId, propertyName, propertyValue) {
index 07e2e3f..cc14195 100644 (file)
@@ -29,7 +29,7 @@ import org.onap.cps.ncmp.api.impl.events.EventsPublisher
 import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionPersistence
 import org.onap.cps.ncmp.api.impl.utils.DataNodeBaseSpec
 import org.onap.cps.ncmp.api.impl.utils.SubscriptionOutcomeCloudMapper
-import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent
+import org.onap.cps.ncmp.api.models.CmSubscriptionEvent
 import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent
 import org.onap.cps.ncmp.utils.TestUtils
 import org.onap.cps.utils.JsonObjectMapper
@@ -37,7 +37,7 @@ import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 
-@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper, CmSubscriptionNcmpOutEventPublisher])
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper, CmSubscriptionNcmpOutEventPublisher])
 class CmSubscriptionNcmpOutEventPublisherSpec extends DataNodeBaseSpec {
 
     @Autowired
@@ -48,7 +48,7 @@ class CmSubscriptionNcmpOutEventPublisherSpec extends DataNodeBaseSpec {
     @SpringBean
     EventsPublisher<CloudEvent> mockCmSubscriptionNcmpOutEventPublisher = Mock(EventsPublisher<CloudEvent>)
     @SpringBean
-    CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper cmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper = Mappers.getMapper(CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper)
+    CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper = Mappers.getMapper(CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper)
     @SpringBean
     SubscriptionOutcomeCloudMapper subscriptionOutcomeCloudMapper = new SubscriptionOutcomeCloudMapper(new ObjectMapper())
 
@@ -59,17 +59,17 @@ class CmSubscriptionNcmpOutEventPublisherSpec extends DataNodeBaseSpec {
     ObjectMapper objectMapper
 
     def 'Send response to the client apps successfully'() {
-        given: 'a subscription response event'
-            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
-        and: 'a subscription outcome event'
-            def subscriptionOutcomeJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpOutEvent2.json')
-            def subscriptionOutcomeEvent = jsonObjectMapper.convertJsonString(subscriptionOutcomeJsonData, CmSubscriptionNcmpOutEvent.class)
+        given: 'a cm subscription event'
+            def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionEvent.json')
+            def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class)
+        and: 'a ncmp out event'
+            def ncmpOutEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpOutEvent2.json')
+            def ncmpOutEvent = jsonObjectMapper.convertJsonString(ncmpOutEventJsonData, CmSubscriptionNcmpOutEvent.class)
         and: 'a random id for the cloud event'
             SubscriptionOutcomeCloudMapper.randomId = 'some-id'
         and: 'a cloud event containing the outcome event'
             def testCloudEventSent = CloudEventBuilder.v1()
-                .withData(objectMapper.writeValueAsBytes(subscriptionOutcomeEvent))
+                .withData(objectMapper.writeValueAsBytes(ncmpOutEvent))
                 .withId('some-id')
                 .withType('subscriptionCreatedStatus')
                 .withDataSchema(URI.create('urn:cps:' + 'org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent' + ':1.0.0'))
@@ -78,25 +78,25 @@ class CmSubscriptionNcmpOutEventPublisherSpec extends DataNodeBaseSpec {
         and: 'the persistence service return a data node that includes pending cm handles that makes it partial success'
             mockSubscriptionPersistence.getCmHandlesForSubscriptionEvent(*_) >> [dataNode4]
         when: 'the response is being sent'
-            objectUnderTest.sendResponse(subscriptionResponseEvent, 'subscriptionCreatedStatus')
+            objectUnderTest.sendResponse(cmSubscriptionEvent, 'subscriptionCreatedStatus')
         then: 'the publisher publish the cloud event with itself and expected parameters'
             1 * mockCmSubscriptionNcmpOutEventPublisher.publishCloudEvent('subscription-response', 'SCO-9989752cm-subscription-001', testCloudEventSent)
     }
 
-    def 'Create subscription outcome message as expected'() {
-        given: 'a subscription response event'
-            def subscriptionResponseJsonData = TestUtils.getResourceFileContent('cmSubscriptionDmiOutEvent.json')
-            def subscriptionResponseEvent = jsonObjectMapper.convertJsonString(subscriptionResponseJsonData, CmSubscriptionDmiOutEvent.class)
-        and: 'a subscription outcome event'
-            def subscriptionOutcomeJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpOutEvent.json')
-            def subscriptionOutcomeEvent = jsonObjectMapper.convertJsonString(subscriptionOutcomeJsonData, CmSubscriptionNcmpOutEvent.class)
+    def 'Create ncmp out message as expected'() {
+        given: 'a cm subscription event'
+            def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionEvent.json')
+            def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class)
+        and: 'a ncmp out event'
+            def ncmpOutEventJsonData = TestUtils.getResourceFileContent('cmSubscriptionNcmpOutEvent.json')
+            def ncmpOutEvent = jsonObjectMapper.convertJsonString(ncmpOutEventJsonData, CmSubscriptionNcmpOutEvent.class)
         and: 'a status code and status message a per #scenarios'
-            subscriptionOutcomeEvent.getData().setStatusCode(statusCode)
-            subscriptionOutcomeEvent.getData().setStatusMessage(statusMessage)
-        when: 'a subscription event outcome message is being formed'
-            def result = objectUnderTest.fromDmiOutEvent(subscriptionResponseEvent, ncmpEventResponseCode)
-        then: 'the result will be equal to event outcome'
-            result == subscriptionOutcomeEvent
+            ncmpOutEvent.getData().setStatusCode(statusCode)
+            ncmpOutEvent.getData().setStatusMessage(statusMessage)
+        when: 'a cm subscription event is being formed'
+            def expectedResult = objectUnderTest.fromCmSubscriptionEvent(cmSubscriptionEvent, ncmpEventResponseCode)
+        then: 'the result will be equal to ncmp out event'
+            expectedResult == ncmpOutEvent
         where: 'the following values are used'
             scenario             | ncmpEventResponseCode                                        || statusMessage                          ||  statusCode
             'is full outcome'    | NcmpEventResponseCode.SUCCESSFULLY_APPLIED_SUBSCRIPTION      || 'successfully applied subscription'    ||  1
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy
new file mode 100644 (file)
index 0000000..48de23d
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *  ============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.ncmp.api.impl.trustlevel
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.hazelcast.collection.ISet
+import io.cloudevents.CloudEvent
+import io.cloudevents.core.builder.CloudEventBuilder
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.boot.test.context.SpringBootTest
+import spock.lang.Specification
+
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
+class DeviceHeartbeatConsumerSpec extends Specification {
+
+    def mockUntrustworthyCmHandlesSet = Mock(ISet<String>)
+    def objectMapper = new ObjectMapper()
+
+    def objectUnderTest = new DeviceHeartbeatConsumer(mockUntrustworthyCmHandlesSet)
+
+    def 'Operations to be done in an empty untrustworthy set for #scenario'() {
+        given: 'an event with trustlevel as #trustLevel'
+            def incomingEvent = testCloudEvent(trustLevel)
+        and: 'transformed as a kafka record'
+            def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', incomingEvent)
+            consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1'))
+        when: 'the event is consumed'
+            objectUnderTest.heartbeatListener(consumerRecord)
+        then: 'untrustworthy cmhandles are stored'
+            untrustworthyCmHandlesSetInvocationForAdd * mockUntrustworthyCmHandlesSet.add(_)
+        and: 'trustworthy cmHandles will be removed from untrustworthy set'
+            untrustworthyCmHandlesSetInvocationForContains * mockUntrustworthyCmHandlesSet.contains(_)
+
+        where: 'below scenarios are applicable'
+            scenario         | trustLevel          || untrustworthyCmHandlesSetInvocationForAdd | untrustworthyCmHandlesSetInvocationForContains
+            'None trust'     | TrustLevel.NONE     || 1                                         | 0
+            'Complete trust' | TrustLevel.COMPLETE || 0                                         | 1
+    }
+
+    def 'Invalid trust'() {
+        when: 'we provide an invalid trust in the event'
+            def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', testCloudEvent(null))
+            consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1'))
+            objectUnderTest.heartbeatListener(consumerRecord)
+        then: 'no interaction with the untrustworthy cmhandles set'
+            0 * mockUntrustworthyCmHandlesSet.add(_)
+            0 * mockUntrustworthyCmHandlesSet.contains(_)
+            0 * mockUntrustworthyCmHandlesSet.remove(_)
+        and: 'control flow returns without any exception'
+            noExceptionThrown()
+
+    }
+
+    def 'Remove trustworthy cmhandles from untrustworthy cmhandles set'() {
+        given: 'an event with COMPLETE trustlevel'
+            def incomingEvent = testCloudEvent(TrustLevel.COMPLETE)
+        and: 'transformed as a kafka record'
+            def consumerRecord = new ConsumerRecord<String, CloudEvent>('test-device-heartbeat', 0, 0, 'cmhandle1', incomingEvent)
+            consumerRecord.headers().add('ce_id', objectMapper.writeValueAsBytes('cmhandle1'))
+        and: 'untrustworthy cmhandles set contains cmhandle1'
+            1 * mockUntrustworthyCmHandlesSet.contains(_) >> true
+        when: 'the event is consumed'
+            objectUnderTest.heartbeatListener(consumerRecord)
+        then: 'cmhandle removed from untrustworthy cmhandles set'
+            1 * mockUntrustworthyCmHandlesSet.remove(_) >> {
+                args ->
+                    {
+                        args[0].equals('cmhandle1')
+                    }
+            }
+
+    }
+
+    def testCloudEvent(trustLevel) {
+        return CloudEventBuilder.v1().withData(objectMapper.writeValueAsBytes(new DeviceTrustLevel(trustLevel)))
+            .withId("cmhandle1")
+            .withSource(URI.create('DMI'))
+            .withDataSchema(URI.create('test'))
+            .withType('org.onap.cm.events.trustlevel-notification')
+            .build()
+    }
+
+}
index c866824..38b2056 100644 (file)
@@ -110,9 +110,9 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
                 toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class)
         and: 'data operation response event response size is 3'
             dataOperationResponseEvent.data.responses.size() == 3
-        and: 'verify published response data as json string'
-            jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses)
-                    == '[{"operationId":"operational-14","ids":["unknown-cm-handle"],"statusCode":"100","statusMessage":"cm handle id(s) not found"},{"operationId":"operational-14","ids":["non-ready-cm handle"],"statusCode":"101","statusMessage":"cm handle(s) not ready"},{"operationId":"running-12","ids":["non-ready-cm handle"],"statusCode":"101","statusMessage":"cm handle(s) not ready"}]'
+        and: 'verify published data operation response as json string'
+        def dataOperationResponseEventJson = TestUtils.getResourceFileContent('dataOperationResponseEvent.json')
+            jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson
     }
 
     static def getYangModelCmHandles() {
@@ -126,7 +126,7 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
                 new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
                 new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
                 new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
-                new YangModelCmHandle(id: 'non-ready-cm handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState)
+                new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState)
         ]
     }
 }
index dfe8f50..ae14b5c 100644 (file)
         "id": "CMHandle2",
         "status": "REJECTED",
         "details": "Some other error message from the DMI"
-      },
-      {
-        "id": "CMHandle3",
-        "status": "PENDING",
-        "details": "No reply from DMI yet"
-      },
-      {
-        "id": "CMHandle4",
-        "status": "PENDING",
-        "details": "No reply from DMI yet"
       }
     ]
   }
diff --git a/cps-ncmp-service/src/test/resources/cmSubscriptionEvent.json b/cps-ncmp-service/src/test/resources/cmSubscriptionEvent.json
new file mode 100644 (file)
index 0000000..c38cb79
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "clientId": "SCO-9989752",
+  "subscriptionName": "cm-subscription-001",
+  "cmSubscriptionStatus": [
+    {
+      "id": "CMHandle1",
+      "status": "REJECTED",
+      "details": "Some error message from the DMI"
+    },
+    {
+      "id": "CMHandle2",
+      "status": "REJECTED",
+      "details": "Some other error message from the DMI"
+    },
+    {
+      "id": "CMHandle3",
+      "status": "PENDING",
+      "details": "Some error causes pending"
+    },
+    {
+      "id": "CMHandle4",
+      "status": "PENDING",
+      "details": "Some other error happened"
+    },
+    {
+      "id": "CMHandle5",
+      "status": "PENDING",
+      "details": "Some other error happened"
+    }
+  ]
+}
\ No newline at end of file
index 14e8cbb..856f238 100644 (file)
       ],
       "pending": [
         {
-          "details": "No reply from DMI yet",
-          "targets": ["CMHandle3", "CMHandle4"]
+          "details": "Some other error happened",
+          "targets": ["CMHandle4", "CMHandle5"]
+        },
+        {
+          "details": "Some error causes pending",
+          "targets": ["CMHandle3"]
         }
       ]
     }
index d2e0d64..f69b876 100644 (file)
         "ch3-dmi2",
         "unknown-cm-handle",
         "ch6-dmi1",
-        "non-ready-cm handle"
+        "non-ready-cm-handle"
       ]
     },
     {
       "operation": "read",
       "operationId": "running-12",
       "datastore": "ncmp-datastore:passthrough-running",
+      "options": "some option",
+      "resourceIdentifier": "some resource identifier",
       "targetIds": [
         "ch1-dmi1",
         "ch7-dmi2",
         "ch2-dmi1",
-        "non-ready-cm handle"
+        "non-ready-cm-handle"
       ]
     },
     {
@@ -29,6 +31,7 @@
       "operationId": "operational-15",
       "datastore": "ncmp-datastore:passthrough-operational",
       "options": "some option",
+      "resourceIdentifier": "some resource identifier",
       "targetIds": [
         "ch4-dmi2",
         "ch6-dmi1"
diff --git a/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json
new file mode 100644 (file)
index 0000000..611d47d
--- /dev/null
@@ -0,0 +1 @@
+[{"operationId":"operational-14","ids":["unknown-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"100","statusMessage":"cm handle id(s) not found"},{"operationId":"operational-14","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"},{"operationId":"running-12","ids":["non-ready-cm-handle"],"resourceIdentifier":"some resource identifier","options":"some option","statusCode":"101","statusMessage":"cm handle(s) not ready"}]
\ No newline at end of file
index fd669b7..c30a63f 100644 (file)
@@ -181,12 +181,46 @@ class QueryRestControllerSpec extends Specification {
                         .andReturn().response
         then: 'the response contains the the datanode in json format'
             assert response.status == HttpStatus.OK.value()
-            assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedPageSize
+            assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedTotalPageSize
             assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
             assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}')
         where: 'the following options for include descendants are provided in the request'
-            scenario                     | pageIndex | pageSize | totalAnchors || expectedPageSize
+            scenario                     | pageIndex | pageSize | totalAnchors || expectedTotalPageSize
             '1st page with all anchors'  | 1         | 3        | 3            || 1
             '1st page with less anchors' | 1         | 2        | 3            || 2
     }
+
+    def 'Query data node across all anchors with pagination option with #scenario.'() {
+        given: 'service method returns a list containing a data node from different anchors'
+        def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
+                .withAnchor('my_anchor')
+                .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
+        def dataNode2 = new DataNodeBuilder().withXpath('/xpath')
+                .withAnchor('my_anchor_2')
+                .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build()
+        and: 'the query endpoint'
+            def dataspaceName = 'my_dataspace'
+            def cpsPath = 'some/cps/path'
+            def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query"
+            mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath,
+                INCLUDE_ALL_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2]
+            mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2
+        when: 'query data nodes API is invoked'
+            def response =
+                mvc.perform(
+                        get(dataNodeEndpoint)
+                                .param('cps-path', cpsPath)
+                                .param('descendants', "all")
+                                .param(parameterName, "1"))
+                        .andReturn().response
+        then: 'the response contains the the datanode in json format'
+            assert response.status == HttpStatus.OK.value()
+            assert Integer.valueOf(response.getHeaderValue("total-pages")) == 1
+            assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
+            assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}')
+        where:
+            scenario           | parameterName
+            'only page size'   | 'pageSize'
+            'only page index'  | 'pageIndex'
+    }
 }
index 6f9f5a4..847a4a3 100755 (executable)
@@ -171,6 +171,18 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
         anchorRepository.deleteAllByDataspaceAndNameIn(dataspaceEntity, anchorNames);
     }
 
+    @Transactional
+    @Override
+    public void updateAnchorSchemaSet(final String dataspaceName,
+                                         final String anchorName,
+                                         final String schemaSetName) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+        final SchemaSetEntity schemaSetEntity = schemaSetRepository
+                .getByDataspaceAndName(dataspaceEntity, schemaSetName);
+        anchorRepository.updateAnchorSchemaSetId(schemaSetEntity.getId(), anchorEntity.getId());
+    }
+
     private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
index 5bb5857..b8503a7 100755 (executable)
@@ -99,4 +99,8 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Long> {
         deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0]));
     }
 
+    @Modifying
+    @Query(value = "UPDATE anchor SET schema_set_id =:schemaSetId WHERE id = :anchorId ", nativeQuery = true)
+    void updateAnchorSchemaSetId(@Param("schemaSetId") int schemaSetId, @Param("anchorId") long anchorId);
+
 }
index 4f056c8..c187f20 100644 (file)
@@ -94,7 +94,7 @@ public class FragmentPrefetchRepositoryImpl implements FragmentPrefetchRepositor
             final FragmentEntity fragmentEntity = new FragmentEntity();
             fragmentEntity.setId(resultSet.getLong("id"));
             fragmentEntity.setXpath(resultSet.getString("xpath"));
-            fragmentEntity.setParentId(resultSet.getLong("parentId"));
+            fragmentEntity.setParentId(resultSet.getObject("parentId", Long.class));
             fragmentEntity.setAttributes(resultSet.getString("attributes"));
             fragmentEntity.setAnchor(anchorEntityPerId.get(resultSet.getLong("anchorId")));
             fragmentEntity.setChildFragments(new HashSet<>());
index fcf3f54..edd052a 100755 (executable)
@@ -135,4 +135,13 @@ public interface CpsAdminService {
      *         given module names
      */
     Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames);
+
+    /**
+     * Update schema set of an anchor.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorName    anchor name
+     * @param schemaSetName schema set name
+     */
+    void updateAnchorSchemaSet(String dataspaceName, String anchorName, String schemaSetName);
 }
index e286eea..d83ee43 100755 (executable)
@@ -120,4 +120,11 @@ public class CpsAdminServiceImpl implements CpsAdminService {
         final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
         return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
     }
+
+    @Override
+    public void updateAnchorSchemaSet(final String dataspaceName,
+                                         final String anchorName,
+                                         final String schemaSetName) {
+        cpsAdminPersistenceService.updateAnchorSchemaSet(dataspaceName, anchorName, schemaSetName);
+    }
 }
index 405e6e2..067191b 100644 (file)
@@ -24,6 +24,7 @@ import com.hazelcast.config.Config;
 import com.hazelcast.config.MapConfig;
 import com.hazelcast.config.NamedConfig;
 import com.hazelcast.config.QueueConfig;
+import com.hazelcast.config.SetConfig;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
 import lombok.extern.slf4j.Slf4j;
@@ -57,6 +58,10 @@ public class HazelcastCacheConfig {
         if (namedConfig instanceof QueueConfig) {
             config.addQueueConfig((QueueConfig) namedConfig);
         }
+        if (namedConfig instanceof SetConfig) {
+            config.addSetConfig((SetConfig) namedConfig);
+        }
+
         config.setClusterName(clusterName);
         updateDiscoveryMode(config);
         return config;
@@ -76,6 +81,13 @@ public class HazelcastCacheConfig {
         return commonQueueConfig;
     }
 
+    protected static SetConfig createSetConfig(final String configName) {
+        final SetConfig commonSetConfig = new SetConfig(configName);
+        commonSetConfig.setBackupCount(1);
+        commonSetConfig.setAsyncBackupCount(1);
+        return commonSetConfig;
+    }
+
     protected void updateDiscoveryMode(final Config config) {
         if (cacheKubernetesEnabled) {
             log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName);
index 1c1e80a..5a1810f 100755 (executable)
@@ -133,4 +133,13 @@ public interface CpsAdminPersistenceService {
      * @param anchorNames   anchor names
      */
     void deleteAnchors(String dataspaceName, Collection<String> anchorNames);
+
+    /**
+     * Delete anchors by name in given dataspace.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorName    anchor name
+     * @param schemaSetName schema set name
+     */
+    void updateAnchorSchemaSet(String dataspaceName, String anchorName, String schemaSetName);
 }
index eb41e20..12564fb 100755 (executable)
@@ -178,4 +178,11 @@ class CpsAdminServiceImplSpec extends Specification {
         and: 'the CpsValidator is called on the dataspaceName'
             1 * mockCpsValidator.validateNameCharacters('someDataspace')
     }
+
+    def 'Update anchor schema set.'() {
+        when: 'update anchor is invoked'
+            objectUnderTest.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName')
+        then: 'associated persistence service method is invoked with correct parameter'
+            1 * mockCpsAdminPersistenceService.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName')
+    }
 }
index 8efd485..415e9fd 100644 (file)
@@ -45,10 +45,17 @@ class HazelcastCacheConfigSpec extends Specification {
             } 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
-            'Map Config'   | HazelcastCacheConfig.createMapConfig('my map config')     || true            | false
-            'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false           | true
+            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
     }
 
 }
index cd70cf8..3f672ad 100755 (executable)
@@ -39,6 +39,7 @@ Release Data
 Bug Fixes
 ---------
 3.3.6
+    - `CPS-1841 <https://jira.onap.org/browse/CPS-1841>`_ Update of top-level data node fails with exception
 
 Features
 --------
index 4780e36..03ef9c2 100644 (file)
@@ -108,7 +108,7 @@ class CpsIntegrationSpecBase extends Specification {
     def dataspaceExists(dataspaceName) {
         try {
             cpsAdminService.getDataspace(dataspaceName)
-        } catch (DataspaceNotFoundException e) {
+        } catch (DataspaceNotFoundException dataspaceNotFoundException) {
             return false
         }
         return true
index 92fbdaa..bdd894c 100644 (file)
@@ -22,10 +22,12 @@ package org.onap.cps.integration.functional
 
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
 import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
+import java.time.OffsetDateTime
 
 class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase {
 
@@ -44,8 +46,8 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase {
             def thrown = null
             try {
                 objectUnderTest.getDataspace('newDataspace')
-            } catch(Exception e) {
-                thrown = e
+            } catch(Exception exception) {
+                thrown = exception
             }
            assert thrown instanceof DataspaceNotFoundException
     }
@@ -100,8 +102,8 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase {
             def thrown = null
             try {
                 objectUnderTest.getAnchor(GENERAL_TEST_DATASPACE, 'newAnchor')
-            } catch(Exception e) {
-                thrown = e
+            } catch(Exception exception) {
+                thrown = exception
             }
             assert thrown instanceof AnchorNotFoundException
     }
@@ -151,4 +153,28 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase {
            'just unknown module(s)' | GENERAL_TEST_DATASPACE
     }
 
+    def 'Update anchor schema set.'() {
+        when: 'a new schema set with tree yang model is created'
+            def newTreeYangModelAsString = readResourceDataFile('tree/new-test-tree.yang')
+            cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, 'newTreeSchemaSet', [tree: newTreeYangModelAsString])
+        then: 'an anchor with new schema set is created'
+            objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, 'newTreeSchemaSet', 'anchor4')
+        and: 'the new tree datanode is saved'
+            def treeJsonData = readResourceDataFile('tree/new-test-tree.json')
+            cpsDataService.saveData(GENERAL_TEST_DATASPACE, 'anchor4', treeJsonData, OffsetDateTime.now())
+        and: 'saved tree data node can be retrieved by its normalized xpath'
+            def branchName = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'anchor4', "/test-tree/branch", FetchDescendantsOption.DIRECT_CHILDREN_ONLY)[0].leaves['name']
+            assert branchName == 'left'
+        and: 'a another schema set with updated tree yang model is created'
+            def updatedTreeYangModelAsString = readResourceDataFile('tree/updated-test-tree.yang')
+            cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, 'anotherTreeSchemaSet', [tree: updatedTreeYangModelAsString])
+        and: 'anchor4 schema set is updated with another schema set successfully'
+            objectUnderTest.updateAnchorSchemaSet(GENERAL_TEST_DATASPACE, 'anchor4', 'anotherTreeSchemaSet')
+        when: 'updated tree data node with new leaves'
+            def updatedTreeJsonData = readResourceDataFile('tree/updated-test-tree.json')
+            cpsDataService.updateNodeLeaves(GENERAL_TEST_DATASPACE, "anchor4", "/test-tree/branch[@name='left']", updatedTreeJsonData, OffsetDateTime.now())
+        then: 'updated tree data node can be retrieved by its normalized xpath'
+            def birdsName = cpsDataService.getDataNodes(GENERAL_TEST_DATASPACE, 'anchor4',"/test-tree/branch[@name='left']/nest", FetchDescendantsOption.DIRECT_CHILDREN_ONLY)[0].leaves['birds']
+            assert birdsName as String == '[Raven, Night Owl, Crow]'
+    }
 }
index 935a17a..2fe2753 100644 (file)
@@ -408,6 +408,17 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
             restoreBookstoreDataAnchor(1)
     }
 
+    def 'Update bookstore top-level container data node.'() {
+        when: 'the bookstore top-level container is updated'
+            def json = '{ "bookstore": { "bookstore-name": "new bookstore" }}'
+            objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now)
+        then: 'bookstore name has been updated'
+            def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', DIRECT_CHILDREN_ONLY)
+            result.leaves.'bookstore-name'[0] == 'new bookstore'
+        cleanup:
+            restoreBookstoreDataAnchor(1)
+    }
+
     def 'Update multiple data node leaves.'() {
         given: 'Updated json for bookstore data'
             def jsonData =  "{'book-store:books':{'lang':'English/French','price':100,'title':'Matilda'}}"
diff --git a/integration-test/src/test/resources/data/tree/new-test-tree.json b/integration-test/src/test/resources/data/tree/new-test-tree.json
new file mode 100644 (file)
index 0000000..f7aefc4
--- /dev/null
@@ -0,0 +1,12 @@
+{
+  "test-tree": {
+    "branch": [
+      {
+        "name": "left",
+        "nest": {
+          "name": "small"
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/tree/new-test-tree.yang b/integration-test/src/test/resources/data/tree/new-test-tree.yang
new file mode 100644 (file)
index 0000000..1a08b92
--- /dev/null
@@ -0,0 +1,21 @@
+module test-tree {
+    yang-version 1.1;
+
+    namespace "org:onap:cps:test:test-tree";
+    prefix tree;
+    revision "2020-02-02";
+
+    container test-tree {
+        list branch {
+            key "name";
+            leaf name {
+                type string;
+            }
+            container nest {
+                leaf name {
+                    type string;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/tree/updated-test-tree.json b/integration-test/src/test/resources/data/tree/updated-test-tree.json
new file mode 100644 (file)
index 0000000..2c2eea4
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "nest": {
+    "name": "small",
+    "birds": [
+      "Night Owl",
+      "Raven",
+      "Crow"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/tree/updated-test-tree.yang b/integration-test/src/test/resources/data/tree/updated-test-tree.yang
new file mode 100644 (file)
index 0000000..bd883e8
--- /dev/null
@@ -0,0 +1,33 @@
+module test-tree {
+    yang-version 1.1;
+
+    namespace "org:onap:cps:test:test-tree";
+    prefix tree;
+
+    revision "2023-08-17" {
+        description
+            "added list of birds to nest";
+    }
+
+    revision "2020-09-15" {
+        description
+            "Sample Model";
+    }
+
+    container test-tree {
+        list branch {
+            key "name";
+            leaf name {
+                type string;
+            }
+            container nest {
+                leaf name {
+                    type string;
+                }
+                leaf-list birds {
+                    type string;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file