diff options
48 files changed, 986 insertions, 259 deletions
diff --git a/.github/workflows/gerrit-verify.yaml b/.github/workflows/gerrit-verify.yaml new file mode 100644 index 000000000..80447d35e --- /dev/null +++ b/.github/workflows/gerrit-verify.yaml @@ -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 diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index a18de2acd..6aefda9c3 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -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: diff --git a/cps-ncmp-events/src/main/resources/schemas/cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json index c08a4ea51..ebbdde931 100644 --- a/cps-ncmp-events/src/main/resources/schemas/cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json +++ b/cps-ncmp-events/src/main/resources/schemas/cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json @@ -14,8 +14,7 @@ "type": "string", "enum": [ "ACCEPTED", - "REJECTED", - "PENDING" + "REJECTED" ] }, "details" : { diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 63c5f16d0..e60e17418 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -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 index 000000000..816fc5067 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/TrustLevelCacheConfig.java @@ -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"); + } + + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumer.java index 1ac404411..9459778fd 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumer.java @@ -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); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java index 99452c6c2..0fe2c9ae5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java @@ -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()) )); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarder.java index 4a174954e..ea2d17d3a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarder.java @@ -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); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java index 38cc724be..473538c93 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java @@ -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()); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/ResponseTimeoutTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/ResponseTimeoutTask.java index 7f8cbf676..78b000dae 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/ResponseTimeoutTask.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/ResponseTimeoutTask.java @@ -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); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index 02de9854e..ba6f89192 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -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 index 000000000..458c1b851 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumer.java @@ -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 index 000000000..2ed4e4522 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/DeviceTrustLevel.java @@ -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 index 000000000..f4254bb47 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevel.java @@ -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 diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java index 2d9a51b84..65cda9478 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/DataOperationEventCreator.java @@ -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; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java index d8fb904f2..c455337ac 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java @@ -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 index 000000000..2a60b2ab1 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionEvent.java @@ -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 index 000000000..bba560785 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionStatus.java @@ -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; +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy index a8a21b2ed..175ead806 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy @@ -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() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy index 036bedb8a..b13a2ceba 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy @@ -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 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy index df5167da9..07b0925dc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionDmiOutEventToCmSubscriptionNcmpOutEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy @@ -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() diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy index a13fe53e8..fd1a83ee0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy @@ -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) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy index 07e2e3f6a..cc1419519 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy @@ -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 index 000000000..48de23dca --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/DeviceHeartbeatConsumerSpec.groovy @@ -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() + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy index c86682406..38b2056d0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy @@ -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) ] } } diff --git a/cps-ncmp-service/src/test/resources/cmSubscriptionDmiOutEvent.json b/cps-ncmp-service/src/test/resources/cmSubscriptionDmiOutEvent.json index dfe8f5046..ae14b5ca2 100644 --- a/cps-ncmp-service/src/test/resources/cmSubscriptionDmiOutEvent.json +++ b/cps-ncmp-service/src/test/resources/cmSubscriptionDmiOutEvent.json @@ -13,16 +13,6 @@ "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 index 000000000..c38cb7921 --- /dev/null +++ b/cps-ncmp-service/src/test/resources/cmSubscriptionEvent.json @@ -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 diff --git a/cps-ncmp-service/src/test/resources/cmSubscriptionNcmpOutEvent.json b/cps-ncmp-service/src/test/resources/cmSubscriptionNcmpOutEvent.json index 14e8cbb4c..856f238c6 100644 --- a/cps-ncmp-service/src/test/resources/cmSubscriptionNcmpOutEvent.json +++ b/cps-ncmp-service/src/test/resources/cmSubscriptionNcmpOutEvent.json @@ -15,8 +15,12 @@ ], "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"] } ] } diff --git a/cps-ncmp-service/src/test/resources/dataOperationRequest.json b/cps-ncmp-service/src/test/resources/dataOperationRequest.json index d2e0d6489..f69b87631 100644 --- a/cps-ncmp-service/src/test/resources/dataOperationRequest.json +++ b/cps-ncmp-service/src/test/resources/dataOperationRequest.json @@ -10,18 +10,20 @@ "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 index 000000000..611d47d1a --- /dev/null +++ b/cps-ncmp-service/src/test/resources/dataOperationResponseEvent.json @@ -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 diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy index fd669b75c..c30a63fd4 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy @@ -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' + } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java index 6f9f5a482..847a4a32f 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java @@ -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); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java index 5bb585781..b8503a7fe 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java @@ -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); + } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java index 4f056c8f6..c187f20ea 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java @@ -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<>()); diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java index fcf3f54cc..edd052a51 100755 --- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java @@ -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); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java index e286eea17..d83ee434d 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java @@ -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); + } } diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java index 405e6e2a8..067191b5a 100644 --- a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java +++ b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java @@ -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); diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java index 1c1e80a20..5a1810f47 100755 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java @@ -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); } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy index eb41e2085..12564fb6d 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy @@ -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') + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy index 8efd48547..415e9fd49 100644 --- a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy @@ -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 } } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index cd70cf8be..3f672ad62 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -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 -------- diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 4780e3642..03ef9c2fd 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -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 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy index 92fbdaaa2..bdd894c31 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy @@ -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]' + } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy index 935a17a01..2fe275383 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy @@ -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 index 000000000..f7aefc472 --- /dev/null +++ b/integration-test/src/test/resources/data/tree/new-test-tree.json @@ -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 index 000000000..1a08b92f1 --- /dev/null +++ b/integration-test/src/test/resources/data/tree/new-test-tree.yang @@ -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 index 000000000..2c2eea4f0 --- /dev/null +++ b/integration-test/src/test/resources/data/tree/updated-test-tree.json @@ -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 index 000000000..bd883e8b6 --- /dev/null +++ b/integration-test/src/test/resources/data/tree/updated-test-tree.yang @@ -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 |