From: Toine Siebelink Date: Mon, 22 Jan 2024 08:35:36 +0000 (+0000) Subject: Merge "Use PollingConditions to improve intermittent test failure" X-Git-Tag: 3.4.3~24 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=2ba1fea36de49e9b9e82882ead17fa51f53ea66f;hp=76f2021e559cd8ab5cf08e26d5aa4533e3d46309;p=cps.git Merge "Use PollingConditions to improve intermittent test failure" --- diff --git a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-in-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-in-event-schema-1.0.0.json deleted file mode 100644 index 3750bd047..000000000 --- a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-in-event-schema-1.0.0.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.events:cm-subscription-dmi-in-event-schema:1.0.0", - "$ref": "#/definitions/CmSubscriptionDmiInEvent", - "definitions": { - "CmSubscriptionDmiInEvent": { - "description": "The payload for subscription event to be forwarded to dmi plugins.", - "javaType": "org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent", - "properties": { - "data": { - "properties": { - "dataType": { - "description": "The datatype content.", - "properties": { - "dataCategory": { - "description": "The category type of the data", - "type": "string" - }, - "dataProvider": { - "description": "The provider name of the data", - "type": "string" - }, - "dataspace": { - "description": "The dataspace name", - "type": "string" - } - }, - "required": [ - "dataCategory", - "dataProvider", - "dataspace" - ], - "type": "object", - "additionalProperties": false - }, - "predicates": { - "description": "Additional values to be added into the subscription", - "properties": { - "datastore": { - "description": "datastore which is to be used by the subscription", - "type": "string" - }, - "targets": { - "description": "CM Handles to be targeted by the subscription", - "type": "array", - "items": { - "$ref": "#/definitions/CmHandle" - } - }, - "datastore-xpath-filter": { - "description": "filter to be applied to the CM Handles through this event", - "type": "string" - } - }, - "required": [ - "datastore", - "targets", - "datastore-xpath-filter" - ], - "type": "object", - "additionalProperties": false - }, - "subscription": { - "description": "The subscription details.", - "properties": { - "clientID": { - "description": "The clientID", - "type": "string" - }, - "name": { - "description": "The name of the subscription", - "type": "string" - }, - "isTagged": { - "description": "optional parameter, default is no", - "type": "boolean", - "default": false - } - }, - "required": [ - "clientID", - "name" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "dataType", - "predicates", - "subscription" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false, - "required": [ - "data" - ] - }, - "CmHandle": { - "description": "The CM handle information", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "additional-properties": { - "existingJavaType": "java.util.Map" - } - }, - "required": [ - "id", - "additional-properties" - ], - "additionalProperties": false - } - } -} \ No newline at end of file diff --git a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json deleted file mode 100644 index ebbdde931..000000000 --- a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-dmi-out-event-schema-1.0.0.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.events:cm-subscription-dmi-out-event-schema:1.0.0", - "$ref": "#/definitions/CmSubscriptionDmiOutEvent", - "definitions": { - "SubscriptionStatus": { - "description": "The subscription status information", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status" : { - "type": "string", - "enum": [ - "ACCEPTED", - "REJECTED" - ] - }, - "details" : { - "type": "string" - } - }, - "required": [ - "id", - "status" - ], - "additionalProperties": false - }, - "CmSubscriptionDmiOutEvent" : { - "description": "The payload for subscription response event.", - "type": "object", - "javaType": "org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent", - "properties": { - "data": { - "type": "object", - "properties": { - "clientId": { - "type": "string" - }, - "subscriptionName": { - "type": "string" - }, - "dmiName": { - "type": "string" - }, - "subscriptionStatus": { - "type": "array", - "items": { - "$ref": "#/definitions/SubscriptionStatus" - } - } - }, - "required": [ - "clientId", - "subscriptionName", - "dmiName", - "subscriptionStatus" - ], - "additionalProperties": false - } - }, - "additionalProperties": false, - "required": [ - "data" - ] - } - } -} \ No newline at end of file diff --git a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-in-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-in-event-schema-1.0.0.json deleted file mode 100644 index 818a8e78b..000000000 --- a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-in-event-schema-1.0.0.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$id": "urn:cps:org.onap.cps.ncmp.events:cm-subscription-ncmp-in-event:1.0.0", - "$ref": "#/definitions/CmSubscriptionNcmpInEvent", - "$schema": "https://json-schema.org/draft/2019-09/schema", - "definitions": { - "CmSubscriptionNcmpInEvent": { - "description": "The payload for subscription event.", - "javaType": "org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent", - "properties": { - "data": { - "properties": { - "dataType": { - "description": "The datatype content.", - "properties": { - "dataCategory": { - "description": "The category type of the data", - "type": "string" - }, - "dataProvider": { - "description": "The provider name of the data", - "type": "string" - }, - "dataspace": { - "description": "The dataspace name", - "type": "string" - } - }, - "required": [ - "dataCategory", - "dataProvider", - "dataspace" - ], - "type": "object", - "additionalProperties": false - }, - "predicates": { - "description": "Additional values to be added into the subscription", - "properties": { - "datastore": { - "description": "datastore which is to be used by the subscription", - "type": "string" - }, - "targets": { - "description": "CM Handles to be targeted by the subscription", - "type": "array", - "items": { - "type": "string" - } - }, - "datastore-xpath-filter": { - "description": "filter to be applied to the CM Handles through this event", - "type": "string" - } - }, - "required": [ - "datastore", - "targets", - "datastore-xpath-filter" - ], - "type": "object", - "additionalProperties": false - }, - "subscription": { - "description": "The subscription details.", - "properties": { - "clientID": { - "description": "The clientID", - "type": "string" - }, - "name": { - "description": "The name of the subscription", - "type": "string" - } - }, - "required": [ - "clientID", - "name" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "dataType", - "predicates", - "subscription" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false, - "required": [ - "data" - ] - } - } -} \ No newline at end of file diff --git a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-out-event-schema-1.0.0.json b/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-out-event-schema-1.0.0.json deleted file mode 100644 index 9c0c28b2f..000000000 --- a/cps-ncmp-events/src/main/resources/schemas/deprecated.cmsubscription/cm-subscription-ncmp-out-event-schema-1.0.0.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.events:cm-subscription-ncmp-out-event-schema:1.0.0", - "$ref": "#/definitions/CmSubscriptionNcmpOutEvent", - "definitions": { - "CmSubscriptionNcmpOutEvent": { - "description": "The payload for avc subscription event outcome message.", - "type": "object", - "javaType": "org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/definitions/data" - } - }, - "required": [ - "data" - ] - }, - "data": { - "type": "object", - "description": "The actual data containing information about the pending and rejected targets", - "additionalProperties": false, - "properties": { - "statusCode": { - "type": "integer" - }, - "statusMessage": { - "type": "string" - }, - "additionalInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "rejected": { - "$ref": "#/definitions/additionalInfoDetails" - }, - "pending": { - "$ref": "#/definitions/additionalInfoDetails" - } - } - } - }, - "required": [ - "statusCode", - "statusMessage" - ] - }, - "additionalInfoDetails": { - "type": "array", - "items": { - "type": "object", - "description": "Details for the target cmhandles", - "additionalProperties": false, - "properties": { - "details": { - "type": "string" - }, - "targets": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfig.java deleted file mode 100644 index 306d103a8..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.config.embeddedcache; - -import com.hazelcast.config.MapConfig; -import com.hazelcast.map.IMap; -import java.util.Set; -import org.onap.cps.cache.HazelcastCacheConfig; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Core infrastructure of the hazelcast distributed cache for subscription forward config use cases. - */ -@Configuration -public class ForwardedSubscriptionEventCacheConfig extends HazelcastCacheConfig { - - public static final int SUBSCRIPTION_FORWARD_STARTED_TTL_SECS = 600; - - private static final MapConfig forwardedSubscriptionEventCacheMapConfig = - createMapConfig("forwardedSubscriptionEventCacheMapConfig"); - - /** - * Distributed instance of forwarded subscription information cache that contains subscription event - * id by dmi names as properties. - * - * @return configured map of subscription event ids as keys to sets of dmi names for values - */ - @Bean - public IMap> forwardedSubscriptionEventCache() { - return createHazelcastInstance("hazelCastInstanceSubscriptionEvents", - forwardedSubscriptionEventCacheMapConfig).getMap("forwardedSubscriptionEventCache"); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistence.java deleted file mode 100644 index 7aa073d35..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistence.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.deprecated.subscriptions; - -import java.util.Collection; -import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent; -import org.onap.cps.spi.model.DataNode; - -public interface SubscriptionPersistence extends NcmpPersistence { - - /** - * Save subscription Event. - * - * @param yangModelSubscriptionEvent subscription Event as Yang Model. - */ - void saveSubscriptionEvent(YangModelSubscriptionEvent yangModelSubscriptionEvent); - - /** - * Get data nodes. - * - * @return the DataNode as collection. - */ - Collection getDataNodesForSubscriptionEvent(); - - /** - * Get data nodes by xpath. - * - * @return the DataNode as collection. - */ - Collection getCmHandlesForSubscriptionEvent(String clientId, String subscriptionName); -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceImpl.java deleted file mode 100644 index 29b7c7d04..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.deprecated.subscriptions; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsDataService; -import org.onap.cps.api.CpsModuleService; -import org.onap.cps.ncmp.api.impl.inventory.NcmpPersistenceImpl; -import org.onap.cps.ncmp.api.impl.utils.DataNodeHelper; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent; -import org.onap.cps.spi.FetchDescendantsOption; -import org.onap.cps.spi.model.DataNode; -import org.onap.cps.spi.utils.CpsValidator; -import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class SubscriptionPersistenceImpl extends NcmpPersistenceImpl implements SubscriptionPersistence { - - private static final String SUBSCRIPTION_ANCHOR_NAME = "AVC-Subscriptions"; - private static final String SUBSCRIPTION_REGISTRY_PARENT = "/subscription-registry"; - - public SubscriptionPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService, - final CpsModuleService cpsModuleService, final CpsValidator cpsValidator) { - super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator); - } - - - @Override - public void saveSubscriptionEvent(final YangModelSubscriptionEvent yangModelSubscriptionEvent) { - final String clientId = yangModelSubscriptionEvent.getClientId(); - final String subscriptionName = yangModelSubscriptionEvent.getSubscriptionName(); - - final Collection dataNodes = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, - SUBSCRIPTION_ANCHOR_NAME, SUBSCRIPTION_REGISTRY_PARENT, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - - if (isSubscriptionRegistryEmptyOrNonExist(dataNodes, clientId, subscriptionName)) { - saveSubscriptionEventYangModel(createSubscriptionEventJsonData( - jsonObjectMapper.asJsonString(yangModelSubscriptionEvent))); - } else { - findDeltaCmHandlesAddOrUpdateInDatabase(yangModelSubscriptionEvent, clientId, subscriptionName, dataNodes); - } - } - - private void findDeltaCmHandlesAddOrUpdateInDatabase(final YangModelSubscriptionEvent yangModelSubscriptionEvent, - final String clientId, final String subscriptionName, - final Collection dataNodes) { - final Map> cmHandleIdToStatusAndDetailsAsMapNew = - extractCmHandleFromYangModelAsMap(yangModelSubscriptionEvent); - final Map> cmHandleIdToStatusAndDetailsAsMapOriginal = - DataNodeHelper.cmHandleIdToStatusAndDetailsAsMapFromDataNode(dataNodes); - - final Map> newTargetCmHandles = mapDifference(cmHandleIdToStatusAndDetailsAsMapNew, - cmHandleIdToStatusAndDetailsAsMapOriginal); - traverseCmHandleList(newTargetCmHandles, clientId, subscriptionName, true); - - final Map> existingTargetCmHandles = - mapDifference(cmHandleIdToStatusAndDetailsAsMapNew, newTargetCmHandles); - traverseCmHandleList(existingTargetCmHandles, clientId, subscriptionName, false); - } - - private static Map> extractCmHandleFromYangModelAsMap( - final YangModelSubscriptionEvent yangModelSubscriptionEvent) { - return yangModelSubscriptionEvent.getPredicates().getTargetCmHandles() - .stream().collect( - HashMap::new, - (result, cmHandle) -> { - final String cmHandleId = cmHandle.getCmHandleId(); - final SubscriptionStatus status = cmHandle.getStatus(); - final String details = cmHandle.getDetails(); - - if (cmHandleId != null && status != null) { - result.put(cmHandleId, new HashMap<>()); - result.get(cmHandleId).put("status", status.toString()); - result.get(cmHandleId).put("details", details == null ? "" : details); - } - }, - HashMap::putAll - ); - } - - private void traverseCmHandleList(final Map> cmHandleMap, - final String clientId, - final String subscriptionName, - final boolean isAddListElementOperation) { - final List cmHandleList = targetCmHandlesAsList(cmHandleMap); - for (final YangModelSubscriptionEvent.TargetCmHandle targetCmHandle : cmHandleList) { - final String targetCmHandleAsJson = - createTargetCmHandleJsonData(jsonObjectMapper.asJsonString(targetCmHandle)); - addOrReplaceCmHandlePredicateListElement(targetCmHandleAsJson, clientId, subscriptionName, - isAddListElementOperation); - } - } - - private boolean isSubscriptionRegistryEmptyOrNonExist(final Collection dataNodes, - final String clientId, final String subscriptionName) { - final Optional dataNodeFirst = dataNodes.stream().findFirst(); - return ((dataNodeFirst.isPresent() && dataNodeFirst.get().getChildDataNodes().isEmpty()) - || getCmHandlesForSubscriptionEvent(clientId, subscriptionName).isEmpty()); - } - - private void addOrReplaceCmHandlePredicateListElement(final String targetCmHandleAsJson, - final String clientId, - final String subscriptionName, - final boolean isAddListElementOperation) { - if (isAddListElementOperation) { - log.info("targetCmHandleAsJson to be added into DB {}", targetCmHandleAsJson); - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - createCmHandleXpathPredicates(clientId, subscriptionName), targetCmHandleAsJson, NO_TIMESTAMP); - } else { - log.info("targetCmHandleAsJson to be updated into DB {}", targetCmHandleAsJson); - cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - createCmHandleXpathPredicates(clientId, subscriptionName), targetCmHandleAsJson, NO_TIMESTAMP); - } - } - - private void saveSubscriptionEventYangModel(final String subscriptionEventJsonData) { - log.info("SubscriptionEventJsonData to be saved into DB {}", subscriptionEventJsonData); - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - SUBSCRIPTION_REGISTRY_PARENT, subscriptionEventJsonData, NO_TIMESTAMP); - } - - @Override - public Collection getDataNodesForSubscriptionEvent() { - return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - SUBSCRIPTION_REGISTRY_PARENT, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - } - - @Override - public Collection getCmHandlesForSubscriptionEvent(final String clientId, final String subscriptionName) { - return cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - List.of(createCmHandleXpath(clientId, subscriptionName)), - FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - } - - private static List targetCmHandlesAsList( - final Map> newCmHandles) { - return newCmHandles.entrySet().stream().map(entry -> { - final String cmHandleId = entry.getKey(); - final Map statusAndDetailsMap = entry.getValue(); - final String status = statusAndDetailsMap.get("status"); - final String details = statusAndDetailsMap.get("details"); - return new YangModelSubscriptionEvent.TargetCmHandle(cmHandleId, SubscriptionStatus.fromString(status), - details); - }).collect(Collectors.toList()); - } - - private static String createSubscriptionEventJsonData(final String yangModelSubscriptionAsJson) { - return "{\"subscription\":[" + yangModelSubscriptionAsJson + "]}"; - } - - private static String createTargetCmHandleJsonData(final String targetCmHandleAsJson) { - return "{\"targetCmHandles\":[" + targetCmHandleAsJson + "]}"; - } - - private static String createCmHandleXpathPredicates(final String clientId, final String subscriptionName) { - return "/subscription-registry/subscription[@clientID='" + clientId - + "' and @subscriptionName='" + subscriptionName + "']/predicates"; - } - - private static String createCmHandleXpath(final String clientId, final String subscriptionName) { - return "/subscription-registry/subscription[@clientID='" + clientId - + "' and @subscriptionName='" + subscriptionName + "']"; - } - - private static Map> mapDifference(final Map> left, - final Map> right) { - final Map> difference = new HashMap<>(); - difference.putAll(left); - difference.putAll(right); - difference.entrySet().removeAll(right.entrySet()); - return difference; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionStatus.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionStatus.java deleted file mode 100644 index 023e8de52..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionStatus.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.deprecated.subscriptions; - - -public enum SubscriptionStatus { - ACCEPTED("ACCEPTED"), - REJECTED("REJECTED"), - PENDING("PENDING"); - - private final String subscriptionStatusValue; - - SubscriptionStatus(final String subscriptionStatusValue) { - this.subscriptionStatusValue = subscriptionStatusValue; - } - - /** - * Finds the value of the given enum. - * - * @param statusValue value of the enum - * @return a SubscriptionStatus - */ - public static SubscriptionStatus fromString(final String statusValue) { - for (final SubscriptionStatus subscriptionStatusType : SubscriptionStatus.values()) { - if (subscriptionStatusType.subscriptionStatusValue.equalsIgnoreCase(statusValue)) { - return subscriptionStatusType; - } - } - return null; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumer.java deleted file mode 100644 index 307940c19..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumer.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; - -import com.hazelcast.map.IMap; -import io.cloudevents.CloudEvent; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.onap.cps.ncmp.api.impl.config.embeddedcache.ForwardedSubscriptionEventCacheConfig; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence; -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.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -@RequiredArgsConstructor -public class CmSubscriptionDmiOutEventConsumer { - - private final IMap> forwardedSubscriptionEventCache; - private final SubscriptionPersistence subscriptionPersistence; - private final CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper - cmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper; - private final CmSubscriptionNcmpOutEventPublisher cmSubscriptionNcmpOutEventPublisher; - - @Value("${notification.enabled:true}") - private boolean notificationFeatureEnabled; - - @Value("${ncmp.model-loader.subscription:false}") - private boolean subscriptionModelLoaderEnabled; - - /** - * Consume subscription response event. - * - * @param cmSubscriptionDmiOutConsumerRecord the event to be consumed - */ - @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}", - containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory") - public void consumeDmiOutEvent( - final ConsumerRecord cmSubscriptionDmiOutConsumerRecord) { - final CloudEvent cloudEvent = cmSubscriptionDmiOutConsumerRecord.value(); - final String eventType = cmSubscriptionDmiOutConsumerRecord.value().getType(); - final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent = - toTargetEvent(cloudEvent, CmSubscriptionDmiOutEvent.class); - final String clientId = cmSubscriptionDmiOutEvent.getData().getClientId(); - log.info("subscription event response of clientId: {} is received.", clientId); - final String subscriptionName = cmSubscriptionDmiOutEvent.getData().getSubscriptionName(); - final String subscriptionEventId = clientId + subscriptionName; - boolean createOutcomeResponse = true; - if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) { - final Set dmiNames = forwardedSubscriptionEventCache.get(subscriptionEventId); - dmiNames.remove(cmSubscriptionDmiOutEvent.getData().getDmiName()); - forwardedSubscriptionEventCache.put(subscriptionEventId, dmiNames, - ForwardedSubscriptionEventCacheConfig.SUBSCRIPTION_FORWARD_STARTED_TTL_SECS, TimeUnit.SECONDS); - createOutcomeResponse = forwardedSubscriptionEventCache.get(subscriptionEventId).isEmpty(); - } - if (subscriptionModelLoaderEnabled) { - updateSubscriptionEvent(cmSubscriptionDmiOutEvent); - } - if (createOutcomeResponse - && notificationFeatureEnabled) { - - final CmSubscriptionEvent cmSubscriptionEvent = new CmSubscriptionEvent(); - cmSubscriptionEvent.setClientId(cmSubscriptionDmiOutEvent.getData().getClientId()); - cmSubscriptionEvent.setSubscriptionName(cmSubscriptionDmiOutEvent.getData().getSubscriptionName()); - - cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent, eventType); - forwardedSubscriptionEventCache.remove(subscriptionEventId); - } - } - - private void updateSubscriptionEvent(final CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent) { - final YangModelSubscriptionEvent yangModelSubscriptionEvent = - cmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper - .toYangModelSubscriptionEvent(cmSubscriptionDmiOutEvent); - subscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent); - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper.java deleted file mode 100644 index e2999950e..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import java.util.List; -import java.util.stream.Collectors; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Named; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent; -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; - -@Mapper(componentModel = "spring") -public interface CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper { - - @Mapping(source = "data.clientId", target = "clientId") - @Mapping(source = "data.subscriptionName", target = "subscriptionName") - @Mapping(source = "data.subscriptionStatus", target = "predicates.targetCmHandles", - qualifiedByName = "mapSubscriptionStatusToCmHandleTargets") - YangModelSubscriptionEvent toYangModelSubscriptionEvent( - CmSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent); - - /** - * Maps SubscriptionStatus to list of TargetCmHandle. - * - * @param subscriptionStatus as a list - * @return TargetCmHandle list - */ - @Named("mapSubscriptionStatusToCmHandleTargets") - default List mapSubscriptionStatusToCmHandleTargets( - List subscriptionStatus) { - return subscriptionStatus.stream().map(status -> new YangModelSubscriptionEvent.TargetCmHandle(status.getId(), - org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus.fromString( - status.getStatus().value()), - status.getDetails())).collect(Collectors.toList()); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java deleted file mode 100644 index 0de04ade7..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Named; -import org.onap.cps.ncmp.api.impl.deprecated.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 CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper { - - @Mapping(source = "cmSubscriptionStatus", target = "data.additionalInfo", - qualifiedByName = "mapCmSubscriptionStatusToAdditionalInfo") - CmSubscriptionNcmpOutEvent toCmSubscriptionNcmpOutEvent(CmSubscriptionEvent cmSubscriptionEvent); - - /** - * Maps list of SubscriptionStatus to an AdditionalInfo. - * - * @param cmSubscriptionStatusList containing details - * @return an AdditionalInfo - */ - @Named("mapCmSubscriptionStatusToAdditionalInfo") - default AdditionalInfo mapCmSubscriptionStatusToAdditionalInfo( - final List cmSubscriptionStatusList) { - if (cmSubscriptionStatusList == null || cmSubscriptionStatusList.isEmpty()) { - throw new DataValidationException("Invalid cmSubscriptionStatusList", - "CmSubscriptionStatus list cannot be null or empty"); - } - - final Map> rejectedSubscriptionsPerDetails = - getSubscriptionsPerDetails(cmSubscriptionStatusList, SubscriptionStatus.REJECTED); - final Map> rejectedCmHandlesPerDetails = - getCmHandlesPerDetails(rejectedSubscriptionsPerDetails); - final List rejectedCmHandles = getAdditionalInfoDetailList(rejectedCmHandlesPerDetails); - - final Map> pendingSubscriptionsPerDetails = - getSubscriptionsPerDetails(cmSubscriptionStatusList, SubscriptionStatus.PENDING); - final Map> pendingCmHandlesPerDetails = - getCmHandlesPerDetails(pendingSubscriptionsPerDetails); - final List pendingCmHandles = getAdditionalInfoDetailList(pendingCmHandlesPerDetails); - - final AdditionalInfo additionalInfo = new AdditionalInfo(); - additionalInfo.setRejected(rejectedCmHandles); - additionalInfo.setPending(pendingCmHandles); - - return additionalInfo; - } - - private static Map> getSubscriptionsPerDetails( - final List cmSubscriptionStatusList, final SubscriptionStatus status) { - return cmSubscriptionStatusList.stream() - .filter(subscriptionStatus -> subscriptionStatus.getStatus() == status) - .collect(Collectors.groupingBy(CmSubscriptionStatus::getDetails)); - } - - private static Map> getCmHandlesPerDetails( - final Map> cmSubscriptionsPerDetails) { - return cmSubscriptionsPerDetails.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().stream() - .map(CmSubscriptionStatus::getId) - .collect(Collectors.toList()) - )); - } - - private static List getAdditionalInfoDetailList( - final Map> cmHandlesPerDetails) { - return cmHandlesPerDetails.entrySet().stream() - .map(entry -> { - final AdditionalInfoDetail detail = new AdditionalInfoDetail(); - detail.setDetails(entry.getKey()); - detail.setTargets(entry.getValue()); - return detail; - }).collect(Collectors.toList()); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumer.java deleted file mode 100644 index 7227af91a..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumer.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL; -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; - -import io.cloudevents.CloudEvent; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; - - -@Component -@Slf4j -@RequiredArgsConstructor -public class CmSubscriptionNcmpInEventConsumer { - - private final CmSubscriptionNcmpInEventForwarder cmSubscriptionNcmpInEventForwarder; - private final CmSubscriptionNcmpInEventMapper cmSubscriptionNcmpInEventMapper; - private final SubscriptionPersistence subscriptionPersistence; - - @Value("${notification.enabled:true}") - private boolean notificationFeatureEnabled; - - @Value("${ncmp.model-loader.subscription:false}") - private boolean subscriptionModelLoaderEnabled; - - /** - * Consume the specified event. - * - * @param subscriptionEventConsumerRecord the event to be consumed - */ - @KafkaListener(topics = "${app.ncmp.avc.subscription-topic}", - containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory") - public void consumeSubscriptionEvent(final ConsumerRecord subscriptionEventConsumerRecord) { - final CloudEvent cloudEvent = subscriptionEventConsumerRecord.value(); - final String eventType = subscriptionEventConsumerRecord.value().getType(); - final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent = - toTargetEvent(cloudEvent, CmSubscriptionNcmpInEvent.class); - final String eventDatastore = cmSubscriptionNcmpInEvent.getData().getPredicates().getDatastore(); - if (!eventDatastore.equals(PASSTHROUGH_RUNNING.getDatastoreName()) || eventDatastore.equals( - PASSTHROUGH_OPERATIONAL.getDatastoreName())) { - throw new UnsupportedOperationException( - "passthrough datastores are currently only supported for event subscriptions"); - } - if ("CM".equals(cmSubscriptionNcmpInEvent.getData().getDataType().getDataCategory())) { - if (subscriptionModelLoaderEnabled) { - persistSubscriptionEvent(cmSubscriptionNcmpInEvent); - } - if ("subscriptionCreated".equals(cloudEvent.getType())) { - log.info("Subscription for ClientID {} with name {} ...", - cmSubscriptionNcmpInEvent.getData().getSubscription().getClientID(), - cmSubscriptionNcmpInEvent.getData().getSubscription().getName()); - if (notificationFeatureEnabled) { - cmSubscriptionNcmpInEventForwarder.forwardCreateSubscriptionEvent(cmSubscriptionNcmpInEvent, - eventType); - } - } - } else { - log.trace("Non-CM subscription event ignored"); - } - } - - private void persistSubscriptionEvent(final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent) { - final YangModelSubscriptionEvent yangModelSubscriptionEvent = - cmSubscriptionNcmpInEventMapper.toYangModelSubscriptionEvent(cmSubscriptionNcmpInEvent); - subscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent); - } - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarder.java deleted file mode 100644 index ae192c439..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarder.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import com.hazelcast.map.IMap; -import io.cloudevents.CloudEvent; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.config.embeddedcache.ForwardedSubscriptionEventCacheConfig; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus; -import org.onap.cps.ncmp.api.impl.events.EventsPublisher; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.utils.CmSubscriptionEventCloudMapper; -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.models.CmSubscriptionEvent; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent; -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; -import org.springframework.stereotype.Component; - - -@Component -@Slf4j -@RequiredArgsConstructor -public class CmSubscriptionNcmpInEventForwarder { - - private static final Pattern REGEX_TO_EXTRACT_DOMAIN_FROM_URL_EXCLUDING_PORT = - Pattern.compile("http[s]?:\\/\\/(?:www\\.)?([^\\/:]+):{0,1}[0-9]{0,5}"); - - private final InventoryPersistence inventoryPersistence; - private final EventsPublisher eventsPublisher; - private final IMap> forwardedSubscriptionEventCache; - private final CmSubscriptionNcmpOutEventPublisher cmSubscriptionNcmpOutEventPublisher; - private final CmSubscriptionNcmpInEventMapper cmSubscriptionNcmpInEventMapper; - private final CmSubscriptionEventCloudMapper cmSubscriptionEventCloudMapper; - private final CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper - cmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper; - private final SubscriptionPersistence subscriptionPersistence; - private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - @Value("${app.ncmp.avc.subscription-forward-topic-prefix}") - private String dmiAvcSubscriptionTopicPrefix; - - @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms:30000}") - private int dmiResponseTimeoutInMs; - - /** - * Forward subscription event. - * - * @param cmSubscriptionNcmpInEvent the event to be forwarded - */ - public void forwardCreateSubscriptionEvent(final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent, - final String eventType) { - final List cmHandleTargets = cmSubscriptionNcmpInEvent.getData().getPredicates().getTargets(); - if (cmHandleTargets == null || cmHandleTargets.isEmpty() || cmHandleTargets.stream() - .anyMatch(id -> (id).contains("*"))) { - throw new UnsupportedOperationException( - "CMHandle targets are required. \"Wildcard\" operations are not yet supported"); - } - final Collection yangModelCmHandles = - inventoryPersistence.getYangModelCmHandles(cmHandleTargets); - final Map>> dmiPropertiesPerCmHandleIdPerServiceName = - DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); - findDmisAndRespond(cmSubscriptionNcmpInEvent, eventType, cmHandleTargets, - dmiPropertiesPerCmHandleIdPerServiceName); - } - - private void findDmisAndRespond(final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent, final String eventType, - final List cmHandleTargetsAsStrings, - final Map>> dmiPropertiesPerCmHandleIdPerServiceName) { - - final CmSubscriptionEvent cmSubscriptionEvent = new CmSubscriptionEvent(); - cmSubscriptionEvent.setSubscriptionName(cmSubscriptionNcmpInEvent.getData().getSubscription().getName()); - cmSubscriptionEvent.setClientId(cmSubscriptionNcmpInEvent.getData().getSubscription().getClientID()); - - final List cmHandlesThatExistsInDb = - dmiPropertiesPerCmHandleIdPerServiceName.entrySet().stream().map(Map.Entry::getValue).map(Map::keySet) - .flatMap(Set::stream).collect(Collectors.toList()); - - final List targetCmHandlesDoesNotExistInDb = new ArrayList<>(cmHandleTargetsAsStrings); - targetCmHandlesDoesNotExistInDb.removeAll(cmHandlesThatExistsInDb); - - final Set dmisToRespond = new HashSet<>(dmiPropertiesPerCmHandleIdPerServiceName.keySet()); - - if (dmisToRespond.isEmpty() || !targetCmHandlesDoesNotExistInDb.isEmpty()) { - updatesCmHandlesToRejectedAndPersistSubscriptionEvent(cmSubscriptionNcmpInEvent, - targetCmHandlesDoesNotExistInDb); - } - if (dmisToRespond.isEmpty()) { - cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent, - "subscriptionCreatedStatus"); - } else { - startResponseTimeout(cmSubscriptionEvent, dmisToRespond); - final CmSubscriptionDmiInEvent cmSubscriptionDmiInEvent = - cmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper.toCmSubscriptionDmiInEvent( - cmSubscriptionNcmpInEvent); - forwardEventToDmis(dmiPropertiesPerCmHandleIdPerServiceName, cmSubscriptionDmiInEvent, eventType); - } - } - - private void startResponseTimeout(final CmSubscriptionEvent cmSubscriptionEvent, - final Set dmisToRespond) { - 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, - cmSubscriptionEvent); - - executorService.schedule(responseTimeoutTask, dmiResponseTimeoutInMs, TimeUnit.MILLISECONDS); - } - - private void forwardEventToDmis(final Map>> dmiNameCmHandleMap, - final CmSubscriptionDmiInEvent cmSubscriptionDmiInEvent, final String eventType) { - dmiNameCmHandleMap.forEach((dmiName, cmHandlePropertiesMap) -> { - final List cmHandleTargets = cmHandlePropertiesMap.entrySet().stream().map( - cmHandleAndProperties -> { - final CmHandle cmHandle = new CmHandle(); - cmHandle.setId(cmHandleAndProperties.getKey()); - cmHandle.setAdditionalProperties(cmHandleAndProperties.getValue()); - return cmHandle; - }).collect(Collectors.toList()); - - cmSubscriptionDmiInEvent.getData().getPredicates().setTargets(cmHandleTargets); - final String dmiNameSuffix = toValidTopicSuffix(dmiName); - final String eventKey = createEventKey(cmSubscriptionDmiInEvent, dmiNameSuffix); - final String dmiAvcSubscriptionTopic = dmiAvcSubscriptionTopicPrefix + dmiNameSuffix; - - final CloudEvent cmSubscriptionDmiInCloudEvent = - cmSubscriptionEventCloudMapper.toCloudEvent(cmSubscriptionDmiInEvent, eventKey, eventType); - eventsPublisher.publishCloudEvent(dmiAvcSubscriptionTopic, eventKey, cmSubscriptionDmiInCloudEvent); - }); - } - - private String createEventKey(final CmSubscriptionDmiInEvent cmSubscriptionDmiInEvent, final String dmiName) { - return cmSubscriptionDmiInEvent.getData().getSubscription().getClientID() + "-" - + cmSubscriptionDmiInEvent.getData().getSubscription().getName() + "-" + dmiName; - } - - private void updatesCmHandlesToRejectedAndPersistSubscriptionEvent( - final CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent, - final List targetCmHandlesDoesNotExistInDb) { - final YangModelSubscriptionEvent yangModelSubscriptionEvent = - cmSubscriptionNcmpInEventMapper.toYangModelSubscriptionEvent(cmSubscriptionNcmpInEvent); - yangModelSubscriptionEvent.getPredicates() - .setTargetCmHandles(findRejectedCmHandles(targetCmHandlesDoesNotExistInDb, yangModelSubscriptionEvent)); - subscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent); - } - - private static List findRejectedCmHandles( - final List targetCmHandlesDoesNotExistInDb, - final YangModelSubscriptionEvent yangModelSubscriptionEvent) { - return yangModelSubscriptionEvent.getPredicates().getTargetCmHandles().stream() - .filter(targetCmHandle -> targetCmHandlesDoesNotExistInDb.contains(targetCmHandle.getCmHandleId())) - .map(target -> new YangModelSubscriptionEvent.TargetCmHandle(target.getCmHandleId(), - SubscriptionStatus.REJECTED, "Targets not found")) - .collect(Collectors.toList()); - } - - /* - CPS-1979 : DmiName can be a URL , which is not a valid topic name. - Hence just taking the domain name(excluding port) information to be part of the topic name. - */ - private String toValidTopicSuffix(final String dmiName) { - final Matcher matcher = REGEX_TO_EXTRACT_DOMAIN_FROM_URL_EXCLUDING_PORT.matcher(dmiName); - return matcher.find() ? matcher.group(1) : dmiName; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapper.java deleted file mode 100644 index 852d5510c..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapper.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import java.util.List; -import java.util.stream.Collectors; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Named; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent; - -@Mapper(componentModel = "spring") -public interface CmSubscriptionNcmpInEventMapper { - - @Mapping(source = "data.subscription.clientID", target = "clientId") - @Mapping(source = "data.subscription.name", target = "subscriptionName") - @Mapping(source = "data.predicates.targets", target = "predicates.targetCmHandles", - qualifiedByName = "mapTargetsToCmHandleTargets") - @Mapping(source = "data.predicates.datastore", target = "predicates.datastore") - YangModelSubscriptionEvent toYangModelSubscriptionEvent(CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent); - - /** - * Maps list of Targets to list of TargetCmHandle. - * - * @param targets list of objects - * @return TargetCmHandle list - */ - @Named("mapTargetsToCmHandleTargets") - default List mapTargetsToCmHandleTargets(List targets) { - return targets.stream().map(YangModelSubscriptionEvent.TargetCmHandle::new) - .collect(Collectors.toList()); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper.java deleted file mode 100644 index a87508ce5..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent; - -@Mapper(componentModel = "spring") -public interface CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper { - - @Mapping(target = "data.predicates.targets", ignore = true) - CmSubscriptionDmiInEvent toCmSubscriptionDmiInEvent( - CmSubscriptionNcmpInEvent cmSubscriptionNcmpInEvent); - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java deleted file mode 100644 index 8a3c44da4..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisher.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.PARTIALLY_APPLIED_SUBSCRIPTION; -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUBSCRIPTION_NOT_APPLICABLE; -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUBSCRIPTION_PENDING; -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUCCESSFULLY_APPLIED_SUBSCRIPTION; -import static org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus.ACCEPTED; -import static org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus.PENDING; -import static org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus.REJECTED; - -import io.cloudevents.CloudEvent; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NcmpResponseStatus; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus; -import org.onap.cps.ncmp.api.impl.events.EventsPublisher; -import org.onap.cps.ncmp.api.impl.utils.DataNodeHelper; -import org.onap.cps.ncmp.api.impl.utils.SubscriptionOutcomeCloudMapper; -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; - -@Component -@Slf4j -@RequiredArgsConstructor -public class CmSubscriptionNcmpOutEventPublisher { - - private final SubscriptionPersistence subscriptionPersistence; - - private final EventsPublisher outcomeEventsPublisher; - - private final CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper - cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper; - - private final SubscriptionOutcomeCloudMapper subscriptionOutcomeCloudMapper; - - @Value("${app.ncmp.avc.subscription-outcome-topic:subscription-response}") - private String subscriptionOutcomeEventTopic; - - /** - * This is for construction of outcome message to be published for client apps. - * - * @param cmSubscriptionEvent event produced by Dmi Plugin - */ - public void sendResponse(final CmSubscriptionEvent cmSubscriptionEvent, final String eventType) { - final CmSubscriptionNcmpOutEvent cmSubscriptionNcmpOutEvent = - formCmSubscriptionNcmpOutEvent(cmSubscriptionEvent); - final String subscriptionClientId = cmSubscriptionEvent.getClientId(); - final String subscriptionName = cmSubscriptionEvent.getSubscriptionName(); - final String subscriptionEventId = subscriptionClientId + subscriptionName; - final CloudEvent subscriptionOutcomeCloudEvent = - subscriptionOutcomeCloudMapper.toCloudEvent(cmSubscriptionNcmpOutEvent, - subscriptionEventId, eventType); - outcomeEventsPublisher.publishCloudEvent(subscriptionOutcomeEventTopic, - subscriptionEventId, subscriptionOutcomeCloudEvent); - } - - private CmSubscriptionNcmpOutEvent formCmSubscriptionNcmpOutEvent( - final CmSubscriptionEvent cmSubscriptionEvent) { - final Map> cmHandleIdToStatusAndDetailsAsMap = - DataNodeHelper.cmHandleIdToStatusAndDetailsAsMapFromDataNode( - subscriptionPersistence.getCmHandlesForSubscriptionEvent( - cmSubscriptionEvent.getClientId(), - cmSubscriptionEvent.getSubscriptionName())); - final List cmSubscriptionStatusList = - mapCmHandleIdStatusDetailsMapToSubscriptionStatusList(cmHandleIdToStatusAndDetailsAsMap); - cmSubscriptionEvent.setCmSubscriptionStatus(cmSubscriptionStatusList); - return fromCmSubscriptionEvent(cmSubscriptionEvent, - decideOnNcmpEventResponseCodeForSubscription(cmHandleIdToStatusAndDetailsAsMap)); - } - - private static List mapCmHandleIdStatusDetailsMapToSubscriptionStatusList( - final Map> cmHandleIdToStatusAndDetailsAsMap) { - return cmHandleIdToStatusAndDetailsAsMap.entrySet() - .stream().map(entryset -> { - final CmSubscriptionStatus cmSubscriptionStatus = new CmSubscriptionStatus(); - final String cmHandleId = entryset.getKey(); - final Map statusAndDetailsMap = entryset.getValue(); - final String status = statusAndDetailsMap.get("status"); - final String details = statusAndDetailsMap.get("details"); - cmSubscriptionStatus.setId(cmHandleId); - cmSubscriptionStatus.setStatus(SubscriptionStatus.fromString(status)); - cmSubscriptionStatus.setDetails(details); - return cmSubscriptionStatus; - }).toList(); - } - - private NcmpResponseStatus decideOnNcmpEventResponseCodeForSubscription( - final Map> cmHandleIdToStatusAndDetailsAsMap) { - - if (allTargetsHaveStatus(cmHandleIdToStatusAndDetailsAsMap, ACCEPTED)) { - return SUCCESSFULLY_APPLIED_SUBSCRIPTION; - } - if (allTargetsHaveStatus(cmHandleIdToStatusAndDetailsAsMap, REJECTED)) { - return SUBSCRIPTION_NOT_APPLICABLE; - } - if (allTargetsHaveStatus(cmHandleIdToStatusAndDetailsAsMap, PENDING)) { - return SUBSCRIPTION_PENDING; - } - return PARTIALLY_APPLIED_SUBSCRIPTION; - } - - private boolean allTargetsHaveStatus(final Map> cmHandleIdToStatusAndDetailsAsMap, - final SubscriptionStatus subscriptionStatus) { - return cmHandleIdToStatusAndDetailsAsMap.values().stream() - .allMatch(entryset -> entryset.containsValue(subscriptionStatus.toString())); - } - - private CmSubscriptionNcmpOutEvent fromCmSubscriptionEvent( - final CmSubscriptionEvent cmSubscriptionEvent, - final NcmpResponseStatus ncmpResponseStatus) { - - final CmSubscriptionNcmpOutEvent cmSubscriptionNcmpOutEvent = - cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper.toCmSubscriptionNcmpOutEvent( - cmSubscriptionEvent); - cmSubscriptionNcmpOutEvent.getData().setStatusCode(Integer.parseInt(ncmpResponseStatus.getCode())); - cmSubscriptionNcmpOutEvent.getData().setStatusMessage(ncmpResponseStatus.getMessage()); - - return cmSubscriptionNcmpOutEvent; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ResponseTimeoutTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ResponseTimeoutTask.java deleted file mode 100644 index 8832ca3d0..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ResponseTimeoutTask.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription; - -import com.hazelcast.map.IMap; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.models.CmSubscriptionEvent; - -@Slf4j -@RequiredArgsConstructor -public class ResponseTimeoutTask implements Runnable { - - private final IMap> forwardedSubscriptionEventCache; - private final CmSubscriptionNcmpOutEventPublisher cmSubscriptionNcmpOutEventPublisher; - private final CmSubscriptionEvent cmSubscriptionEvent; - - @Override - public void run() { - generateTimeoutResponse(); - } - - private void generateTimeoutResponse() { - final String subscriptionClientId = cmSubscriptionEvent.getClientId(); - final String subscriptionName = cmSubscriptionEvent.getSubscriptionName(); - final String subscriptionEventId = subscriptionClientId + subscriptionName; - if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) { - cmSubscriptionNcmpOutEventPublisher.sendResponse(cmSubscriptionEvent, - "subscriptionCreatedStatus"); - forwardedSubscriptionEventCache.remove(subscriptionEventId); - } - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmSubscriptionEventCloudMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmSubscriptionEventCloudMapper.java deleted file mode 100644 index 8e565d220..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmSubscriptionEventCloudMapper.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.utils; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; -import java.net.URI; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class CmSubscriptionEventCloudMapper { - - private final ObjectMapper objectMapper; - - private static String randomId = UUID.randomUUID().toString(); - - /** - * Maps CmSubscriptionDmiInEvent to a CloudEvent. - * - * @param cmSubscriptionDmiInEvent object. - * @param eventKey as String. - * @return CloudEvent built. - */ - public CloudEvent toCloudEvent(final CmSubscriptionDmiInEvent cmSubscriptionDmiInEvent, final String eventKey, - final String eventType) { - try { - return CloudEventBuilder.v1().withId(randomId) - .withSource(URI.create(cmSubscriptionDmiInEvent.getData().getSubscription().getClientID())) - .withType(eventType).withExtension("correlationid", eventKey) - .withDataSchema(URI.create("urn:cps:" + CmSubscriptionDmiInEvent.class.getName() + ":1.0.0")) - .withData(objectMapper.writeValueAsBytes(cmSubscriptionDmiInEvent)).build(); - } catch (final JsonProcessingException jsonProcessingException) { - log.error("The Cloud Event could not be constructed", jsonProcessingException); - } - return null; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java deleted file mode 100644 index af629a6bd..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.utils; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; -import java.net.URI; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class SubscriptionOutcomeCloudMapper { - - private final ObjectMapper objectMapper; - - private static String randomId = UUID.randomUUID().toString(); - - /** - * Maps CmSubscriptionNcmpOutEvent to a CloudEvent. - * - * @param cmSubscriptionNcmpOutEvent object - * @return CloudEvent - */ - public CloudEvent toCloudEvent(final CmSubscriptionNcmpOutEvent cmSubscriptionNcmpOutEvent, - final String eventKey, final String eventType) { - try { - return CloudEventBuilder.v1() - .withId(randomId) - .withSource(URI.create("NCMP")) - .withType(eventType) - .withExtension("correlationid", eventKey) - .withDataSchema(URI.create("urn:cps:" + CmSubscriptionNcmpOutEvent.class.getName() + ":1.0.0")) - .withData(objectMapper.writeValueAsBytes(cmSubscriptionNcmpOutEvent)).build(); - } catch (final JsonProcessingException jsonProcessingException) { - log.error("The Cloud Event could not be constructed", jsonProcessingException); - } - return null; - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelSubscriptionEvent.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelSubscriptionEvent.java deleted file mode 100644 index a6cfa7bb7..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelSubscriptionEvent.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - - -package org.onap.cps.ncmp.api.impl.yangmodels; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus; - -/** - * Subscription event model to persist data into DB. - * Yang model subscription event - */ -@Getter -@Setter -@NoArgsConstructor -@JsonInclude(Include.NON_NULL) -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -public class YangModelSubscriptionEvent { - - @EqualsAndHashCode.Include - @JsonProperty("clientID") - private String clientId; - - @EqualsAndHashCode.Include - @JsonProperty("subscriptionName") - private String subscriptionName; - - private String topic; - - @JsonProperty("isTagged") - private boolean isTagged; - - private Predicates predicates; - - - @Data - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class Predicates { - - private String datastore; - - private List targetCmHandles; - - } - - @AllArgsConstructor - @Data - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class TargetCmHandle { - - @JsonProperty() - private final String cmHandleId; - - @JsonProperty() - private final SubscriptionStatus status; - - @JsonProperty() - private final String details; - - /** - * Constructor with single parameter for TargetCmHandle. - * - * @param cmHandleId as cm handle id - */ - public TargetCmHandle(final String cmHandleId) { - this.cmHandleId = cmHandleId; - this.status = SubscriptionStatus.PENDING; - this.details = "Subscription forwarded to dmi plugin"; - } - } -} - - 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 deleted file mode 100644 index c20696a8b..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.models; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import java.util.ArrayList; -import java.util.List; -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 = 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 deleted file mode 100644 index c56912376..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmSubscriptionStatus.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.models; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; -import org.onap.cps.ncmp.api.impl.deprecated.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/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java deleted file mode 100644 index cabd8683f..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.models; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Map; -import lombok.Getter; -import lombok.Setter; -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@Getter -@Setter -public class SubscriptionEventResponse { - @JsonProperty("clientId") - private String clientId; - @JsonProperty("subscriptionName") - private String subscriptionName; - @JsonProperty("dmiName") - private String dmiName; - @JsonProperty("cmHandleIdToStatus") - private Map cmHandleIdToStatus; -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java index bd8dec4dc..a3ebec3c5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,8 +47,8 @@ abstract class AbstractModelLoader implements ModelLoader { private final CpsDataspaceService cpsDataspaceService; private final CpsModuleService cpsModuleService; - private final CpsDataService cpsDataService; private final CpsAnchorService cpsAnchorService; + protected final CpsDataService cpsDataService; private static final int EXIT_CODE_ON_ERROR = 1; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java index c0f0279ad..88ba5e91b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,16 @@ package org.onap.cps.ncmp.init; import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME; +import static org.onap.cps.utils.ContentType.JSON; +import java.time.OffsetDateTime; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDataspaceService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -38,19 +42,14 @@ public class CmDataSubscriptionModelLoader extends AbstractModelLoader { private static final String SCHEMASET_NAME = "cm-data-subscriptions"; private static final String ANCHOR_NAME = "cm-data-subscriptions"; private static final String REGISTRY_DATANODE_NAME = "datastores"; - - private static final String DEPRECATED_MODEL_FILENAME = "subscription.yang"; - private static final String DEPRECATED_ANCHOR_NAME = "AVC-Subscriptions"; - private static final String DEPRECATED_SCHEMASET_NAME = "subscriptions"; - private static final String DEPRECATED_REGISTRY_DATANODE_NAME = "subscription-registry"; - - + private static final String DATASTORE_PASSTHROUGH_OPERATIONAL = "ncmp-datastores:passthrough-operational"; + private static final String DATASTORE_PASSTHROUGH_RUNNING = "ncmp-datastores:passthrough-running"; public CmDataSubscriptionModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService, - final CpsAnchorService cpsAnchorService) { - super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService); + final CpsAnchorService cpsAnchorService, + final CpsDataService cpsDataService) { + super(cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService); } @Value("${ncmp.model-loader.subscription:true}") @@ -68,12 +67,26 @@ public class CmDataSubscriptionModelLoader extends AbstractModelLoader { } private void onboardSubscriptionModels() { - createSchemaSet(NCMP_DATASPACE_NAME, DEPRECATED_SCHEMASET_NAME, DEPRECATED_MODEL_FILENAME); - createAnchor(NCMP_DATASPACE_NAME, DEPRECATED_SCHEMASET_NAME, DEPRECATED_ANCHOR_NAME); - createTopLevelDataNode(NCMP_DATASPACE_NAME, DEPRECATED_ANCHOR_NAME, DEPRECATED_REGISTRY_DATANODE_NAME); - createSchemaSet(NCMP_DATASPACE_NAME, SCHEMASET_NAME, MODEL_FILENAME); createAnchor(NCMP_DATASPACE_NAME, SCHEMASET_NAME, ANCHOR_NAME); createTopLevelDataNode(NCMP_DATASPACE_NAME, ANCHOR_NAME, REGISTRY_DATANODE_NAME); + createDatastore(DATASTORE_PASSTHROUGH_OPERATIONAL, DATASTORE_PASSTHROUGH_RUNNING); } + + private void createDatastore(final String... datastoreNames) { + for (final String datastoreName : datastoreNames) { + final String nodeData = "{\"datastore\":[{\"name\":\"" + datastoreName + "\",\"cm-handles\":{}}]}"; + try { + cpsDataService.saveData(NCMP_DATASPACE_NAME, ANCHOR_NAME, "/" + REGISTRY_DATANODE_NAME, nodeData, + OffsetDateTime.now(), JSON); + } catch (final AlreadyDefinedException exception) { + log.info("Creating new child data node '{}' for data node '{}' failed as data node already exists", + datastoreName, REGISTRY_DATANODE_NAME); + } catch (final Exception exception) { + log.error("Creating data node failed: {}", exception.getMessage()); + throw new NcmpStartUpException("Creating data node failed", exception.getMessage()); + } + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java index 0e562cdd8..01bfc2b5d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,9 +39,9 @@ public class InventoryModelLoader extends AbstractModelLoader { public InventoryModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService, - final CpsAnchorService cpsAnchorService) { - super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService); + final CpsAnchorService cpsAnchorService, + final CpsDataService cpsDataService) { + super(cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService); } @Override diff --git a/cps-ncmp-service/src/main/resources/models/subscription.yang b/cps-ncmp-service/src/main/resources/models/subscription.yang deleted file mode 100644 index 7096c18ab..000000000 --- a/cps-ncmp-service/src/main/resources/models/subscription.yang +++ /dev/null @@ -1,57 +0,0 @@ -module subscription { - yang-version 1.1; - namespace "org:onap:ncmp:subscription"; - - prefix subs; - - revision "2023-03-21" { - description - "NCMP subscription model"; - } - - container subscription-registry { - list subscription { - key "clientID subscriptionName"; - - leaf clientID { - type string; - } - - leaf subscriptionName { - type string; - } - - leaf topic { - type string; - } - - leaf isTagged { - type boolean; - } - - container predicates { - - list targetCmHandles { - key "cmHandleId"; - - leaf cmHandleId { - type string; - } - - leaf status { - type string; - } - - leaf details { - type string; - } - } - - leaf datastore { - type string; - } - } - - } - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfigSpec.groovy deleted file mode 100644 index 879525e57..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ForwardedSubscriptionEventCacheConfigSpec.groovy +++ /dev/null @@ -1,77 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.config.embeddedcache - -import com.hazelcast.config.Config -import com.hazelcast.core.Hazelcast -import com.hazelcast.map.IMap -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [ForwardedSubscriptionEventCacheConfig]) -class ForwardedSubscriptionEventCacheConfigSpec extends Specification { - - @Autowired - private IMap> forwardedSubscriptionEventCache - - def 'Embedded (hazelcast) cache for Forwarded Subscription Event Cache.'() { - expect: 'system is able to create an instance of the Forwarded Subscription Event Cache' - assert null != forwardedSubscriptionEventCache - and: 'there is at least 1 instance' - assert Hazelcast.allHazelcastInstances.size() > 0 - and: 'Forwarded Subscription Event Cache is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceSubscriptionEvents') - } - - def 'Verify configs for Distributed Caches'(){ - given: 'the Forwarded Subscription Event Cache config' - def forwardedSubscriptionEventCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config - def forwardedSubscriptionEventCacheMapConfig = forwardedSubscriptionEventCacheConfig.mapConfigs.get('forwardedSubscriptionEventCacheMapConfig') - expect: 'system created instance with correct config' - assert forwardedSubscriptionEventCacheConfig.clusterName == 'cps-and-ncmp-test-caches' - assert forwardedSubscriptionEventCacheMapConfig.backupCount == 3 - assert forwardedSubscriptionEventCacheMapConfig.asyncBackupCount == 3 - } - - def 'Verify deployment network configs for Distributed Caches'() { - given: 'the Forwarded Subscription Event Cache config' - def forwardedSubscriptionEventCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config.networkConfig - expect: 'system created instance with correct config' - assert forwardedSubscriptionEventCacheNetworkConfig.join.autoDetectionConfig.enabled - assert !forwardedSubscriptionEventCacheNetworkConfig.join.kubernetesConfig.enabled - } - - def 'Verify network config'() { - given: 'Synchronization config object and test configuration' - def objectUnderTest = new ForwardedSubscriptionEventCacheConfig() - def testConfig = new Config() - when: 'kubernetes properties are enabled' - objectUnderTest.cacheKubernetesEnabled = true - objectUnderTest.cacheKubernetesServiceName = 'test-service-name' - and: 'method called to update the discovery mode' - objectUnderTest.updateDiscoveryMode(testConfig) - then: 'applied properties are reflected' - assert testConfig.networkConfig.join.kubernetesConfig.enabled - assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' - - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceSpec.groovy deleted file mode 100644 index da1a12235..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/deprecated/subscriptions/SubscriptionPersistenceSpec.groovy +++ /dev/null @@ -1,99 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.deprecated.subscriptions - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent -import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.api.CpsModuleService -import org.onap.cps.spi.utils.CpsValidator -import spock.lang.Specification - -class SubscriptionPersistenceSpec extends Specification { - - private static final String SUBSCRIPTION_ANCHOR_NAME = "AVC-Subscriptions"; - private static final String SUBSCRIPTION_REGISTRY_PARENT = "/subscription-registry"; - private static final String SUBSCRIPTION_REGISTRY_PREDICATES_XPATH = "/subscription-registry/subscription[@clientID='some-client-id' and @subscriptionName='some-subscription-name']/predicates"; - - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockCpsDataService = Mock(CpsDataService) - def mockCpsModuleService = Mock(CpsModuleService) - def mockCpsValidator = Mock(CpsValidator) - - def objectUnderTest = new SubscriptionPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, - mockCpsModuleService, mockCpsValidator) - - def predicates = new YangModelSubscriptionEvent.Predicates(datastore: 'some-datastore', - targetCmHandles: [new YangModelSubscriptionEvent.TargetCmHandle('cmhandle1'), - new YangModelSubscriptionEvent.TargetCmHandle('cmhandle2')]) - def yangModelSubscriptionEvent = new YangModelSubscriptionEvent(clientId: 'some-client-id', - subscriptionName: 'some-subscription-name', tagged: true, topic: 'some-topic', predicates: predicates) - - def 'save a subscription event as yang model into db for the #scenarios' () { - given: 'a blank data node that exist in db' - def blankDataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME) - .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry').build() - and: 'cps data service return an empty data node' - mockCpsDataService.getDataNodes(*_) >> [blankDataNode] - when: 'the yangModelSubscriptionEvent is saved into db' - objectUnderTest.saveSubscriptionEvent(yangModelSubscriptionEvent) - then: 'the cpsDataService save operation is called with the correct data' - 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - SUBSCRIPTION_REGISTRY_PARENT, - '{"subscription":[{' + - '"topic":"some-topic",' + - '"predicates":{"datastore":"some-datastore","targetCmHandles":[{"cmHandleId":"cmhandle1","status":"PENDING","details":"Subscription forwarded to dmi plugin"},' + - '{"cmHandleId":"cmhandle2","status":"PENDING","details":"Subscription forwarded to dmi plugin"}]},' + - '"clientID":"some-client-id","subscriptionName":"some-subscription-name","isTagged":true}]}', - NO_TIMESTAMP) - } - - def 'add or replace cm handle list element into db' () { - given: 'a data node with child node exist in db' - def leaves1 = [status:'REJECTED', cmHandleId:'cmhandle1', details:'Cm handle does not exist'] as Map - def childDataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME) - .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription') - .withLeaves(leaves1).build() - def engagedDataNode = new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME) - .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry') - .withChildDataNodes([childDataNode]).build() - and: 'cps data service return data node including a child data node' - mockCpsDataService.getDataNodes(*_) >> [engagedDataNode] - and: 'cps data service return data node for querying by xpaths' - mockCpsDataService.getDataNodesForMultipleXpaths(*_) >> [engagedDataNode] - when: 'the yang model subscription event is saved into db' - objectUnderTest.saveSubscriptionEvent(yangModelSubscriptionEvent) - then: 'the cpsDataService save non-existing cm handle with the correct data' - 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - SUBSCRIPTION_REGISTRY_PREDICATES_XPATH, '{"targetCmHandles":[{"cmHandleId":"cmhandle2","status":"PENDING","details":"Subscription forwarded to dmi plugin"}]}', - NO_TIMESTAMP) - and: 'the cpsDataService update existing cm handle with the correct data' - 1 * mockCpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, - SUBSCRIPTION_REGISTRY_PREDICATES_XPATH, '{"targetCmHandles":[{"cmHandleId":"cmhandle1","status":"PENDING","details":"Subscription forwarded to dmi plugin"}]}', - NO_TIMESTAMP) - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ClientCmSubscriptionNcmpInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ClientCmSubscriptionNcmpInEventMapperSpec.groovy deleted file mode 100644 index 468a402c0..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/ClientCmSubscriptionNcmpInEventMapperSpec.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import com.fasterxml.jackson.databind.ObjectMapper -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [JsonObjectMapper, ObjectMapper]) -class ClientCmSubscriptionNcmpInEventMapperSpec extends Specification { - - CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper) - - @Autowired - JsonObjectMapper jsonObjectMapper - - def 'Map clients subscription event to ncmps subscription event'() { - given: 'a Subscription Event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpInEvent.json') - def testEventToMap = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class) - when: 'the client event is mapped to a ncmp subscription event' - def result = objectUnderTest.toCmSubscriptionDmiInEvent(testEventToMap) - then: 'the resulting ncmp subscription event contains the correct clientId' - assert result.getData().getSubscription().getClientID() == "SCO-9989752" - and: 'subscription name' - assert result.getData().getSubscription().getName() == "cm-subscription-001" - and: 'is tagged value is false' - assert result.getData().getSubscription().getIsTagged() == false - and: 'data category is CM' - assert result.getData().getDataType().getDataCategory() == 'CM' - and: 'predicate targets is null' - assert result.getData().getPredicates().getTargets() == [] - and: 'datastore is passthrough-running' - assert result.getData().getPredicates().getDatastore() == 'ncmp-datastore:passthrough-running' - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy deleted file mode 100644 index 1d38d3f02..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventConsumerSpec.groovy +++ /dev/null @@ -1,140 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - -import com.fasterxml.jackson.databind.ObjectMapper -import com.hazelcast.map.IMap -import io.cloudevents.CloudEvent -import io.cloudevents.core.builder.CloudEventBuilder -import org.apache.kafka.clients.consumer.ConsumerRecord -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistenceImpl -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class CmSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpec { - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - ObjectMapper objectMapper - - IMap> mockForwardedSubscriptionEventCache = Mock(IMap>) - def mockSubscriptionPersistence = Mock(SubscriptionPersistenceImpl) - def mockSubscriptionEventResponseMapper = Mock(CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper) - def mockCmSubscriptionNcmpOutEventPublisher = Mock(CmSubscriptionNcmpOutEventPublisher) - - def objectUnderTest = new CmSubscriptionDmiOutEventConsumer(mockForwardedSubscriptionEventCache, - mockSubscriptionPersistence, mockSubscriptionEventResponseMapper, mockCmSubscriptionNcmpOutEventPublisher) - - 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.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) - and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed' - 1 * mockForwardedSubscriptionEventCache.get('SCO-9989752cm-subscription-001') >> ([] as Set) - and: 'the response event is map to yang model' - numberOfTimeToPersist * mockSubscriptionEventResponseMapper.toYangModelSubscriptionEvent(_) - and: 'the response event is persisted into the db' - numberOfTimeToPersist * mockSubscriptionPersistence.saveSubscriptionEvent(_) - and: 'the subscription event is removed from the map' - numberOfTimeToRemove * mockForwardedSubscriptionEventCache.remove('SCO-9989752cm-subscription-001') - and: 'a response outcome has been created' - 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 - 'Both model loader and notification are disabled' | false | false || 0 || 0 || 0 - 'Model loader enabled and notification disabled' | true | false || 1 || 0 || 0 - 'Model loader disabled and notification enabled' | false | true || 0 || 1 || 1 - } - - 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.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) - and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed' - 1 * mockForwardedSubscriptionEventCache.get('SCO-9989752cm-subscription-001') >> (['non-responded-dmi'] as Set) - and: 'the response event is map to yang model' - numberOfTimeToPersist * mockSubscriptionEventResponseMapper.toYangModelSubscriptionEvent(_) - and: 'the response event is persisted into the db' - numberOfTimeToPersist * mockSubscriptionPersistence.saveSubscriptionEvent(_) - and: 'the subscription event is removed from the map' - and: 'the subscription event is not removed from the map' - 0 * mockForwardedSubscriptionEventCache.remove(_) - and: 'a response outcome has not been created' - 0 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(*_) - where: 'the following values are used' - scenario | modelLoaderEnabled | notificationEnabled || numberOfTimeToPersist - 'Both model loader and notification are enabled' | true | true || 1 - 'Both model loader and notification are disabled' | false | false || 0 - 'Model loader enabled and notification disabled' | true | false || 1 - 'Model loader disabled and notification enabled' | false | true || 0 - } - - def getDmiOutEvent() { - def cmSubscriptionDmiOutEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionDmiOutEvent.json') - return jsonObjectMapper.convertJsonString(cmSubscriptionDmiOutEventJsonData, CmSubscriptionDmiOutEvent.class) - } - - def getCloudEvent() { - return CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(getDmiOutEvent())) - .withId('some-id') - .withType('subscriptionCreated') - .withSource(URI.create('NCMP')).build() - } - - def getDmiOutConsumerRecord() { - return new ConsumerRecord('topic-name', 0, 0, 'event-key', getCloudEvent()) - } - - def getDataNode() { - def leaves = [status:'ACCEPTED', cmHandleId:'cmhandle1'] as Map - return new DataNodeBuilder().withDataspace(NCMP_DATASPACE_NAME) - .withAnchor('AVC-Subscriptions').withXpath('/subscription-registry/subscription') - .withLeaves(leaves).build() - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy deleted file mode 100644 index 1c91cb5d5..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import com.fasterxml.jackson.databind.ObjectMapper -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus -import org.onap.cps.ncmp.events.cmsubscription1_0_0.dmi_to_ncmp.CmSubscriptionDmiOutEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - - -@SpringBootTest(classes = [JsonObjectMapper, ObjectMapper]) -class CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapperSpec extends Specification { - - CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionDmiOutEventToYangModelSubscriptionEventMapper) - - @Autowired - JsonObjectMapper jsonObjectMapper - - def 'Map dmi out event to yang model subscription event'() { - given: 'a dmi out event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionDmiOutEvent.json') - def testEventToMap = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionDmiOutEvent.class) - when: 'the event is mapped to a yang model subscription' - def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap) - then: 'the resulting yang model subscription event contains the correct clientId' - assert result.clientId == "SCO-9989752" - and: 'subscription name' - assert result.subscriptionName == "cm-subscription-001" - and: 'predicate targets cm handle size as expected' - assert result.predicates.targetCmHandles.size() == 2 - and: 'predicate targets cm handle ids as expected' - 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] - } - -} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy deleted file mode 100644 index 17b0984eb..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec.groovy +++ /dev/null @@ -1,89 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import com.fasterxml.jackson.databind.ObjectMapper -import org.mapstruct.factory.Mappers -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 -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - - -@SpringBootTest(classes = [JsonObjectMapper, ObjectMapper]) -class CmSubscriptionEventToCmSubscriptionNcmpOutEventMapperSpec extends Specification { - - CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper) - - @Autowired - JsonObjectMapper jsonObjectMapper - - def 'Map cm subscription event to ncmp out event'() { - given: 'a cm subscription event' - def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/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() == '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'] - assert rejectedCmHandleTargetsPerDetails.get(1).getDetails() == 'Some error message from the DMI' - assert rejectedCmHandleTargetsPerDetails.get(1).getTargets() == ['CMHandle1'] - } - - 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('deprecatedCmSubscription/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 == '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() - } - - def 'Map cm subscription event to ncmp out event without any exception'() { - given: 'a cm subscription Event' - def subscriptionResponseJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/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() - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumerSpec.groovy deleted file mode 100644 index 9484e19a4..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventConsumerSpec.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2022-2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.CloudEvent -import io.cloudevents.core.builder.CloudEventBuilder -import org.apache.kafka.clients.consumer.ConsumerRecord -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class CmSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpec { - - def mockCmSubscriptionNcmpInEventForwarder = Mock(CmSubscriptionNcmpInEventForwarder) - def mockCmSubscriptionNcmpInEventMapper = Mock(CmSubscriptionNcmpInEventMapper) - def mockSubscriptionPersistence = Mock(SubscriptionPersistence) - def objectUnderTest = new CmSubscriptionNcmpInEventConsumer(mockCmSubscriptionNcmpInEventForwarder, mockCmSubscriptionNcmpInEventMapper, mockSubscriptionPersistence) - - def yangModelSubscriptionEvent = new YangModelSubscriptionEvent() - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - ObjectMapper objectMapper - - - def 'Consume, persist and forward valid CM create message'() { - given: 'an event with data category CM' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpInEvent.json') - def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class) - testEventSent.getData().getDataType().setDataCategory(dataCategory) - def testCloudEventSent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventSent)) - .withId('subscriptionCreated') - .withType(dataType) - .withSource(URI.create('some-resource')) - .withExtension('correlationid', 'test-cmhandle1').build() - def consumerRecord = new ConsumerRecord('topic-name', 0, 0, 'event-key', testCloudEventSent) - and: 'notifications are enabled' - objectUnderTest.notificationFeatureEnabled = isNotificationEnabled - and: 'subscription model loader is enabled' - objectUnderTest.subscriptionModelLoaderEnabled = isModelLoaderEnabled - when: 'the valid event is consumed' - objectUnderTest.consumeSubscriptionEvent(consumerRecord) - then: 'the event is mapped to a yangModelSubscription' - numberOfTimesToPersist * mockCmSubscriptionNcmpInEventMapper.toYangModelSubscriptionEvent(testEventSent) >> yangModelSubscriptionEvent - and: 'the event is persisted' - numberOfTimesToPersist * mockSubscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent) - and: 'the event is forwarded' - numberOfTimesToForward * mockCmSubscriptionNcmpInEventForwarder.forwardCreateSubscriptionEvent(testEventSent, 'subscriptionCreated') - where: 'given values are used' - scenario | dataCategory | dataType | isNotificationEnabled | isModelLoaderEnabled || numberOfTimesToForward || numberOfTimesToPersist - 'Both model loader and notification are enabled' | 'CM' | 'subscriptionCreated' | true | true || 1 || 1 - 'Both model loader and notification are disabled' | 'CM' | 'subscriptionCreated' | false | false || 0 || 0 - 'Model loader enabled and notification disabled' | 'CM' | 'subscriptionCreated' | false | true || 0 || 1 - 'Model loader disabled and notification enabled' | 'CM' | 'subscriptionCreated' | true | false || 1 || 0 - 'Flags are enabled but data category is FM' | 'FM' | 'subscriptionCreated' | true | true || 0 || 0 - 'Flags are enabled but data type is UPDATE' | 'CM' | 'subscriptionUpdated' | true | true || 0 || 1 - } - - def 'Consume event with wrong datastore causes an exception'() { - given: 'an event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpInEvent.json') - def testEventSent = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class) - and: 'datastore is set to a passthrough-running datastore' - testEventSent.getData().getPredicates().setDatastore('operational') - def testCloudEventSent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventSent)) - .withId('some-event-id') - .withType('some-event-type') - .withSource(URI.create('some-resource')) - .withExtension('correlationid', 'test-cmhandle1').build() - def consumerRecord = new ConsumerRecord('topic-name', 0, 0, 'event-key', testCloudEventSent) - when: 'the valid event is consumed' - objectUnderTest.consumeSubscriptionEvent(consumerRecord) - then: 'an operation not supported exception is thrown' - thrown(UnsupportedOperationException) - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy deleted file mode 100644 index ef3ea88bb..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventForwarderSpec.groovy +++ /dev/null @@ -1,209 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent - -import com.fasterxml.jackson.databind.ObjectMapper -import com.hazelcast.map.IMap -import io.cloudevents.CloudEvent -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionPersistence -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus -import org.onap.cps.ncmp.api.impl.utils.CmSubscriptionEventCloudMapper -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.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent -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.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.util.concurrent.BlockingVariable -import java.util.concurrent.TimeUnit - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, CmSubscriptionNcmpInEventForwarder]) -class CmSubscriptionNcmpInEventForwarderSpec extends MessagingBaseSpec { - - @Autowired - CmSubscriptionNcmpInEventForwarder objectUnderTest - @SpringBean - InventoryPersistence mockInventoryPersistence = Mock(InventoryPersistence) - @SpringBean - EventsPublisher mockSubscriptionEventPublisher = Mock(EventsPublisher) - @SpringBean - IMap> mockForwardedSubscriptionEventCache = Mock(IMap>) - @SpringBean - CmSubscriptionEventCloudMapper subscriptionEventCloudMapper = new CmSubscriptionEventCloudMapper(new ObjectMapper()) - @SpringBean - CmSubscriptionNcmpOutEventPublisher mockCmSubscriptionNcmpOutEventPublisher = Mock(CmSubscriptionNcmpOutEventPublisher) - @SpringBean - SubscriptionPersistence mockSubscriptionPersistence = Mock(SubscriptionPersistence) - @SpringBean - CmSubscriptionNcmpInEventMapper cmSubscriptionNcmpInEventMapper = Mappers.getMapper(CmSubscriptionNcmpInEventMapper) - @SpringBean - CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper cmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper = Mappers.getMapper(CmSubscriptionNcmpInEventToCmSubscriptionDmiInEventMapper) - @Autowired - JsonObjectMapper jsonObjectMapper - @Autowired - ObjectMapper objectMapper - - def 'Forward valid CM create subscription and simulate timeout'() { - given: 'a ncmp in event' - def ncmpInEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/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(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(5) - when: 'the valid event is forwarded' - 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","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 -> { - def targets = toTargetEvent(cloudEvent, CmSubscriptionDmiInEvent.class).getData().getPredicates().getTargets() - def cmHandle2 = createCmHandle('CMHandle2', ['shape': 'square'] as Map) - def cmHandle1 = createCmHandle('CMHandle1', ['shape': 'circle'] as Map) - 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(*_) - and: 'the subscription id is removed from the event cache map returning the asynchronous blocking variable' - 1 * mockForwardedSubscriptionEventCache.remove("SCO-9989752cm-subscription-001") >> { block.set(_) } - } - - def 'Forward CM create subscription where target CM Handles are #scenario'() { - given: 'a ncmp in event' - def ncmpInEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpInEvent.json') - def ncmpInEventJson = jsonObjectMapper.convertJsonString(ncmpInEventJsonData, CmSubscriptionNcmpInEvent.class) - and: 'the target CMHandles are set to #scenario' - ncmpInEventJson.getData().getPredicates().setTargets(invalidTargets) - when: 'the event is forwarded' - objectUnderTest.forwardCreateSubscriptionEvent(ncmpInEventJson, 'some-event-type') - then: 'an operation not supported exception is thrown' - thrown(UnsupportedOperationException) - where: - scenario | invalidTargets - 'null' | null - 'empty' | [] - 'wildcard' | ['CMHandle*'] - } - - def 'Forward valid CM create subscription where targets are not associated to any existing CMHandles'() { - given: 'a ncmp in event' - def ncmpInEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/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(ncmpInEventJson) - yangModelSubscriptionEvent.getPredicates().setTargetCmHandles(rejectedCmHandles) - 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(5) - when: 'the valid event is forwarded' - 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' - 0 * mockSubscriptionEventPublisher.publishCloudEvent("ncmp-dmi-cm-avc-subscription-DMIName1", "SCO-9989752-cm-subscription-001-DMIName1", - cloudEvent -> { - def targets = toTargetEvent(cloudEvent, CmSubscriptionDmiInEvent.class).getData().getPredicates().getTargets() - def cmHandle2 = createCmHandle('CMHandle2', ['shape': 'square'] as Map) - def cmHandle1 = createCmHandle('CMHandle1', ['shape': 'circle'] as Map) - targets == [cmHandle2, cmHandle1] - } - ) - 0 * 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' - 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' - 1 * mockSubscriptionPersistence.saveSubscriptionEvent(yangModelSubscriptionEvent) - and: 'subscription outcome has been sent' - 1 * mockCmSubscriptionNcmpOutEventPublisher.sendResponse(_, 'subscriptionCreatedStatus') - } - - def 'Extract domain name from URL for #scenario'() { - when: 'a valid dmi name is provided' - def domainName = objectUnderTest.toValidTopicSuffix(dmiName) - then: 'domain name is as expected with no port information' - assert domainName == expectedDomainName - where: '' - scenario | dmiName || expectedDomainName - 'insecure http url with port' | 'http://www.onap-dmi:8080/xyz=123' || 'onap-dmi' - 'insecure http url without port' | 'http://www.onap-dmi/xyz=123' || 'onap-dmi' - 'secure https url with port' | 'https://127.0.0.1:8080/xyz=123' || '127.0.0.1' - 'secure https url without port' | 'https://127.0.0.1/xyz=123' || '127.0.0.1' - 'servername without protocol and port' | 'dminame1' || 'dminame1' - 'servername without protocol' | 'www.onap-dmi:8080/xyz=123' || 'www.onap-dmi:8080/xyz=123' - - } - - static def createYangModelCmHandleWithDmiProperty(id, dmiId, propertyName, propertyValue) { - return new YangModelCmHandle(id: "CMHandle" + id, dmiDataServiceName: "DMIName" + dmiId, dmiProperties: [new YangModelCmHandle.Property(propertyName, propertyValue)]) - } - - static def createCmHandle(id, additionalProperties) { - def cmHandle = new CmHandle(); - cmHandle.setId(id) - cmHandle.setAdditionalProperties(additionalProperties) - return cmHandle - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapperSpec.groovy deleted file mode 100644 index 6dcc997ec..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpInEventMapperSpec.groovy +++ /dev/null @@ -1,78 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import com.fasterxml.jackson.databind.ObjectMapper -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.deprecated.subscriptions.SubscriptionStatus -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - - -@SpringBootTest(classes = [JsonObjectMapper, ObjectMapper]) -class CmSubscriptionNcmpInEventMapperSpec extends Specification { - - CmSubscriptionNcmpInEventMapper objectUnderTest = Mappers.getMapper(CmSubscriptionNcmpInEventMapper) - - @Autowired - JsonObjectMapper jsonObjectMapper - - def 'Map subscription event to yang model subscription event where #scenario'() { - given: 'a Subscription Event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpInEvent.json') - def testEventToMap = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpInEvent.class) - when: 'the event is mapped to a yang model subscription' - def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap) - then: 'the resulting yang model subscription event contains the correct clientId' - assert result.clientId == "SCO-9989752" - and: 'subscription name' - assert result.subscriptionName == "cm-subscription-001" - and: 'is tagged value is false' - assert !result.isTagged - and: 'predicate targets ' - assert result.predicates.targetCmHandles.cmHandleId == ["CMHandle1", "CMHandle2", "CMHandle3"] - and: 'the status for these targets is set to pending' - assert result.predicates.targetCmHandles.status == [SubscriptionStatus.PENDING, SubscriptionStatus.PENDING, SubscriptionStatus.PENDING] - and: 'the topic is null' - assert result.topic == null - } - - def 'Map empty subscription event to yang model subscription event'() { - given: 'a new Subscription Event with no data' - def testEventToMap = new CmSubscriptionNcmpInEvent() - when: 'the event is mapped to a yang model subscription' - def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap) - then: 'the resulting yang model subscription event contains null clientId' - assert result.clientId == null - and: 'subscription name is null' - assert result.subscriptionName == null - and: 'is tagged value is false' - assert result.isTagged == false - and: 'predicates is null' - assert result.predicates == null - and: 'the topic is null' - assert result.topic == null - } -} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy deleted file mode 100644 index 725d32437..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/deprecated/cmsubscription/CmSubscriptionNcmpOutEventPublisherSpec.groovy +++ /dev/null @@ -1,128 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.deprecated.cmsubscription - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUCCESSFULLY_APPLIED_SUBSCRIPTION -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUBSCRIPTION_PENDING -import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUBSCRIPTION_NOT_APPLICABLE -import static org.onap.cps.ncmp.api.NcmpResponseStatus.PARTIALLY_APPLIED_SUBSCRIPTION - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.CloudEvent -import io.cloudevents.core.builder.CloudEventBuilder -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.deprecated.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.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 -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper, CmSubscriptionNcmpOutEventPublisher]) -class CmSubscriptionNcmpOutEventPublisherSpec extends DataNodeBaseSpec { - - @Autowired - CmSubscriptionNcmpOutEventPublisher objectUnderTest - - @SpringBean - SubscriptionPersistence mockSubscriptionPersistence = Mock(SubscriptionPersistence) - @SpringBean - EventsPublisher mockCmSubscriptionNcmpOutEventPublisher = Mock(EventsPublisher) - @SpringBean - CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper cmSubscriptionEventToCmSubscriptionNcmpOutEventMapper = Mappers.getMapper(CmSubscriptionEventToCmSubscriptionNcmpOutEventMapper) - @SpringBean - SubscriptionOutcomeCloudMapper subscriptionOutcomeCloudMapper = new SubscriptionOutcomeCloudMapper(new ObjectMapper()) - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - ObjectMapper objectMapper - - def 'Send response to the client apps successfully'() { - given: 'a cm subscription event' - def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionEvent.json') - def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class) - and: 'a ncmp out event' - def ncmpOutEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/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(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')) - .withExtension("correlationid", 'SCO-9989752cm-subscription-001') - .withSource(URI.create('NCMP')).build() - 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(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 ncmp out message as expected'() { - given: 'a cm subscription event' - def cmSubscriptionEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionEvent.json') - def cmSubscriptionEvent = jsonObjectMapper.convertJsonString(cmSubscriptionEventJsonData, CmSubscriptionEvent.class) - and: 'a ncmp out event' - def ncmpOutEventJsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpOutEvent.json') - def ncmpOutEvent = jsonObjectMapper.convertJsonString(ncmpOutEventJsonData, CmSubscriptionNcmpOutEvent.class) - and: 'a status code and status message a per #scenarios' - 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' | SUCCESSFULLY_APPLIED_SUBSCRIPTION || 'successfully applied subscription' || 1 - 'is partial outcome' | PARTIALLY_APPLIED_SUBSCRIPTION || 'partially applied subscription' || 104 - } - - def 'Check cm handle id to status map to see if it is a full outcome response'() { - when: 'is full outcome response evaluated' - def response = objectUnderTest.decideOnNcmpEventResponseCodeForSubscription(cmHandleIdToStatusAndDetailsAsMap) - then: 'the result will be as expected' - response == expectedOutcomeResponseDecision - where: 'the following values are used' - scenario | cmHandleIdToStatusAndDetailsAsMap || expectedOutcomeResponseDecision - 'The map contains PENDING status' | [CMHandle1: [details: 'Subscription forwarded to dmi plugin', status: 'PENDING'] as Map] as Map || SUBSCRIPTION_PENDING - 'The map contains ACCEPTED status' | [CMHandle1: [details: '', status: 'ACCEPTED'] as Map] as Map || SUCCESSFULLY_APPLIED_SUBSCRIPTION - 'The map contains REJECTED status' | [CMHandle1: [details: 'Cm handle does not exist', status: 'REJECTED'] as Map] as Map || SUBSCRIPTION_NOT_APPLICABLE - 'The map contains PENDING and PENDING statuses' | [CMHandle1: [details: 'Some details', status: 'PENDING'] as Map, CMHandle2: [details: 'Some details', status: 'PENDING'] as Map as Map] as Map || SUBSCRIPTION_PENDING - 'The map contains ACCEPTED and ACCEPTED statuses' | [CMHandle1: [details: '', status: 'ACCEPTED'] as Map, CMHandle2: [details: '', status: 'ACCEPTED'] as Map as Map] as Map || SUCCESSFULLY_APPLIED_SUBSCRIPTION - 'The map contains REJECTED and REJECTED statuses' | [CMHandle1: [details: 'Reject details', status: 'REJECTED'] as Map, CMHandle2: [details: 'Reject details', status: 'REJECTED'] as Map as Map] as Map || SUBSCRIPTION_NOT_APPLICABLE - 'The map contains PENDING and ACCEPTED statuses' | [CMHandle1: [details: 'Some details', status: 'PENDING'] as Map, CMHandle2: [details: '', status: 'ACCEPTED'] as Map as Map] as Map || PARTIALLY_APPLIED_SUBSCRIPTION - 'The map contains REJECTED and ACCEPTED statuses' | [CMHandle1: [details: 'Cm handle does not exist', status: 'REJECTED'] as Map, CMHandle2: [details: '', status: 'ACCEPTED'] as Map as Map] as Map || PARTIALLY_APPLIED_SUBSCRIPTION - 'The map contains PENDING and REJECTED statuses' | [CMHandle1: [details: 'Subscription forwarded to dmi plugin', status: 'PENDING'] as Map, CMHandle2: [details: 'Cm handle does not exist', status: 'REJECTED'] as Map as Map] as Map || PARTIALLY_APPLIED_SUBSCRIPTION - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/mapper/CloudEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/mapper/CloudEventMapperSpec.groovy deleted file mode 100644 index e6d383128..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/mapper/CloudEventMapperSpec.groovy +++ /dev/null @@ -1,67 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.events.mapper - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.core.builder.CloudEventBuilder -import org.onap.cps.ncmp.events.cmsubscription1_0_0.client_to_ncmp.CmSubscriptionNcmpInEvent -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class CloudEventMapperSpec extends Specification { - - @Autowired - JsonObjectMapper jsonObjectMapper - - def 'Cloud event to target event type'() { - given: 'a cloud event with valid payload' - def cloudEvent = testCloudEvent(new CmSubscriptionNcmpInEvent()) - when: 'the cloud event mapped to target event' - def result = CloudEventMapper.toTargetEvent((cloudEvent), CmSubscriptionNcmpInEvent.class) - then: 'the cloud event is mapped' - assert result instanceof CmSubscriptionNcmpInEvent - } - - def 'Cloud event to target event type when it is #scenario'() { - given: 'a cloud event with invalid payload' - def cloudEvent = testCloudEvent(payload) - when: 'the cloud event mapped to target event' - def result = CloudEventMapper.toTargetEvent(cloudEvent, CmSubscriptionNcmpInEvent.class) - then: 'result is null' - assert result == null - where: 'below are the scenarios' - scenario | payload - 'invalid payload type' | ArrayList.class - 'without payload' | null - } - - def testCloudEvent(payload) { - return CloudEventBuilder.v1().withData(jsonObjectMapper.asJsonBytes(payload)) - .withId("cmhandle1") - .withSource(URI.create('test-source')) - .withDataSchema(URI.create('test')) - .withType('org.onap.cm.events.cm-subscription') - .build() - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/CmSubscriptionEventCloudMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/CmSubscriptionEventCloudMapperSpec.groovy deleted file mode 100644 index d61a026b7..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/CmSubscriptionEventCloudMapperSpec.groovy +++ /dev/null @@ -1,85 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.util.deprecated - -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.core.builder.CloudEventBuilder -import org.onap.cps.ncmp.api.impl.utils.CmSubscriptionEventCloudMapper -import org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class CmSubscriptionEventCloudMapperSpec extends Specification { - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - ObjectMapper objectMapper - - def spyObjectMapper = Spy(ObjectMapper) - - def objectUnderTest = new CmSubscriptionEventCloudMapper(spyObjectMapper) - - def 'Map the subscription event to data of the cloud event'() { - given: 'a subscription event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionDmiInEvent.json') - def testEventData = jsonObjectMapper.convertJsonString(jsonData, - CmSubscriptionDmiInEvent.class) - def testCloudEvent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventData)) - .withId('some-id') - .withType('subscriptionCreated') - .withSource(URI.create('SCO-9989752')) - .withExtension('correlationid', 'test-cmhandle1').build() - when: 'the subscription event map to data of cloud event' - CmSubscriptionEventCloudMapper.randomId = 'some-id' - def resultCloudEvent = objectUnderTest.toCloudEvent(testEventData, 'some-event-key', 'subscriptionCreated') - then: 'the subscription event resulted having expected values' - resultCloudEvent.getData() == testCloudEvent.getData() - resultCloudEvent.getId() == testCloudEvent.getId() - resultCloudEvent.getType() == testCloudEvent.getType() - resultCloudEvent.getSource() == URI.create('SCO-9989752') - resultCloudEvent.getDataSchema() == URI.create('urn:cps:org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_dmi.CmSubscriptionDmiInEvent:1.0.0') - } - - def 'Map the subscription event to cloud event with JSON processing exception'() { - given: 'a json processing exception during process' - def jsonProcessingException = new JsonProcessingException('The Cloud Event could not be constructed') - spyObjectMapper.writeValueAsBytes(_) >> { throw jsonProcessingException } - and: 'a subscription event of ncmp version' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionDmiInEvent.json') - def testEventData = jsonObjectMapper.convertJsonString(jsonData, - CmSubscriptionDmiInEvent.class) - when: 'the subscription event map to cloud event' - def expectedResult = objectUnderTest.toCloudEvent(testEventData, 'some-key', 'some-event-type') - then: 'no exception is thrown since it has been handled already' - noExceptionThrown() - and: 'expected result should be null' - expectedResult == null - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/SubscriptionOutcomeCloudMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/SubscriptionOutcomeCloudMapperSpec.groovy deleted file mode 100644 index 9d79a8b4b..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/util/deprecated/SubscriptionOutcomeCloudMapperSpec.groovy +++ /dev/null @@ -1,83 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (c) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.util.deprecated - -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.core.builder.CloudEventBuilder -import org.onap.cps.ncmp.api.impl.utils.SubscriptionOutcomeCloudMapper -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 -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) -class SubscriptionOutcomeCloudMapperSpec extends Specification { - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - ObjectMapper objectMapper - - def spyObjectMapper = Spy(ObjectMapper) - - def objectUnderTest = new SubscriptionOutcomeCloudMapper(spyObjectMapper) - - def 'Map the subscription outcome to cloud event'() { - given: 'a subscription event' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpOutEvent.json') - def testEventData = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpOutEvent.class) - def testCloudEvent = CloudEventBuilder.v1() - .withData(objectMapper.writeValueAsBytes(testEventData)) - .withId('some-id') - .withType('subscriptionCreatedStatus') - .withSource(URI.create('NCMP')) - .withExtension('correlationid', 'test-cmhandle1').build() - when: 'the subscription event map to data of cloud event' - SubscriptionOutcomeCloudMapper.randomId = 'some-id' - def resultCloudEvent = objectUnderTest.toCloudEvent(testEventData, 'some-event-key', 'subscriptionCreatedStatus') - then: 'the subscription event resulted having expected values' - resultCloudEvent.getData() == testCloudEvent.getData() - resultCloudEvent.getId() == testCloudEvent.getId() - resultCloudEvent.getType() == testCloudEvent.getType() - resultCloudEvent.getSource() == testCloudEvent.getSource() - resultCloudEvent.getDataSchema() == URI.create('urn:cps:org.onap.cps.ncmp.events.cmsubscription1_0_0.ncmp_to_client.CmSubscriptionNcmpOutEvent:1.0.0') - } - - def 'Map the subscription outcome to cloud event with JSON processing exception'() { - given: 'a json processing exception during process' - def jsonProcessingException = new JsonProcessingException('The Cloud Event could not be constructed') - spyObjectMapper.writeValueAsBytes(_) >> { throw jsonProcessingException } - and: 'a cloud event having a subscription outcome in the data part' - def jsonData = TestUtils.getResourceFileContent('deprecatedCmSubscription/cmSubscriptionNcmpOutEvent.json') - def testEventData = jsonObjectMapper.convertJsonString(jsonData, CmSubscriptionNcmpOutEvent.class) - when: 'the subscription outcome map to cloud event' - def expectedResult = objectUnderTest.toCloudEvent(testEventData, 'some-key', 'some-event-type') - then: 'no exception is thrown since it has been handled already' - noExceptionThrown() - and: 'expected result should be null' - expectedResult == null - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy index f9ecd6337..a9f24667f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ class AbstractModelLoaderSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) def mockCpsAnchorService = Mock(CpsAnchorService) - def objectUnderTest = Spy(new TestModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService)) + def objectUnderTest = Spy(new TestModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsAnchorService, mockCpsDataService)) def applicationContext = new AnnotationConfigApplicationContext() @@ -221,9 +221,9 @@ class AbstractModelLoaderSpec extends Specification { TestModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService, - final CpsAnchorService cpsAnchorService) { - super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService) + final CpsAnchorService cpsAnchorService, + final CpsDataService cpsDataService) { + super(cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService) super.maximumAttemptCount = 2 super.retryTimeMs = 1 } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index aed495ec4..9722fe8c6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ package org.onap.cps.ncmp.init import org.onap.cps.api.CpsAnchorService +import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException +import org.onap.cps.spi.exceptions.AlreadyDefinedException import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME @@ -42,7 +44,7 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) def mockCpsAnchorService = Mock(CpsAnchorService) - def objectUnderTest = new CmDataSubscriptionModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService) + def objectUnderTest = new CmDataSubscriptionModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsAnchorService, mockCpsDataService) def applicationContext = new AnnotationConfigApplicationContext() @@ -77,6 +79,35 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { 1 * mockCpsAnchorService.createAnchor(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', 'cm-data-subscriptions') and: 'the data service to create a top level datanode is called once' 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', '{"datastores":{}}', _) + and: 'the data service is called once to create datastore for Passthrough-operational' + 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', '/datastores', + '{"datastore":[{"name":"ncmp-datastores:passthrough-operational","cm-handles":{}}]}', _, _) + and: 'the data service is called once to create datastore for Passthrough-running' + 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', '/datastores', + '{"datastore":[{"name":"ncmp-datastores:passthrough-running","cm-handles":{}}]}', _, _) + } + + def 'Create node for datastore with already defined exception.'() { + given:'the data service throws an Already Defined exception' + mockCpsDataService.saveData(*_) >> { throw AlreadyDefinedException.forDataNodes([], 'some context') } + when: 'attempt to create datastore' + objectUnderTest.createDatastore('some datastore') + then: 'the exception is ignored i.e. no exception thrown up' + noExceptionThrown() + and: 'the exception message is logged' + def logs = loggingListAppender.list.toString() + logs.contains("Creating new child data node 'some datastore' for data node 'datastores' failed as data node already exists") + } + + def 'Create node for datastore with any other exception.'() { + given: 'the data service throws an exception' + mockCpsDataService.saveData(*_) >> { throw new RuntimeException('test message') } + when: 'attempt to create datastore' + objectUnderTest.createDatastore('some datastore') + then: 'a startup exception with correct message and details is thrown' + def thrown = thrown(NcmpStartUpException) + assert thrown.message.contains('Creating data node failed') + assert thrown.details.contains('test message') } def 'Subscription model loader disabled.' () { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index 5557993ab..a36532761 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ class InventoryModelLoaderSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) def mockCpsAnchorService = Mock(CpsAnchorService) - def objectUnderTest = new InventoryModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService) + def objectUnderTest = new InventoryModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsAnchorService, mockCpsDataService) def applicationContext = new AnnotationConfigApplicationContext() diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index e49714b66..b3f42279d 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -48,9 +48,8 @@ import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.model.DeltaReport; import org.onap.cps.spi.utils.CpsValidator; import org.onap.cps.utils.ContentType; -import org.onap.cps.utils.TimedYangParser; +import org.onap.cps.utils.YangParser; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.springframework.stereotype.Service; @Service @@ -63,9 +62,8 @@ public class CpsDataServiceImpl implements CpsDataService { private final CpsDataPersistenceService cpsDataPersistenceService; private final CpsAnchorService cpsAnchorService; - private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; private final CpsValidator cpsValidator; - private final TimedYangParser timedYangParser; + private final YangParser yangParser; private final CpsDeltaService cpsDeltaService; @Override @@ -309,10 +307,9 @@ public class CpsDataServiceImpl implements CpsDataService { private Collection buildDataNodes(final Anchor anchor, final String parentNodeXpath, final String nodeData, final ContentType contentType) { - final SchemaContext schemaContext = getSchemaContext(anchor); if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = timedYangParser.parseData(contentType, nodeData, schemaContext); + final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, ""); final Collection dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); @@ -323,7 +320,7 @@ public class CpsDataServiceImpl implements CpsDataService { } final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath); final ContainerNode containerNode = - timedYangParser.parseData(contentType, nodeData, schemaContext, normalizedParentNodeXpath); + yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath); final Collection dataNodes = new DataNodeBuilder() .withParentNodeXpath(normalizedParentNodeXpath) .withContainerNode(containerNode) @@ -334,10 +331,6 @@ public class CpsDataServiceImpl implements CpsDataService { return dataNodes; } - private SchemaContext getSchemaContext(final Anchor anchor) { - return yangTextSchemaSourceSetCache - .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext(); - } private static boolean isRootNodeXpath(final String xpath) { return ROOT_NODE_XPATH.equals(xpath); diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java index 683ddce3d..1e1fe819a 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import lombok.NoArgsConstructor; +import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.spi.model.DataNode; @@ -38,7 +38,6 @@ import org.springframework.stereotype.Service; @Slf4j @Service -@NoArgsConstructor public class CpsDeltaServiceImpl implements CpsDeltaService { @Override @@ -50,7 +49,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { final Map xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes); final Map xpathToTargetDataNodes = convertToXPathToDataNodesMap(targetDataNodes); - deltaReport.addAll(getRemovedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); + deltaReport.addAll(getRemovedAndUpdatedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); deltaReport.addAll(getAddedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); @@ -70,26 +69,122 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return xpathToDataNode; } - private static List getRemovedDeltaReports( - final Map xpathToSourceDataNodes, - final Map xpathToTargetDataNodes) { - - final List removedDeltaReportEntries = new ArrayList<>(); + private static List getRemovedAndUpdatedDeltaReports( + final Map xpathToSourceDataNodes, + final Map xpathToTargetDataNodes) { + final List removedAndUpdatedDeltaReportEntries = new ArrayList<>(); for (final Map.Entry entry: xpathToSourceDataNodes.entrySet()) { final String xpath = entry.getKey(); final DataNode sourceDataNode = entry.getValue(); final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath); - + final List deltaReports; if (targetDataNode == null) { - final Map sourceDataNodeLeaves = sourceDataNode.getLeaves(); - final DeltaReport removedData = new DeltaReportBuilder().actionRemove().withXpath(xpath) - .withSourceData(sourceDataNodeLeaves).build(); - removedDeltaReportEntries.add(removedData); + deltaReports = getRemovedDeltaReports(xpath, sourceDataNode); + } else { + deltaReports = getUpdatedDeltaReports(xpath, sourceDataNode, targetDataNode); } + removedAndUpdatedDeltaReportEntries.addAll(deltaReports); } + return removedAndUpdatedDeltaReportEntries; + } + + private static List getRemovedDeltaReports(final String xpath, final DataNode sourceDataNode) { + final List removedDeltaReportEntries = new ArrayList<>(); + final Map sourceDataNodeLeaves = sourceDataNode.getLeaves(); + final DeltaReport removedDeltaReportEntry = new DeltaReportBuilder().actionRemove().withXpath(xpath) + .withSourceData(sourceDataNodeLeaves).build(); + removedDeltaReportEntries.add(removedDeltaReportEntry); return removedDeltaReportEntries; } + private static List getUpdatedDeltaReports(final String xpath, final DataNode sourceDataNode, + final DataNode targetDataNode) { + final List updatedDeltaReportEntries = new ArrayList<>(); + final Map, Map> updatedLeavesAsSourceDataToTargetData = + getUpdatedLeavesBetweenSourceAndTargetDataNode(sourceDataNode.getLeaves(), targetDataNode.getLeaves()); + addUpdatedLeavesToDeltaReport(xpath, updatedLeavesAsSourceDataToTargetData, updatedDeltaReportEntries); + return updatedDeltaReportEntries; + } + + private static Map, + Map> getUpdatedLeavesBetweenSourceAndTargetDataNode( + final Map leavesOfSourceDataNode, + final Map leavesOfTargetDataNode) { + final Map, Map> updatedLeavesAsSourceDataToTargetData = + new LinkedHashMap<>(); + final Map sourceDataInDeltaReport = new LinkedHashMap<>(); + final Map targetDataInDeltaReport = new LinkedHashMap<>(); + processLeavesPresentInSourceAndTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode, + sourceDataInDeltaReport, targetDataInDeltaReport); + processLeavesUniqueInTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode, + sourceDataInDeltaReport, targetDataInDeltaReport); + final boolean isUpdatedDataInDeltaReport = + !sourceDataInDeltaReport.isEmpty() || !targetDataInDeltaReport.isEmpty(); + if (isUpdatedDataInDeltaReport) { + updatedLeavesAsSourceDataToTargetData.put(sourceDataInDeltaReport, targetDataInDeltaReport); + } + return updatedLeavesAsSourceDataToTargetData; + } + + private static void processLeavesPresentInSourceAndTargetDataNode( + final Map leavesOfSourceDataNode, + final Map leavesOfTargetDataNode, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + for (final Map.Entry entry: leavesOfSourceDataNode.entrySet()) { + final String key = entry.getKey(); + final Serializable sourceLeaf = entry.getValue(); + final Serializable targetLeaf = leavesOfTargetDataNode.get(key); + compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport); + } + } + + private static void processLeavesUniqueInTargetDataNode( + final Map leavesOfSourceDataNode, + final Map leavesOfTargetDataNode, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + final Map uniqueLeavesOfTargetDataNode = + new LinkedHashMap<>(leavesOfTargetDataNode); + uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet()); + for (final Map.Entry entry: uniqueLeavesOfTargetDataNode.entrySet()) { + final String key = entry.getKey(); + final Serializable targetLeaf = entry.getValue(); + final Serializable sourceLeaf = leavesOfSourceDataNode.get(key); + compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport); + } + } + + private static void compareLeaves(final String key, + final Serializable sourceLeaf, + final Serializable targetLeaf, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + if (sourceLeaf != null && targetLeaf != null) { + if (!Objects.equals(sourceLeaf, targetLeaf)) { + sourceDataInDeltaReport.put(key, sourceLeaf); + targetDataInDeltaReport.put(key, targetLeaf); + } + } else if (sourceLeaf != null) { + sourceDataInDeltaReport.put(key, sourceLeaf); + } else if (targetLeaf != null) { + targetDataInDeltaReport.put(key, targetLeaf); + } + } + + private static void addUpdatedLeavesToDeltaReport(final String xpath, + final Map, Map> updatedLeavesAsSourceDataToTargetData, + final List updatedDeltaReportEntries) { + for (final Map.Entry, Map> entry: + updatedLeavesAsSourceDataToTargetData.entrySet()) { + final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate() + .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); + updatedDeltaReportEntries.add(updatedDataForDeltaReport); + } + + } + private static List getAddedDeltaReports(final Map xpathToSourceDataNodes, final Map xpathToTargetDataNodes) { diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java index b9c05dcf0..fb9c1971b 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java @@ -32,6 +32,7 @@ public class DeltaReport { public static final String ADD_ACTION = "add"; public static final String REMOVE_ACTION = "remove"; + public static final String UPDATE_ACTION = "update"; DeltaReport() {} diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java index cef6ca3fa..1e151eeb2 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java @@ -58,6 +58,11 @@ public class DeltaReportBuilder { return this; } + public DeltaReportBuilder actionUpdate() { + this.action = DeltaReport.UPDATE_ACTION; + return this; + } + /** * To create a single entry of {@link DeltaReport}. * diff --git a/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java b/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java deleted file mode 100644 index 4640593dc..000000000 --- a/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.utils; - -import io.micrometer.core.annotation.Timed; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.springframework.stereotype.Service; - -@Service -public class TimedYangParser { - - /** - * Parses data into Collection of NormalizedNode according to given schema context. - * - * @param nodeData data string - * @param schemaContext schema context describing associated data model - * @return the NormalizedNode object - */ - @Timed(value = "cps.utils.yangparser.nodedata.parse", - description = "Time taken to parse node data without a parent") - public ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext) { - return YangUtils.parseData(contentType, nodeData, schemaContext); - } - - /** - * Parses data into NormalizedNode according to given schema context. - * - * @param nodeData data string - * @param schemaContext schema context describing associated data model - * @return the NormalizedNode object - */ - @Timed(value = "cps.utils.yangparser.nodedata.with.parent.parse", - description = "Time taken to parse node data with a parent") - public ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext, - final String parentNodeXpath) { - return YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath); - } -} diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java index 98c7947e1..7a6d0bb3d 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG - * Modifications Copyright (C) 2023 Nordix Foundation. + * Modifications Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ public class XmlFileUtils { public static String prepareXmlContent(final String xmlContent, final SchemaContext schemaContext) throws IOException, ParserConfigurationException, TransformerException, SAXException { return addRootNodeToXmlContent(xmlContent, schemaContext.getModules().iterator().next().getName(), - YangUtils.DATA_ROOT_NODE_NAMESPACE); + YangParserHelper.DATA_ROOT_NODE_NAMESPACE); } /** @@ -106,7 +106,7 @@ public class XmlFileUtils { documentBuilder.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8))); final Element root = document.getDocumentElement(); if (!root.getTagName().equals(rootNodeTagName) - && !root.getTagName().equals(YangUtils.DATA_ROOT_NODE_TAG_NAME)) { + && !root.getTagName().equals(YangParserHelper.DATA_ROOT_NODE_TAG_NAME)) { final Document documentWithRootNode = addDataRootNode(root, rootNodeTagName, namespace, rootNodeProperty); documentWithRootNode.setXmlStandalone(true); final Transformer transformer = getTransformerFactory().newTransformer(); diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java new file mode 100644 index 000000000..6299ef39f --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java @@ -0,0 +1,70 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.utils; + +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.impl.YangTextSchemaSourceSetCache; +import org.onap.cps.spi.exceptions.DataValidationException; +import org.onap.cps.spi.model.Anchor; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class YangParser { + + private final YangParserHelper yangParserHelper; + private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; + + /** + * Parses data into (normalized) ContainerNode according to schema context for the given anchor. + * + * @param nodeData data string + * @param anchor the anchor for the node data + * @return the NormalizedNode object + */ + @Timed(value = "cps.utils.yangparser.nodedata.with.parent.parse", + description = "Time taken to parse node data with a parent") + public ContainerNode parseData(final ContentType contentType, + final String nodeData, + final Anchor anchor, + final String parentNodeXpath) { + final SchemaContext schemaContext = getSchemaContext(anchor); + try { + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + } catch (final DataValidationException e) { + invalidateCache(anchor); + } + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + } + + private SchemaContext getSchemaContext(final Anchor anchor) { + return yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), + anchor.getSchemaSetName()).getSchemaContext(); + } + + private void invalidateCache(final Anchor anchor) { + yangTextSchemaSourceSetCache.removeFromCache(anchor.getDataspaceName(), anchor.getSchemaSetName()); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java new file mode 100644 index 000000000..5cadd2936 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java @@ -0,0 +1,252 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.utils; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonReader; +import java.io.IOException; +import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.TransformerException; +import lombok.RequiredArgsConstructor; +import org.onap.cps.cpspath.parser.CpsPathUtil; +import org.onap.cps.cpspath.parser.PathParsingException; +import org.onap.cps.spi.exceptions.DataValidationException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory; +import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier; +import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; +import org.springframework.stereotype.Service; +import org.xml.sax.SAXException; + +@Service +@RequiredArgsConstructor +public class YangParserHelper { + + static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + static final String DATA_ROOT_NODE_TAG_NAME = "data"; + + /** + * Parses data into NormalizedNode according to given schema context. + * + * @param contentType the type of the node data (json or xml) + * @param nodeData data string + * @param schemaContext schema context describing associated data model + * @param parentNodeXpath the xpath referencing the parent node current data fragment belong to + * @return the NormalizedNode object + */ + public ContainerNode parseData(final ContentType contentType, + final String nodeData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + if (contentType == ContentType.JSON) { + return parseJsonData(nodeData, schemaContext, parentNodeXpath); + } + return parseXmlData(nodeData, schemaContext, parentNodeXpath); + } + + private ContainerNode parseJsonData(final String jsonData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 + .getShared((EffectiveModelContext) schemaContext); + final DataContainerNodeBuilder dataContainerNodeBuilder = + Builders.containerBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier( + QName.create(DATA_ROOT_NODE_NAMESPACE, DATA_ROOT_NODE_TAG_NAME) + )); + final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(dataContainerNodeBuilder); + final JsonReader jsonReader = new JsonReader(new StringReader(jsonData)); + final JsonParserStream jsonParserStream; + + if (parentNodeXpath.isEmpty()) { + jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory); + } else { + final Collection dataSchemaNodeIdentifiers + = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath); + final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + jsonParserStream = + JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference); + } + + try (jsonParserStream) { + jsonParserStream.parse(jsonReader); + } catch (final IOException | JsonSyntaxException exception) { + throw new DataValidationException( + "Failed to parse json data: " + jsonData, exception.getMessage(), exception); + } catch (final IllegalStateException | IllegalArgumentException exception) { + throw new DataValidationException( + "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception + .getMessage(), exception); + } + return dataContainerNodeBuilder.build(); + } + + private ContainerNode parseXmlData(final String xmlData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + final XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(normalizedNodeResult); + + final EffectiveModelContext effectiveModelContext = (EffectiveModelContext) schemaContext; + final XmlParserStream xmlParserStream; + final String preparedXmlContent; + try { + if (parentNodeXpath.isEmpty()) { + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext); + } else { + final DataSchemaNode parentSchemaNode = + (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNode"); + final Collection dataSchemaNodeIdentifiers = + getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference); + } + + try (xmlParserStream; + StringReader stringReader = new StringReader(preparedXmlContent)) { + final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(stringReader); + xmlParserStream.parse(xmlStreamReader); + } + } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException + | ParserConfigurationException | TransformerException exception) { + throw new DataValidationException( + "Failed to parse xml data: " + xmlData, exception.getMessage(), exception); + } + final DataContainerChild dataContainerChild = + (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); + final YangInstanceIdentifier.NodeIdentifier nodeIdentifier = + new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); + return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build(); + } + + private static Collection getDataSchemaNodeIdentifiers(final SchemaContext schemaContext, + final String parentNodeXpath) { + return (Collection) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNodeIdentifiers"); + } + + private static Map getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, + final SchemaContext schemaContext) { + final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), + new ArrayList<>()); + } + + private static String[] xpathToNodeIdSequence(final String xpath) { + try { + return CpsPathUtil.getXpathNodeIdSequence(xpath); + } catch (final PathParsingException pathParsingException) { + throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), + pathParsingException); + } + } + + private static Map findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + final String[] xpathNodeIdSequence, + final Collection dataSchemaNodes, + final Collection dataSchemaNodeIdentifiers) { + final String currentXpathNodeId = xpathNodeIdSequence[0]; + final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream() + .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName())) + .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); + dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName()); + if (xpathNodeIdSequence.length <= 1) { + final Map dataSchemaNodeAndIdentifiers = + new HashMap<>(); + dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); + dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); + return dataSchemaNodeAndIdentifiers; + } + if (currentDataSchemaNode instanceof DataNodeContainer) { + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), + ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), + dataSchemaNodeIdentifiers); + } + throw schemaNodeNotFoundException(xpathNodeIdSequence[1]); + } + + private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) { + final String[] nextXpathNodeIdSequence = new String[xpathNodeIdSequence.length - 1]; + System.arraycopy(xpathNodeIdSequence, 1, nextXpathNodeIdSequence, 0, nextXpathNodeIdSequence.length); + return nextXpathNodeIdSequence; + } + + private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) { + return new DataValidationException("Invalid xpath.", + String.format("No schema node was found for xpath identifier '%s'.", schemaNodeIdentifier)); + } + + private static NormalizedNode getFirstChildXmlRoot(final NormalizedNode parent) { + final String rootNodeType = parent.getIdentifier().getNodeType().getLocalName(); + final Collection children = (Collection) parent.body(); + final Iterator iterator = children.iterator(); + NormalizedNode child = null; + while (iterator.hasNext()) { + child = iterator.next(); + if (!child.getIdentifier().getNodeType().getLocalName().equals(rootNodeType) + && !(child instanceof LeafNode)) { + return child; + } + } + return getFirstChildXmlRoot(child); + } +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java index f00f9442c..96841bf5c 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation + * Copyright (C) 2020-2024 Nordix Foundation * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -24,120 +24,16 @@ package org.onap.cps.utils; -import com.google.gson.JsonSyntaxException; -import com.google.gson.stream.JsonReader; -import java.io.IOException; -import java.io.StringReader; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.transform.TransformerException; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.cpspath.parser.CpsPathUtil; -import org.onap.cps.cpspath.parser.PathParsingException; -import org.onap.cps.spi.exceptions.DataValidationException; -import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory; -import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier; -import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream; -import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; -import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; -import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; -import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; -import org.xml.sax.SAXException; -@Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class YangUtils { - public static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; - public static final String DATA_ROOT_NODE_TAG_NAME = "data"; - - /** - * Parses data into Collection of NormalizedNode according to given schema context. - * - * @param nodeData data string - * @param schemaContext schema context describing associated data model - * @return the NormalizedNode object - */ - static ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext) { - if (contentType == ContentType.JSON) { - return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.empty()); - } - return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.empty()); - } - - /** - * Parses data into NormalizedNode according to given schema context. - * - * @param nodeData data string - * @param schemaContext schema context describing associated data model - * @return the NormalizedNode object - */ - static ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext, - final String parentNodeXpath) { - if (contentType == ContentType.JSON) { - return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath)); - } - return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath)); - } - - /** - * Parses data into Collection of NormalizedNode according to given schema context. - * - * @param jsonData json data as string - * @param schemaContext schema context describing associated data model - * @return the Collection of NormalizedNode object - */ - public static ContainerNode parseJsonData(final String jsonData, final SchemaContext schemaContext) { - return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.empty()); - } - - /** - * Parses jsonData into Collection of NormalizedNode according to given schema context. - * - * @param jsonData json data fragment as string - * @param schemaContext schema context describing associated data model - * @param parentNodeXpath the xpath referencing the parent node current data fragment belong to - * @return the NormalizedNode object - */ - public static ContainerNode parseJsonData(final String jsonData, - final SchemaContext schemaContext, - final String parentNodeXpath) { - return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.of(parentNodeXpath)); - } - /** * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument). * @@ -155,99 +51,6 @@ public class YangUtils { return xpathBuilder.toString(); } - private static ContainerNode parseJsonDataWithOptionalParent(final String jsonData, - final SchemaContext schemaContext, - final Optional parentNodeXpath) { - final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 - .getShared((EffectiveModelContext) schemaContext); - final DataContainerNodeBuilder dataContainerNodeBuilder = - Builders.containerBuilder() - .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier( - QName.create(DATA_ROOT_NODE_NAMESPACE, DATA_ROOT_NODE_TAG_NAME) - )); - final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter - .from(dataContainerNodeBuilder); - final JsonReader jsonReader = new JsonReader(new StringReader(jsonData)); - final JsonParserStream jsonParserStream; - - if (parentNodeXpath.isPresent()) { - final Collection dataSchemaNodeIdentifiers - = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get()); - final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); - final EffectiveStatementInference effectiveStatementInference = - SchemaInferenceStack.of(effectiveModelContext, - SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); - jsonParserStream = - JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference); - } else { - jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory); - } - - try (jsonParserStream) { - jsonParserStream.parse(jsonReader); - } catch (final IOException | JsonSyntaxException exception) { - throw new DataValidationException( - "Failed to parse json data: " + jsonData, exception.getMessage(), exception); - } catch (final IllegalStateException | IllegalArgumentException exception) { - throw new DataValidationException( - "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception - .getMessage(), exception); - } - return dataContainerNodeBuilder.build(); - } - - private static ContainerNode parseXmlDataWithOptionalParent(final String xmlData, - final SchemaContext schemaContext, - final Optional parentNodeXpath) { - final XMLInputFactory factory = XMLInputFactory.newInstance(); - factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); - final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult(); - final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter - .from(normalizedNodeResult); - - final EffectiveModelContext effectiveModelContext = (EffectiveModelContext) schemaContext; - final XmlParserStream xmlParserStream; - final String preparedXmlContent; - try { - if (parentNodeXpath.isPresent()) { - final DataSchemaNode parentSchemaNode = - (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath.get(), schemaContext) - .get("dataSchemaNode"); - final Collection dataSchemaNodeIdentifiers = - getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get()); - final EffectiveStatementInference effectiveStatementInference = - SchemaInferenceStack.of(effectiveModelContext, - SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); - preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath.get()); - xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference); - } else { - preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext); - xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext); - } - - try (xmlParserStream; - StringReader stringReader = new StringReader(preparedXmlContent)) { - final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(stringReader); - xmlParserStream.parse(xmlStreamReader); - } - } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException - | ParserConfigurationException | TransformerException exception) { - throw new DataValidationException( - "Failed to parse xml data: " + xmlData, exception.getMessage(), exception); - } - final DataContainerChild dataContainerChild = - (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); - final YangInstanceIdentifier.NodeIdentifier nodeIdentifier = - new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); - return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build(); - } - - private static Collection getDataSchemaNodeIdentifiers(final SchemaContext schemaContext, - final String parentNodeXpath) { - return (Collection) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) - .get("dataSchemaNodeIdentifiers"); - } - private static String getKeyAttributesStatement( final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) { final List keyAttributes = nodeIdentifier.entrySet().stream().map( @@ -266,70 +69,4 @@ public class YangUtils { } } - private static Map getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, - final SchemaContext schemaContext) { - final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); - return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), - new ArrayList<>()); - } - - private static String[] xpathToNodeIdSequence(final String xpath) { - try { - return CpsPathUtil.getXpathNodeIdSequence(xpath); - } catch (final PathParsingException pathParsingException) { - throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), - pathParsingException); - } - } - - private static Map findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( - final String[] xpathNodeIdSequence, - final Collection dataSchemaNodes, - final Collection dataSchemaNodeIdentifiers) { - final String currentXpathNodeId = xpathNodeIdSequence[0]; - final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream() - .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName())) - .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); - dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName()); - if (xpathNodeIdSequence.length <= 1) { - final Map dataSchemaNodeAndIdentifiers = - new HashMap<>(); - dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); - dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); - return dataSchemaNodeAndIdentifiers; - } - if (currentDataSchemaNode instanceof DataNodeContainer) { - return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( - getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), - ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), - dataSchemaNodeIdentifiers); - } - throw schemaNodeNotFoundException(xpathNodeIdSequence[1]); - } - - private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) { - final String[] nextXpathNodeIdSequence = new String[xpathNodeIdSequence.length - 1]; - System.arraycopy(xpathNodeIdSequence, 1, nextXpathNodeIdSequence, 0, nextXpathNodeIdSequence.length); - return nextXpathNodeIdSequence; - } - - private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) { - return new DataValidationException("Invalid xpath.", - String.format("No schema node was found for xpath identifier '%s'.", schemaNodeIdentifier)); - } - - private static NormalizedNode getFirstChildXmlRoot(final NormalizedNode parent) { - final String rootNodeType = parent.getIdentifier().getNodeType().getLocalName(); - final Collection children = (Collection) parent.body(); - final Iterator iterator = children.iterator(); - NormalizedNode child = null; - while (iterator.hasNext()) { - child = iterator.next(); - if (!child.getIdentifier().getNodeType().getLocalName().equals(rootNodeType) - && !(child instanceof LeafNode)) { - return child; - } - } - return getFirstChildXmlRoot(child); - } } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index 322d2c915..b2b2d7d44 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -34,28 +34,26 @@ import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.SessionManagerException import org.onap.cps.spi.exceptions.SessionTimeoutException import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType -import org.onap.cps.utils.TimedYangParser +import org.onap.cps.utils.YangParser +import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import spock.lang.Shared import spock.lang.Specification import java.time.OffsetDateTime -import java.util.stream.Collectors class CpsDataServiceImplSpec extends Specification { def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) def mockCpsAnchorService = Mock(CpsAnchorService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) def mockCpsValidator = Mock(CpsValidator) - def timedYangParser = new TimedYangParser() + def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache) def mockCpsDeltaService = Mock(CpsDeltaService); - def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, - mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService) + def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) def setup() { mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy index a4f433973..e21c6f0e2 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy @@ -21,7 +21,6 @@ package org.onap.cps.api.impl import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Shared import spock.lang.Specification @@ -29,38 +28,81 @@ class CpsDeltaServiceImplSpec extends Specification{ def objectUnderTest = new CpsDeltaServiceImpl() - @Shared - def dataNodeWithLeafAndChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload']) - .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").withLeaves('child-leaf': 'child-payload').build()]).build()] - @Shared - def dataNodeWithChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload']) - .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()] - @Shared - def emptyDataNode = [new DataNodeBuilder().withXpath('/parent').build()] - def 'Get delta between data nodes for removed data where source data node has #scenario'() { + static def sourceDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-source'])] + static def sourceDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')] + static def targetDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-target'])] + static def targetDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')] + static def sourceDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'])] + static def targetDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target'])] + + def 'Get delta between data nodes for REMOVED data where source data node has #scenario'() { + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports(sourceDataNodeWithLeafData, []) + then: 'the delta report contains expected "remove" action' + assert result[0].action.equals('remove') + and : 'the delta report contains the expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains expected source data' + assert result[0].sourceData == ['parent-leaf': 'parent-payload-in-source'] + and: 'the delta report contains no target data' + assert result[0].targetData == null + } + + def 'Get delta between data nodes with ADDED data where target data node has #scenario'() { + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports([], targetDataNodeWithLeafData) + then: 'the delta report contains expected "add" action' + assert result[0].action.equals('add') + and: 'the delta report contains expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains no source data' + assert result[0].sourceData == null + and: 'the delta report contains expected target data' + assert result[0].targetData == ['parent-leaf': 'parent-payload-in-target'] + } + + def 'Delta Report between leaves for parent and child nodes, #scenario'() { + given: 'Two data nodes' + def sourceDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload'])])] + def targetDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-updated'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload-updated'])])] + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) + then: 'the delta report contains expected "update" action' + assert result[index].action.equals('update') + and: 'the delta report contains expected xpath' + assert result[index].xpath == expectedXpath + and: 'the delta report contains expected source and target data' + assert result[index].sourceData == expectedSourceData + assert result[index].targetData == expectedTargetData + where: 'the following data was used' + scenario | index || expectedXpath | expectedSourceData | expectedTargetData + 'parent data node' | 0 || '/parent' | ['parent-leaf': 'parent-payload'] | ['parent-leaf': 'parent-payload-updated'] + 'child data node' | 1 || '/parent/child' | ['child-leaf': 'child-payload'] | ['child-leaf': 'child-payload-updated'] + } + + def 'Delta report between leaves, #scenario'() { when: 'attempt to get delta between 2 data nodes' - def result = objectUnderTest.getDeltaReports(sourceDataNode as Collection, emptyDataNode) - then: 'the delta report contains "remove" action with right data' - assert result.first().action.equals("remove") - assert result.first().xpath == "/parent/child" - assert result.first().sourceData == expectedSourceData - where: 'following data was used' - scenario | sourceDataNode || expectedSourceData - 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload'] - 'no leaf data' | dataNodeWithChildDataNode || null + def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) + then: 'the delta report contains expected "update" action' + assert result[0].action.equals('update') + and: 'the delta report contains expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains expected source and target data' + assert result[0].sourceData == expectedSourceData + assert result[0].targetData == expectedTargetData + where: 'the following data was used' + scenario | sourceDataNode | targetDataNode || expectedSourceData | expectedTargetData + 'source and target data nodes have leaves' | sourceDataNodeWithLeafData | targetDataNodeWithLeafData || ['parent-leaf': 'parent-payload-in-source'] | ['parent-leaf': 'parent-payload-in-target'] + 'only source data node has leaves' | sourceDataNodeWithLeafData | targetDataNodeWithoutLeafData || ['parent-leaf': 'parent-payload-in-source'] | null + 'only target data node has leaves' | sourceDataNodeWithoutLeafData | targetDataNodeWithLeafData || null | ['parent-leaf': 'parent-payload-in-target'] + 'source and target dsta node with multiple leaves' | sourceDataNodeWithMultipleLeaves | targetDataNodeWithMultipleLeaves || ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'] | ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target'] } - def 'Get delta between data nodes with new data where target data node has #scenario'() { + def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() { when: 'attempt to get delta between 2 data nodes' - def result = objectUnderTest.getDeltaReports(emptyDataNode, targetDataNode) - then: 'the delta report contains "add" action with right data' - assert result.first().action.equals("add") - assert result.first().xpath == "/parent/child" - assert result.first().targetData == expectedTargetData - where: 'following data was used' - scenario | targetDataNode || expectedTargetData - 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload'] - 'no leaf data' | dataNodeWithChildDataNode || null + def result = objectUnderTest.getDeltaReports(sourceDataNodeWithoutLeafData, targetDataNodeWithoutLeafData) + then: 'the delta report contains "update" action with right data' + assert result.isEmpty() } } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 4782468f1..140dfaac9 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation. + * Copyright (C) 2021-2024 Nordix Foundation. * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. @@ -27,12 +27,12 @@ import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDeltaService import org.onap.cps.spi.CpsDataPersistenceService -import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.utils.CpsValidator -import org.onap.cps.utils.TimedYangParser -import org.onap.cps.utils.YangUtils +import org.onap.cps.utils.ContentType +import org.onap.cps.utils.YangParser +import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import spock.lang.Specification @@ -44,14 +44,13 @@ class E2ENetworkSliceSpec extends Specification { def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) def mockCpsValidator = Mock(CpsValidator) def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder() - def timedYangParser = new TimedYangParser() + def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache) def mockCpsDeltaService = Mock(CpsDeltaService) def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService, mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder) - def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService, - mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService) + def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) def dataspaceName = 'someDataspace' def anchorName = 'someAnchor' @@ -165,6 +164,6 @@ class E2ENetworkSliceSpec extends Specification { expect: 'schema context is built with no exception indicating the schema set being valid ' def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext() and: 'data is parsed with no exception indicating the model match' - YangUtils.parseJsonData(jsonData, schemaContext) != null + new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null } } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index fcbae628e..e305abee8 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2023 Nordix Foundation. + * Modifications Copyright (C) 2021-2024 Nordix Foundation. * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,9 @@ package org.onap.cps.spi.model import org.onap.cps.TestUtils import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.utils.ContentType import org.onap.cps.utils.DataMapUtils -import org.onap.cps.utils.YangUtils +import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode @@ -33,6 +34,7 @@ import spock.lang.Specification class DataNodeBuilderSpec extends Specification { def objectUnderTest = new DataNodeBuilder() + def yangParserHelper = new YangParserHelper() def expectedLeavesByXpathMap = [ '/test-tree' : [], @@ -58,7 +60,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('test-tree.json') - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -78,7 +80,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree") + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree') when: 'the container node is converted to a data node with parent node xpath defined' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -94,7 +96,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data parsed into container node object' def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') when: 'the container node is converted to a data node ' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -127,7 +129,7 @@ class DataNodeBuilderSpec extends Specification { def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']" and: 'the json data fragment parsed into container node object for given parent node xpath' def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath) when: 'the container node is converted to a data node with given parent node xpath' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() then: 'the resulting data node represents a child of augmentation node' @@ -142,7 +144,7 @@ class DataNodeBuilderSpec extends Specification { def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'the json data fragment parsed into container node object' def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '') when: 'the container node is converted to a data node' def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) @@ -160,7 +162,7 @@ class DataNodeBuilderSpec extends Specification { and: 'parent node xpath referencing parent of list element' def parentNodeXpath = '/test-tree' and: 'the json data fragment (list element) parsed into container node object' - def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath) + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath) when: 'the container node is converted to a data node collection' def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() def resultXpaths = result.collect { it.getXpath() } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy index c100ea31d..7e211deb7 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy @@ -1,13 +1,32 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + package org.onap.cps.utils import com.google.gson.stream.JsonReader import org.onap.cps.TestUtils import spock.lang.Specification +class GsonSpec extends Specification { -class GsonSpec extends Specification{ - - def 'Iterate over JSON data with gson JsonReader'(){ + def 'Iterate over JSON data with gson JsonReader'() { given: 'json data with two objects and JSON reader' def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json') def objectUnderTest = new JsonReader(new StringReader(jsonData)); @@ -17,7 +36,7 @@ class GsonSpec extends Specification{ noExceptionThrown() } - def iterateWithJsonReader(JsonReader jsonReader){ + def iterateWithJsonReader(JsonReader jsonReader) { switch(jsonReader.peek()) { case "STRING": print(jsonReader.nextString() + " ") @@ -36,5 +55,4 @@ class GsonSpec extends Specification{ } } - } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy index 3864a5253..dc6027de2 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG - * Modifications Copyright (c) 2023 Nordix Foundation + * Modifications Copyright (c) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ class XmlFileUtilsSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() and: 'Parent schema node by xPath' - def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get("dataSchemaNode") + def parentSchemaNode = YangParserHelper.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get('dataSchemaNode') when: 'the XML data is parsed' def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, xPath) then: 'the result XML is wrapped by xPath defined parent root node' diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy new file mode 100644 index 000000000..073383113 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy @@ -0,0 +1,166 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.utils + +import org.onap.cps.TestUtils +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.opendaylight.yangtools.yang.common.QName +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode +import spock.lang.Specification + +class YangParserHelperSpec extends Specification { + + def objectUnderTest = new YangParserHelper() + + def 'Parsing a valid multicontainer Json String.'() { + given: 'a yang model (file)' + def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json') + and: 'a model for that data' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'the json data is parsed' + def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '') + then: 'a ContainerNode holding collection of normalized nodes is returned' + result.body().getAt(index) instanceof NormalizedNode == true + then: 'qualified name of children created is as expected' + result.body().getAt(index).getIdentifier().nodeType == QName.create('org:onap:ccsdk:multiDataTree', '2020-09-15', nodeName) + where: + index | nodeName + 0 | 'first-container' + 1 | 'last-container' + } + + def 'Parsing a valid #scenario String.'() { + given: 'a yang model (file)' + def fileData = TestUtils.getResourceFileContent(contentFile) + and: 'a model for that data' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'the data is parsed' + NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '') + then: 'the result is a normalized node of the correct type' + if (revision) { + result.identifier.nodeType == QName.create(namespace, revision, localName) + } else { + result.identifier.nodeType == QName.create(namespace, localName) + } + where: + scenario | contentFile | contentType | namespace | revision | localName + 'JSON' | 'bookstore.json' | ContentType.JSON | 'org:onap:ccsdk:sample' | '2020-09-15' | 'bookstore' + 'XML' | 'bookstore.xml' | ContentType.XML | 'urn:ietf:params:xml:ns:netconf:base:1.0' | '' | 'bookstore' + } + + def 'Parsing invalid data: #description.'() { + given: 'a yang model (file)' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'invalid data is parsed' + objectUnderTest.parseData(contentType, invalidData, schemaContext, '') + then: 'an exception is thrown' + thrown(DataValidationException) + where: 'the following invalid data is provided' + invalidData | contentType | description + '{incomplete json' | ContentType.JSON | 'incomplete json' + '{"test:bookstore": {"address": "Parnell st." }}' | ContentType.JSON | 'json with un-modelled data' + '{" }' | ContentType.JSON | 'json with syntax exception' + '' | ContentType.XML | 'incomplete xml' + 'blabla' | ContentType.XML | 'xml with invalid model' + '' | ContentType.XML | 'empty xml' + } + + def 'Parsing data fragment by xpath for #scenario.'() { + given: 'schema context' + def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() + when: 'json string is parsed' + def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath) + then: 'a ContainerNode holding collection of normalized nodes is returned' + result.body().getAt(0) instanceof NormalizedNode == true + then: 'result represents a node of expected type' + result.body().getAt(0).getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName) + where: + scenario | contentType | nodeData | parentNodeXpath || nodeName + 'JSON list element as container' | ContentType.JSON | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch' + 'JSON list element within list' | ContentType.JSON | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch' + 'JSON container element' | ContentType.JSON | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest' + 'XML element test tree' | ContentType.XML | 'LeftSmallSparrow' | '/test-tree' || 'branch' + 'XML element branch xpath' | ContentType.XML | 'LeftSmallSparrowRobin' | '/test-tree' || 'branch' + 'XML container element' | ContentType.XML | 'SmallSparrow' | '/test-tree/branch[@name=\'Branch\']' || 'nest' + } + + def 'Parsing json data fragment by xpath error scenario: #scenario.'() { + given: 'schema context' + def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() + when: 'json string is parsed' + objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath) + then: 'expected exception is thrown' + thrown(DataValidationException) + where: + scenario | parentNodeXpath + 'xpath has no identifiers' | '/' + 'xpath has no valid identifiers' | '/[@name=\'Name\']' + 'invalid parent path' | '/test-bush' + 'another invalid parent path' | '/test-tree/branch[@name=\'Branch\']/nest/name/last-name' + 'fragment does not belong to parent' | '/test-tree/' + } + + def 'Parsing json data with invalid json string: #description.'() { + given: 'schema context' + def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() + when: 'malformed json string is parsed' + objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '') + then: 'an exception is thrown' + thrown(DataValidationException) + where: 'the following malformed json is provided' + description | invalidJson + 'malformed json string with unterminated array data' | '{bookstore={categories=[{books=[{authors=[Iain M. Banks]}]}]}}' + 'incorrect json' | '{" }' + } + + def 'Parsing json data with space.'() { + given: 'schema context' + def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() + and: 'some json data with space in the array elements' + def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json') + when: 'that json data is parsed' + objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '') + then: 'no exception thrown' + noExceptionThrown() + } + + def 'Converting xPath to nodeId for #scenario.'() { + when: 'xPath is parsed' + def result = objectUnderTest.xpathToNodeIdSequence(xPath) + then: 'result represents an array of expected identifiers' + assert result == expectedNodeIdentifier + where: 'the following parameters are used' + scenario | xPath || expectedNodeIdentifier + 'container xpath' | '/test-tree' || ['test-tree'] + 'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch'] + 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories'] + } + + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy new file mode 100644 index 000000000..99070fe72 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.utils + +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.Anchor +import org.onap.cps.yang.YangTextSchemaSourceSet +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode +import org.opendaylight.yangtools.yang.model.api.SchemaContext +import spock.lang.Specification +import org.onap.cps.api.impl.YangTextSchemaSourceSetCache + +class YangParserSpec extends Specification { + + def mockYangParserHelper = Mock(YangParserHelper) + def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) + + def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache) + + def anchor = new Anchor(dataspaceName: 'my dataspace', schemaSetName: 'my schema') + def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) + def mockSchemaContext = Mock(SchemaContext) + def containerNodeFromYangUtils = Mock(ContainerNode) + + def noParent = '' + + def setup() { + mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet + mockYangTextSchemaSourceSet.getSchemaContext() >> mockSchemaContext + } + + def 'Parsing data.'() { + given: 'the yang parser (utility) always returns a container node' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils + when: 'parsing some json data' + def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) + then: 'the schema source set for the correct dataspace and schema set is retrieved form the cache' + 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet + and: 'the result is the same container node as return from yang utils' + assert result == containerNodeFromYangUtils + and: 'nothing is removed from the cache' + 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_) + } + + def 'Parsing data with exception on first attempt.'() { + given: 'the yang parser throws an exception on the first attempt only' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils + when: 'attempt to parse some data' + def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) + then: 'the cache is cleared for the correct dataspace and schema' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + and: 'the result is the same container node as return from yang utils (no exception thrown!)' + assert result == containerNodeFromYangUtils + } + + def 'Parsing data with exception on all attempts.'() { + given: 'the yang parser always throws an exception' + mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } + when: 'attempt to parse some data' + objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the cache is cleared for the correct dataspace and schema (but that did not help)' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema') + } + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy index e6344d303..3852bae57 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation + * Copyright (C) 2020-2024 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG @@ -23,146 +23,10 @@ package org.onap.cps.utils -import org.onap.cps.TestUtils -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder -import org.opendaylight.yangtools.yang.common.QName import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode import spock.lang.Specification class YangUtilsSpec extends Specification { - def 'Parsing a valid multicontainer Json String.'() { - given: 'a yang model (file)' - def jsonData = org.onap.cps.TestUtils.getResourceFileContent('multiple-object-data.json') - and: 'a model for that data' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - when: 'the json data is parsed' - def result = YangUtils.parseJsonData(jsonData, schemaContext) - then: 'a ContainerNode holding collection of normalized nodes is returned' - result.body().getAt(index) instanceof NormalizedNode == true - then: 'qualified name of children created is as expected' - result.body().getAt(index).getIdentifier().nodeType == QName.create('org:onap:ccsdk:multiDataTree', '2020-09-15', nodeName) - where: - index | nodeName - 0 | 'first-container' - 1 | 'last-container' - } - - def 'Parsing a valid #scenario String.'() { - given: 'a yang model (file)' - def fileData = org.onap.cps.TestUtils.getResourceFileContent(contentFile) - and: 'a model for that data' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - when: 'the data is parsed' - NormalizedNode result = YangUtils.parseData(contentType, fileData, schemaContext) - then: 'the result is a normalized node of the correct type' - if (revision) { - result.identifier.nodeType == QName.create(namespace, revision, localName) - } else { - result.identifier.nodeType == QName.create(namespace, localName) - } - where: - scenario | contentFile | contentType | namespace | revision | localName - 'JSON' | 'bookstore.json' | ContentType.JSON | 'org:onap:ccsdk:sample' | '2020-09-15' | 'bookstore' - 'XML' | 'bookstore.xml' | ContentType.XML | 'urn:ietf:params:xml:ns:netconf:base:1.0' | '' | 'bookstore' - } - - def 'Parsing invalid data: #description.'() { - given: 'a yang model (file)' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - when: 'invalid data is parsed' - YangUtils.parseData(contentType, invalidData, schemaContext) - then: 'an exception is thrown' - thrown(DataValidationException) - where: 'the following invalid data is provided' - invalidData | contentType | description - '{incomplete json' | ContentType.JSON | 'incomplete json' - '{"test:bookstore": {"address": "Parnell st." }}' | ContentType.JSON | 'json with un-modelled data' - '{" }' | ContentType.JSON | 'json with syntax exception' - '' | ContentType.XML | 'incomplete xml' - 'blabla' | ContentType.XML | 'xml with invalid model' - '' | ContentType.XML | 'empty xml' - } - - def 'Parsing data fragment by xpath for #scenario.'() { - given: 'schema context' - def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() - when: 'json string is parsed' - def result = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath) - then: 'a ContainerNode holding collection of normalized nodes is returned' - result.body().getAt(0) instanceof NormalizedNode == true - then: 'result represents a node of expected type' - result.body().getAt(0).getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName) - where: - scenario | contentType | nodeData | parentNodeXpath || nodeName - 'JSON list element as container' | ContentType.JSON | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch' - 'JSON list element within list' | ContentType.JSON | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch' - 'JSON container element' | ContentType.JSON | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest' - 'XML element test tree' | ContentType.XML | 'LeftSmallSparrow' | '/test-tree' || 'branch' - 'XML element branch xpath' | ContentType.XML | 'LeftSmallSparrowRobin' | '/test-tree' || 'branch' - 'XML container element' | ContentType.XML | 'SmallSparrow' | '/test-tree/branch[@name=\'Branch\']' || 'nest' - } - - def 'Parsing json data fragment by xpath error scenario: #scenario.'() { - given: 'schema context' - def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() - when: 'json string is parsed' - YangUtils.parseJsonData('{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, - parentNodeXpath) - then: 'expected exception is thrown' - thrown(DataValidationException) - where: - scenario | parentNodeXpath - 'xpath has no identifiers' | '/' - 'xpath has no valid identifiers' | '/[@name=\'Name\']' - 'invalid parent path' | '/test-bush' - 'another invalid parent path' | '/test-tree/branch[@name=\'Branch\']/nest/name/last-name' - 'fragment does not belong to parent' | '/test-tree/' - } - - def 'Parsing json data with invalid json string: #description.'() { - given: 'schema context' - def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() - when: 'malformed json string is parsed' - YangUtils.parseJsonData(invalidJson, schemaContext) - then: 'an exception is thrown' - thrown(DataValidationException) - where: 'the following malformed json is provided' - description | invalidJson - 'malformed json string with unterminated array data' | '{bookstore={categories=[{books=[{authors=[Iain M. Banks]}]}]}}' - 'incorrect json' | '{" }' - } - - def 'Parsing json data with space.'() { - given: 'schema context' - def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext() - and: 'some json data with space in the array elements' - def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json') - when: 'that json data is parsed' - YangUtils.parseJsonData(jsonDataWithSpacesInArrayElement, schemaContext) - then: 'no exception thrown' - noExceptionThrown() - } - - def 'Parsing xPath to nodeId for #scenario.'() { - when: 'xPath is parsed' - def result = YangUtils.xpathToNodeIdSequence(xPath) - then: 'result represents an array of expected identifiers' - assert result == expectedNodeIdentifier - where: 'the following parameters are used' - scenario | xPath || expectedNodeIdentifier - 'container xpath' | '/test-tree' || ['test-tree'] - 'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch'] - 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories'] - } def 'Get key attribute statement without key attributes'() { given: 'a path argument without key attributes' diff --git a/csit/data/subscription-notification/cmSubscriptionNcmpOutEventForCsit.json b/csit/data/subscription-notification/cmSubscriptionNcmpOutEventForCsit.json deleted file mode 100644 index 71fee37b2..000000000 --- a/csit/data/subscription-notification/cmSubscriptionNcmpOutEventForCsit.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "data":{ - "statusCode":1, - "statusMessage":"successfully applied subscription", - "additionalInfo":{ - "rejected":[], - "pending":[] - } - } -} \ No newline at end of file diff --git a/csit/plans/cps/testplanNcmp.txt b/csit/plans/cps/testplanNcmp.txt index 81c546861..e80f0d98a 100644 --- a/csit/plans/cps/testplanNcmp.txt +++ b/csit/plans/cps/testplanNcmp.txt @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2023 Nordix Foundation +# Copyright (C) 2023-2024 Nordix Foundation # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ # Place the suites in run order. cps-model-sync cps-data-sync -cps-subscriptions ncmp-passthrough cm-handle-query cps-trust-level diff --git a/csit/tests/cps-subscriptions/cps-subscription-notification.robot b/csit/tests/cps-subscriptions/cps-subscription-notification.robot deleted file mode 100644 index 2d5152a29..000000000 --- a/csit/tests/cps-subscriptions/cps-subscription-notification.robot +++ /dev/null @@ -1,90 +0,0 @@ -# ============LICENSE_START======================================================= -# Copyright (C) 2023 Nordix Foundation. -# ================================================================================ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# ============LICENSE_END========================================================= - - -*** Settings *** -Library ConfluentKafkaLibrary -Library Collections -Library OperatingSystem -Library RequestsLibrary - -Suite Setup Starting Test - -*** Variables *** - -${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE= -${basePath} /ncmpInventory/v1/ch - - -*** Test Cases *** -Create CM Handle - Create Session CPS_URL http://${CPS_CORE_HOST}:${CPS_CORE_PORT} - ${headers} Create Dictionary Content-Type=application/json Authorization=${auth} - ${jsonData}= Get Binary File ${DATADIR_SUBS_NOTIFICATION}${/}createCmHandleRequestBody.json - ${response}= POST On Session CPS_URL ${basePath} headers=${headers} data=${jsonData} - Should Be Equal As Strings ${response.status_code} 200 - Sleep 5 wait some time to get updated in the db - -Verify Kafka flow for Subscription Creation Notification - ${group_id}= Create Consumer - Subscribe Topic group_id=${group_id} topics=${RESPONSE_TOPIC} - Wait Until Keyword Succeeds 10x 3s All Messages Are Produced and Consumed ${group_id} - [Teardown] Basic Teardown ${group_id} - -*** Keywords *** -Starting Test - Set Suite Variable ${REQUEST_TOPIC} subscription - Set Suite Variable ${RESPONSE_TOPIC} subscription-response - ${ncmpOutEventJson}= Get File ${DATADIR_SUBS_NOTIFICATION}${/}cmSubscriptionNcmpOutEventForCsit.json - ${ncmpOutEventJson}= Evaluate json.loads("""${ncmpOutEventJson}""") json - Set Suite Variable ${ncmpOutEventJsonGlobal} ${ncmpOutEventJson} - ${thread}= Start Consumer Threaded topics=test - Set Suite Variable ${MAIN_THREAD} ${thread} - ${producer_group_id}= Create Producer - Set Suite Variable ${PRODUCER_ID} ${producer_group_id} - ${ncmpInEventJson}= Get File ${DATADIR_SUBS_NOTIFICATION}${/}cmSubscriptionNcmpInEventForCsit.json encoding=UTF-8 - Set Suite Variable ${ncmpInEventJsonGlobal} ${ncmpInEventJson} - ${headers}= Create Dictionary ce_specversion=1.0 ce_id=some-event-id ce_source=some-resource ce_type=subscriptionCreated ce_correlationid=test-cmhandle1 - Set Suite Variable ${headersGlobal} ${headers} - -All Messages Are Produced and Consumed - [Arguments] ${GROUP_ID} - Produce group_id=${PRODUCER_ID} topic=${REQUEST_TOPIC} value=${ncmpInEventJsonGlobal} headers=${headersGlobal} - Sleep 10sec - ${result}= Poll group_id=${GROUP_ID} only_value=False - ${headers} Set Variable ${result[0].headers()} - ${value} Set Variable ${result[0].value()} - ${valueAsDict}= Evaluate json.loads("""${value}""") json - FOR ${header_key_value_pair} IN @{headers} - Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_specversion" "1.0" - Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_source" "NCMP" - Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_type" "subscriptionCreatedStatus" - Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_correlationid" "SCO-9989752cm-subscription-001" - END - Dictionaries Should Be Equal ${valueAsDict} ${ncmpOutEventJsonGlobal} - -Compare Header Values - [Arguments] ${header_key} ${header_value} ${header_to_check} ${expected_header_value} - IF "${header_key}" == ${header_to_check} - Should Be Equal As Strings "${header_value}" ${expected_header_value} - END - -Basic Teardown - [Arguments] ${group_id} - Unsubscribe ${group_id} - Close Consumer ${group_id} \ No newline at end of file diff --git a/csit/tests/cps-trust-level/cps-trust-level.robot b/csit/tests/cps-trust-level/cps-trust-level.robot index 5a16a343e..70659000c 100644 --- a/csit/tests/cps-trust-level/cps-trust-level.robot +++ b/csit/tests/cps-trust-level/cps-trust-level.robot @@ -37,17 +37,18 @@ ${dmiUrl} http://${DMI_HOST}:${DMI_PORT} ${jsonCreateCmHandles} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"trustLevel":"COMPLETE","cmHandle":"CH-1"},{"trustLevel":"COMPLETE","cmHandle":"CH-2"},{"cmHandle":"CH-3"},{"trustLevel":"NONE","cmHandle":"CH-4"}]} ${jsonTrustLevelPropertyQueryParameters} {"cmHandleQueryParameters": [{"conditionName": "cmHandleWithTrustLevel", "conditionParameters": [ {"trustLevel": "COMPLETE"} ] }]} ${jsonTrustLevelQueryResponse} {"data":{"attributeValueChange":[{"attributeName":"trustLevel","newAttributeValue":"NONE"}]}} -${partitionId} ${0} *** Test Cases *** -Register data node and verify notification - ${group_id}= Create Consumer - ${topic_partition}= Create Topic Partition cm-events ${partitionId} - ${offset}= Get Watermark Offsets ${group_id} ${topic_partition} - ${tp}= Create Topic Partition cm-events ${partitionId} ${offset[1]} - Assign To Topic Partition ${group_id} ${tp} - Sleep 5sec - Register Data Nodes +Register data node + ${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch + ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} + ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonCreateCmHandles} + Should Be Equal As Strings ${response.status_code} 200 + Sleep 5 + +Verify notification + ${group_id}= Create Consumer auto_offset_reset=earliest + Subscribe Topic topics=cm-events group_id=${group_id} ${result}= Poll group_id=${group_id} only_value=False poll_attempts=5 ${headers} Set Variable ${result[0].headers()} ${payload} Set Variable ${result[0].value()} @@ -72,11 +73,6 @@ Retrieve CM Handle ids where query parameters Match (trust level query) Should Not Contain ${responseJson} CH-4 *** Keywords *** -Register Data Nodes - ${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch - ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} - ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonCreateCmHandles} - Should Be Equal As Strings ${response.status_code} 200 Compare Header Values [Arguments] ${header_key} ${header_value} ${header_to_check} ${expected_header_value} diff --git a/docs/deployment.rst b/docs/deployment.rst index 8a9c8700b..7ba163d57 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -327,8 +327,6 @@ Below are the list of distributed datastructures that we have. +--------------+---------------------------------+----------------------------------------------------------+ | cps-ncmp | moduleSyncWorkQueue | Queue used internally for workers to pick the task. | +--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | forwardedSubscriptionEventCache | Keeps track of the LCM Subscription Events. | -+--------------+---------------------------------+----------------------------------------------------------+ | cps-ncmp | untrustworthyCmHandlesSet | Stores untrustworthy cmHandles whose TrustLevel is NONE. | +--------------+---------------------------------+----------------------------------------------------------+ | cps-ncmp | trustLevelPerDmiPlugin | Stores the TrustLevel for the dmi-plugins. | @@ -338,4 +336,4 @@ Below are the list of distributed datastructures that we have. | cps-ncmp | cmSubscriptionEventCache | Stores and tracks CmSubscription requests. | +--------------+---------------------------------+----------------------------------------------------------+ -Total number of caches : 9 +Total number of caches : 8 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy index b10194560..20687c3ea 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); @@ -75,7 +75,7 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase { def anchorName = 'bookstoreAnchor' + anchorNumber cpsAnchorService.deleteAnchor(FUNCTIONAL_TEST_DATASPACE_1, anchorName) cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, anchorName) - cpsDataService.saveData(FUNCTIONAL_TEST_DATASPACE_1, anchorName, bookstoreJsonData.replace("Easons", "Easons-"+anchorNumber.toString()), OffsetDateTime.now()) + cpsDataService.saveData(FUNCTIONAL_TEST_DATASPACE_1, anchorName, bookstoreJsonData.replace('Easons', 'Easons-'+anchorNumber.toString()), OffsetDateTime.now()) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy index e1ccdaa6e..69c2d1744 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ package org.onap.cps.integration.base import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.api.impl.YangTextSchemaSourceSetCache import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.impl.CpsAdminPersistenceServiceImpl @@ -35,7 +36,8 @@ import org.onap.cps.spi.repository.YangResourceRepository import org.onap.cps.spi.utils.SessionManager import org.onap.cps.spi.utils.TimeLimiterProvider import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.utils.TimedYangParser +import org.onap.cps.utils.YangParser +import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean @@ -77,6 +79,15 @@ class TestConfig extends Specification{ @Lazy SessionManager sessionManager + @Autowired + @Lazy + YangParserHelper yangParserHelper + + @Autowired + @Lazy + YangTextSchemaSourceSetCache YangTextSchemaSourceSetCache + + @Bean CpsAdminPersistenceServiceImpl cpsAdminPersistenceService() { new CpsAdminPersistenceServiceImpl(dataspaceRepository, anchorRepository, schemaSetRepository, yangResourceRepository) @@ -98,8 +109,13 @@ class TestConfig extends Specification{ } @Bean - TimedYangParser timedYangParser() { - return new TimedYangParser() + YangParserHelper yangParserHelper() { + return new YangParserHelper() + } + + @Bean + YangParser yangParser() { + return new YangParser(yangParserHelper, yangTextSchemaSourceSetCache) } @Bean 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 e14309994..3843a9f1b 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 @@ -431,40 +431,30 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between 2 anchors for when #scenario'() { when: 'attempt to get delta report between anchors' - def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, xpath, fetchDescendantOption) + def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS) then: 'delta report contains expected number of changes' - result.size() == 2 - and: 'delta report contains expected action' - assert result.get(index).getAction() == expectedActions - and: 'delta report contains expected xpath' - assert result.get(index).getXpath() == expectedXpath - where: 'following data was used' - scenario | index | xpath || expectedActions || expectedXpath | fetchDescendantOption - 'a node is removed' | 0 | '/' || 'remove' || "/bookstore-address[@bookstore-name='Easons-1']" | OMIT_DESCENDANTS - 'a node is added' | 1 | '/' || 'add' || "/bookstore-address[@bookstore-name='Crossword Bookstores']" | OMIT_DESCENDANTS - } - - def 'Get delta between 2 anchors where child nodes are added/removed but parent node remains unchanged'() { - def parentNodeXpath = "/bookstore" - when: 'attempt to get delta report between anchors' - def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) - then: 'delta report contains expected number of changes' - result.size() == 11 - and: 'the delta report does not contain parent node xpath' - def xpaths = getDeltaReportEntities(result).get('xpaths') - assert !(xpaths.contains(parentNodeXpath)) + result.size() == 3 + and: 'delta report contains UPDATE action with expected xpath' + assert result[0].getAction() == 'update' + assert result[0].getXpath() == '/bookstore' + and: 'delta report contains REMOVE action with expected xpath' + assert result[1].getAction() == 'remove' + assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" + and: 'delta report contains ADD action with expected xpath' + assert result[2].getAction() == 'add' + assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" } def 'Get delta between 2 anchors returns empty response when #scenario'() { when: 'attempt to get delta report between anchors' - def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS) + def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS) then: 'delta report is empty' assert result.isEmpty() where: 'following data was used' - scenario | sourceAnchor | targetAnchor | xpath - 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_4 | '/' - 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_3 | '/' - 'non existing xpath' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath' + scenario | targetAnchor | xpath + 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_4 | '/' + 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | '/' + 'non existing xpath' | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath' } def 'Get delta between anchors error scenario: #scenario'() { @@ -511,6 +501,64 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { 'is empty' | "/bookstore/container-without-leaves" } + def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() { + when: 'attempt to get delta between leaves of existing data nodes' + def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS) + then: 'expected action is update' + assert result[0].getAction() == 'update' + and: 'the payload has expected leaf values' + def sourceData = result[0].getSourceData() + def targetData = result[0].getTargetData() + assert sourceData == expectedSourceValue + assert targetData == expectedTargetValue + where: 'following data was used' + scenario | sourceAnchor | targetAnchor | xpath || expectedSourceValue | expectedTargetValue + 'leaf is updated in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || ['bookstore-name': 'Easons-1'] | ['bookstore-name': 'Crossword Bookstores'] + 'leaf is removed in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || [price:1] | null + 'leaf is added in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || null | [price:1] + } + + def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() { + when: 'attempt to get delta between leaves of existing data nodes' + def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY) + then: 'expected action is update' + assert result[0].getAction() == 'update' + and: 'the delta report has expected child node xpaths' + def deltaReportEntities = getDeltaReportEntities(result) + def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths') + assert childNodeXpathsInDeltaReport.contains(expectedChildNodeXpath) + where: 'following data was used' + scenario | sourceAnchor | targetAnchor | xpath || expectedChildNodeXpath + 'source and target anchors have child data nodes' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/premises' || '/bookstore/premises/addresses[@house-number=\'2\' and @street=\'Main Street\']' + 'removed child data nodes in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore' || '/bookstore/support-info' + 'added child data nodes in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || '/bookstore/support-info' + } + + def 'Get delta between anchors where source and target data nodes have leaves and child data nodes'() { + given: 'parent node xpath and expected data in delta report' + def parentNodeXpath = "/bookstore/categories[@code='1']" + def expectedSourceDataInParentNode = ['name':'Children'] + def expectedTargetDataInParentNode = ['name':'Kids'] + def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]] + def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[2023, 1988, 2000]]] + when: 'attempt to get delta between leaves of existing data nodes' + def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) + def deltaReportEntities = getDeltaReportEntities(result) + then: 'expected action is update' + assert result[0].getAction() == 'update' + and: 'the payload has expected parent node xpath' + assert deltaReportEntities.get('xpaths').contains(parentNodeXpath) + and: 'delta report has expected source and target data' + assert deltaReportEntities.get('sourcePayload').contains(expectedSourceDataInParentNode) + assert deltaReportEntities.get('targetPayload').contains(expectedTargetDataInParentNode) + and: 'the delta report also has expected child node xpaths' + assert deltaReportEntities.get('xpaths').containsAll(["/bookstore/categories[@code='1']/books[@title='The Gruffalo']", "/bookstore/categories[@code='1']/books[@title='Matilda']"]) + and: 'the delta report also has expected source and target data of child nodes' + assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode) + assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode) + + } + def getDeltaReportEntities(List deltaReport) { def xpaths = [] def action = [] diff --git a/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json index 73b84fc98..1dd6c0d41 100644 --- a/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json +++ b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json @@ -7,7 +7,7 @@ } ], "bookstore": { - "bookstore-name": "Easons", + "bookstore-name": "Crossword Bookstores", "premises": { "addresses": [ { @@ -96,8 +96,7 @@ "title": "Book 1", "lang": "blah", "authors": [], - "editions": [], - "price": 1 + "editions": [] }, { "title": "Book 2",