diff options
23 files changed, 253 insertions, 301 deletions
diff --git a/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json new file mode 100644 index 0000000000..18f83ccf86 --- /dev/null +++ b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps:data-updated-event-schema:1.0.0", + "$ref": "#/definitions/CpsDataUpdatedEvent", + "definitions": { + "CpsDataUpdatedEvent": { + "description": "The payload for CPS data updated event.", + "type": "object", + "javaType": "org.onap.cps.events.model.CpsDataUpdatedEvent", + "properties": { + "data": { + "type": "object", + "properties": { + "observedTimestamp": { + "description": "The timestamp when the data has been observed. The expected format is 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'. Ex: '2020-12-01T00:00:00.000+0000' ", + "type": "string" + }, + "dataspaceName": { + "description": "The name of CPS Core dataspace the data belongs to.", + "type": "string" + }, + "schemaSetName": { + "description": "The name of CPS Core schema set the data adheres to.", + "type": "string" + }, + "anchorName": { + "description": "The name of CPS Core anchor the data is attached to.", + "type": "string" + }, + "operation": { + "description": "The operation on the data", + "type": "string", + "enum": [ + "CREATE", + "UPDATE", + "DELETE" + ] + }, + "xpath": { + "description": "xpath of the updated content", + "type": "string" + } + }, + "required": [ + "observedTimestamp", + "dataspaceName", + "schemaSetName", + "anchorName", + "operation", + "xpath" + ], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "data" + ] + } + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmSubscriptionEventCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfig.java index 8b429d44c9..1d6da90a9a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmSubscriptionEventCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfig.java @@ -24,26 +24,26 @@ import com.hazelcast.config.MapConfig; import com.hazelcast.map.IMap; import java.util.Map; import org.onap.cps.cache.HazelcastCacheConfig; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmSubscriptionCacheObject; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration -public class CmSubscriptionEventCacheConfig extends HazelcastCacheConfig { +public class CmNotificationSubscriptionCacheConfig extends HazelcastCacheConfig { - private static final MapConfig cmSubscriptionEventCacheMapConfig = - createMapConfig("cmSubscriptionEventCacheMapConfig"); + private static final MapConfig cmNotificationSubscriptionCacheMapConfig = + createMapConfig("cmNotificationSubscriptionCacheMapConfig"); /** - * Distributed instance of cm subscription information + * Distributed instance of cm notification subscription information * cache that contains subscription id as key * and incoming event data processed per dmi plugin. * * @return configured map of subscription events. */ @Bean - public IMap<String, Map<String, CmSubscriptionCacheObject>> cmSubscriptionEventCache() { - return createHazelcastInstance("hazelCastInstanceCmSubscriptionEvents", - cmSubscriptionEventCacheMapConfig).getMap("cmSubscriptionEventCache"); + public IMap<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache() { + return createHazelcastInstance("hazelCastInstanceCmNotificationSubscription", + cmNotificationSubscriptionCacheMapConfig).getMap("cmNotificationSubscriptionCache"); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionStatus.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmNotificationSubscriptionStatus.java index 0bc3cbe93d..68d54fac95 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionStatus.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmNotificationSubscriptionStatus.java @@ -20,13 +20,13 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.model; -public enum CmSubscriptionStatus { +public enum CmNotificationSubscriptionStatus { ACCEPTED("ACCEPTED"), REJECTED("REJECTED"), PENDING("PENDING"); - private final String cmSubscriptionStatusValue; + private final String cmNotificationSubscriptionStatusValue; - CmSubscriptionStatus(final String cmSubscriptionStatusValue) { - this.cmSubscriptionStatusValue = cmSubscriptionStatusValue; + CmNotificationSubscriptionStatus(final String cmNotificationSubscriptionStatusValue) { + this.cmNotificationSubscriptionStatusValue = cmNotificationSubscriptionStatusValue; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionPredicate.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionPredicate.java deleted file mode 100644 index 262126ef14..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionPredicate.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * ============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.ncmp.api.impl.events.cmsubscription.model; - -import java.util.List; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class CmSubscriptionPredicate { - - private List<String> targetFilter; - private ScopeFilter scopeFilter; - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionCacheObject.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionDetails.java index 2888a6734b..4f6caefce7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/CmSubscriptionCacheObject.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionDetails.java @@ -26,8 +26,8 @@ import lombok.Setter; @Getter @Setter -public class CmSubscriptionCacheObject { +public class DmiCmNotificationSubscriptionDetails { - private List<CmSubscriptionPredicate> cmSubscriptionPredicates; - private CmSubscriptionStatus cmSubscriptionStatus; + private List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates; + private CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/ScopeFilter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionPredicate.java index b9ca68761f..65365808d2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/ScopeFilter.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/model/DmiCmNotificationSubscriptionPredicate.java @@ -27,8 +27,10 @@ import org.onap.cps.ncmp.api.impl.operations.DatastoreType; @Getter @Setter -public class ScopeFilter { +public class DmiCmNotificationSubscriptionPredicate { + private List<String> targetCmHandleIds; private DatastoreType datastoreType; - private List<String> xpathFilters; + private List<String> xpaths; + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java index 723a3032a5..189fbb53d9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java @@ -20,9 +20,10 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service; +import java.util.Collection; import org.onap.cps.ncmp.api.impl.operations.DatastoreType; -public interface CmSubscriptionService { +public interface CmNotificationSubscriptionPersistenceService { String NCMP_DATASPACE_NAME = "NCMP-Admin"; String CM_SUBSCRIPTIONS_ANCHOR_NAME = "cm-data-subscriptions"; @@ -35,5 +36,17 @@ public interface CmSubscriptionService { * @param xpath valid xpath * @return true for ongoing cmsubscription , otherwise false */ - boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId, final String xpath); + boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId, + final String xpath); + + /** + * Get all ongoing cm notification subscription based on the parameters. + * + * @param datastoreType valid datastore type + * @param cmHandleId cmhandle id + * @param xpath valid xpath + * @return collection of subscription ids of ongoing cm notification subscription + */ + Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType, + final String cmHandleId, final String xpath); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java index 011397adb3..ca9adb36b2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java @@ -21,9 +21,11 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service; import java.util.Collection; +import java.util.Collections; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsQueryService; import org.onap.cps.ncmp.api.impl.operations.DatastoreType; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; @@ -32,23 +34,33 @@ import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor -public class CmSubscriptionServiceImpl implements CmSubscriptionService { +public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService { private static final String IS_ONGOING_CM_SUBSCRIPTION_CPS_PATH_QUERY = """ /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters/filter[@xpath='%s']"""; - private final CpsDataService cpsDataService; + private final CpsQueryService cpsQueryService; @Override - public boolean isOngoingCmSubscription(final DatastoreType datastoreType, final String cmHandleId, + public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId, final String xpath) { + return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty(); + } + + @Override + public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType, + final String cmHandleId, final String xpath) { + final String isOngoingCmSubscriptionCpsPathQuery = IS_ONGOING_CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId, escapeQuotesByDoublingThem(xpath)); final Collection<DataNode> existingNodes = - cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, + cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, isOngoingCmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS); - return !existingNodes.isEmpty(); + if (existingNodes.isEmpty()) { + return Collections.emptyList(); + } + return (List<String>) existingNodes.iterator().next().getLeaves().get("subscribers"); } private static String escapeQuotesByDoublingThem(final String inputXpath) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationService.java deleted file mode 100644 index 6bf509349d..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationService.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * ============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.ncmp.api.impl.events.cmsubscription.service; - - -public interface CmSubscriptionValidationService { - - /** - * Validate against the allowed datastores. - * - * @param incomingDatastore Datastore from the incoming CmSubscription event from client - * @return true if valid datastore , otherwise false - */ - boolean isValidDataStore(final String incomingDatastore); - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImpl.java deleted file mode 100644 index 697366258d..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * ============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.ncmp.api.impl.events.cmsubscription.service; - -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 java.util.Arrays; -import java.util.List; -import org.springframework.stereotype.Service; - -@Service -public class CmSubscriptionValidationServiceImpl implements CmSubscriptionValidationService { - - private static final List<String> validDatastores = - Arrays.asList(PASSTHROUGH_RUNNING.getDatastoreName(), PASSTHROUGH_OPERATIONAL.getDatastoreName()); - - - @Override - public boolean isValidDataStore(final String incomingDatastore) { - return validDatastores.contains(incomingDatastore); - } - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy new file mode 100644 index 0000000000..a3f41c8ef4 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============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.ncmp.api.impl.config.embeddedcache + +import com.hazelcast.core.Hazelcast +import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate +import org.onap.cps.ncmp.api.impl.operations.DatastoreType +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import spock.lang.Specification + +@SpringBootTest(classes = [CmNotificationSubscriptionCacheConfig]) +class CmNotificationSubscriptionCacheConfigSpec extends Specification { + + @Autowired + IMap<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache; + + def 'Embedded (hazelcast) cache for Cm Notification Subscription Cache.'() { + expect: 'system is able to create an instance of the Cm Notification Subscription Cache' + assert null != cmNotificationSubscriptionCache + and: 'there is at least 1 instance' + assert Hazelcast.allHazelcastInstances.size() > 0 + and: 'Cm Notification Subscription Cache is present' + assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCmNotificationSubscription') + } + + def 'Provided CM Subscription data'() { + given: 'a cm subscription properties' + def subscriptionId = 'sub123' + def dmiPluginName = 'dummydmi' + def cmSubscriptionPredicate = new DmiCmNotificationSubscriptionPredicate(targetCmHandleIds: ['cmhandle1', 'cmhandle2'], datastoreType: DatastoreType.PASSTHROUGH_RUNNING, xpaths: ['/a/b/c']) + def cmSubscriptionCacheObject = new DmiCmNotificationSubscriptionDetails(dmiCmNotificationSubscriptionPredicates: [cmSubscriptionPredicate], cmNotificationSubscriptionStatus: CmNotificationSubscriptionStatus.PENDING) + when: 'the cache is populated' + cmNotificationSubscriptionCache.put(subscriptionId, [(dmiPluginName): cmSubscriptionCacheObject]) + then: 'the values are present in memory' + assert cmNotificationSubscriptionCache.get(subscriptionId) != null + and: 'properties match' + assert dmiPluginName == cmNotificationSubscriptionCache.get(subscriptionId).keySet()[0] + assert cmSubscriptionCacheObject.cmNotificationSubscriptionStatus == cmNotificationSubscriptionCache.get(subscriptionId).values().cmNotificationSubscriptionStatus[0] + assert cmSubscriptionCacheObject.dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds == cmNotificationSubscriptionCache.get(subscriptionId).values().dmiCmNotificationSubscriptionPredicates[0].targetCmHandleIds[0] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmSubscriptionEventCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmSubscriptionEventCacheConfigSpec.groovy deleted file mode 100644 index f1eae14d96..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmSubscriptionEventCacheConfigSpec.groovy +++ /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.config.embeddedcache - -import com.hazelcast.core.Hazelcast -import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmSubscriptionCacheObject -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmSubscriptionStatus -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.ScopeFilter -import org.onap.cps.ncmp.api.impl.operations.DatastoreType -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [CmSubscriptionEventCacheConfig]) -class CmSubscriptionEventCacheConfigSpec extends Specification { - - @Autowired - IMap<String, Map<String, CmSubscriptionCacheObject>> cmSubscriptionEventCache; - - def 'Embedded (hazelcast) cache for Cm Subscription Event Cache.'() { - expect: 'system is able to create an instance of the Forwarded Subscription Event Cache' - assert null != cmSubscriptionEventCache - and: 'there is at least 1 instance' - assert Hazelcast.allHazelcastInstances.size() > 0 - and: 'Forwarded Subscription Event Cache is present' - assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCmSubscriptionEvents') - } - - def 'Provided CM Subscription data'() { - given: 'a cm subscription properties' - def subscriptionId = 'sub123' - def dmiPluginName = 'dummydmi' - def cmSubscriptionPredicate = new CmSubscriptionPredicate(targetFilter: ['cmhandle1', 'cmhandle2'], scopeFilter: new ScopeFilter(datastoreType: DatastoreType.PASSTHROUGH_RUNNING, xpathFilters: ['/a/b/c'])) - def cmSubscriptionCacheObject = new CmSubscriptionCacheObject(cmSubscriptionPredicates: [cmSubscriptionPredicate] , cmSubscriptionStatus: CmSubscriptionStatus.PENDING) - when: 'the cache is populated' - cmSubscriptionEventCache.put(subscriptionId, [(dmiPluginName): cmSubscriptionCacheObject]) - then: 'the values are present in memory' - assert cmSubscriptionEventCache.get(subscriptionId) != null - and: 'properties match' - assert dmiPluginName == cmSubscriptionEventCache.get(subscriptionId).keySet()[0] - assert cmSubscriptionCacheObject.cmSubscriptionStatus == cmSubscriptionEventCache.get(subscriptionId).values().cmSubscriptionStatus[0] - assert cmSubscriptionCacheObject.cmSubscriptionPredicates[0].targetFilter == cmSubscriptionEventCache.get(subscriptionId).values().cmSubscriptionPredicates[0].targetFilter[0] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy index ae52a4af17..f6103ef3ba 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy @@ -20,31 +20,32 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service -import org.onap.cps.api.CpsDataService + +import org.onap.cps.api.CpsQueryService import org.onap.cps.ncmp.api.impl.operations.DatastoreType import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import spock.lang.Specification -class CmSubscriptionServiceImplSpec extends Specification { +class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification { - def mockCpsDataService = Mock(CpsDataService) + def mockCpsQueryService = Mock(CpsQueryService) - def objectUnderTest = new CmSubscriptionServiceImpl(mockCpsDataService) + def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(mockCpsQueryService) def 'Check ongoing cm subscription #scenario'() { given: 'a valid cm subscription query' def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']"; and: 'datanodes optionally returned' - 1 * mockCpsDataService.getDataNodes('NCMP-Admin', 'cm-data-subscriptions', + 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode when: 'we check for an ongoing cm subscription' - def response = objectUnderTest.isOngoingCmSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path') + def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path') then: 'we get expected response' assert response == isOngoingCmSubscription where: 'following scenarios are used' - scenario | dataNode || isOngoingCmSubscription - 'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscribers': 'sub-1'])] || true - 'no datanodes present' | [] || false + scenario | dataNode || isOngoingCmSubscription + 'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscribers': ['sub-1', 'sub-2']])] || true + 'no datanodes present' | [] || false } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImplSpec.groovy deleted file mode 100644 index e7a6965500..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmSubscriptionValidationServiceImplSpec.groovy +++ /dev/null @@ -1,39 +0,0 @@ -/* - * ============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.ncmp.api.impl.events.cmsubscription.service - -import spock.lang.Specification - -class CmSubscriptionValidationServiceImplSpec extends Specification { - - def objectUnderTest = new CmSubscriptionValidationServiceImpl() - - def 'Validate datastore #datastore for Cm Subscription'() { - when: 'we check against incoming datastore' - def result = objectUnderTest.isValidDataStore(datastore) - then: 'the datastores are validated for the use case' - assert result == isValid - where: 'following datastores are checked' - scenario | datastore || isValid - 'Valid datastore' | 'ncmp-datastore:passthrough-running' || true - 'Invalid datastore' | 'invalid-ds' || false - } -} diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index 2e81b004e1..6e7554bf5e 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -3,7 +3,7 @@ ============LICENSE_START======================================================= Copyright (c) 2021 Pantheon.tech. Modifications Copyright (C) 2021 Bell Canada. - Modifications Copyright (C) 2021-2023 Nordix Foundation. + Modifications Copyright (C) 2021-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. @@ -339,7 +339,10 @@ <include>**/*Test.java</include> <!-- Just in case of having also "normal" JUnit tests --> </includes> <excludes> - <exclude>**/IT*.java</exclude> + <!-- maven-failsafe-plugin will run performance tests in the integration-test module, + so performance tests will not affect Jacoco coverage (jacoco-report module is configured + to aggregate results from tests run with maven-surefire-plugin only) --> + <exclude>**/*PerfTest.java</exclude> </excludes> <environmentVariables> <!-- diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index b040af5bb4..9859acdf0e 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021 Bell Canada. All rights reserved. * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022-2023 Nordix Foundation. + * Modifications Copyright (C) 2022-2024 Nordix Foundation. * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,6 +34,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.utils.YangUtils; +import org.opendaylight.yangtools.yang.common.Ordering; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; @@ -242,10 +243,14 @@ public class DataNodeBuilder { private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) { final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); - final List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body()) + List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body()) .stream() - .map(normalizedNode -> (normalizedNode).body()) - .collect(Collectors.toUnmodifiableList()); + .map(NormalizedNode::body) + .collect(Collectors.toList()); + if (leafSetNode.ordering() == Ordering.SYSTEM) { + leafListValues.sort(null); + } + leafListValues = Collections.unmodifiableList(leafListValues); addYangLeaf(currentDataNode, leafListName, (Serializable) leafListValues); } diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot index f506b28011..e83857caea 100644 --- a/csit/tests/cps-data/cps-data.robot +++ b/csit/tests/cps-data/cps-data.robot @@ -59,10 +59,7 @@ Get Updated Data Node by XPath Should Be Equal As Strings ${responseJson['name']} Bigger ${length_birds}= Get Length ${responseJson['birds']} Should Be Equal As Integers ${length_birds} 3 - ${expected_list}= Create List Pigeon Falcon Eagle - FOR ${item_to_check} IN @{expected_list} - Should Contain ${responseJson['birds']} ${item_to_check} - END + Should Be Equal As Strings ${responseJson['birds']} ['Eagle', 'Falcon', 'Pigeon'] Get Data Node by XPath ${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors/${anchorName}/node diff --git a/docs/deployment.rst b/docs/deployment.rst index ca7824d6dc..3b5aad1bfc 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -333,7 +333,7 @@ Below are the list of distributed datastructures that we have. +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | moduleSetTagCacheMapConfig | Stores the module set tags for cm handles. | +--------------+------------------------------------+-----------------------------------------------------------+ -| cps-ncmp | cmSubscriptionEventCache | Stores and tracks cm notification subscription requests. | +| cps-ncmp | cmNotificationSubscriptionCache | Stores and tracks cm notification subscription requests. | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | alternateIdPerCmHandleId | Stores the alternate id for each cm handle id. | +--------------+------------------------------------+-----------------------------------------------------------+ diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 73998cd135..6947a94aaa 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -26,7 +26,6 @@ <version>3.4.4-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> - <modelVersion>4.0.0</modelVersion> <artifactId>integration-test</artifactId> @@ -90,49 +89,28 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-failsafe-plugin</artifactId> - <configuration> - <argLine>-Xms512m -Xmx512m</argLine> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <argLine>-Xms512m -Xmx512m</argLine> - </configuration> - </plugin> - </plugins> - </build> - <profiles> + <!-- Performance tests are run with maven-failsafe-plugin using a separate profile, so they will + not affect Jacoco coverage. Heap size is set here to ensure consistent test environment. --> <profile> - <id>default</id> - <activation> - <activeByDefault>true</activeByDefault> - </activation> + <id>include-performance</id> + <properties> + <failsafeArgLine>-Xms512m -Xmx512m</failsafeArgLine> + </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> + <artifactId>maven-failsafe-plugin</artifactId> <configuration> - <excludes> - <exclude>%regex[.*PerfTest.*]</exclude> - </excludes> + <includes> + <include>**/*PerfTest.java</include> + </includes> </configuration> </plugin> </plugins> </build> </profile> - <profile> - <id>include-performance</id> - </profile> </profiles> - </project> diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy index 64996536e6..f967c62037 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy @@ -423,8 +423,34 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { then: 'the updated data nodes are retrieved' def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS) and: 'the leaf values are updated as expected' - assert result.leaves['lang'] == ['English/French'] - assert result.leaves['price'] == [100] + assert result[0].leaves['lang'] == 'English/French' + assert result[0].leaves['price'] == 100 + cleanup: + restoreBookstoreDataAnchor(2) + } + + def 'Order of leaf-list elements is preserved when "ordered-by user" is set in the YANG model.'() { + given: 'Updated json for bookstore data' + def jsonData = "{'book-store:books':{'title':'Matilda', 'authors': ['beta', 'alpha', 'gamma', 'delta']}}" + when: 'update is performed for leaves' + objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now) + and: 'the updated data nodes are retrieved' + def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS) + then: 'the leaf-list values have expected order' + assert result[0].leaves['authors'] == ['beta', 'alpha', 'gamma', 'delta'] + cleanup: + restoreBookstoreDataAnchor(2) + } + + def 'Leaf-list elements are sorted when "ordered-by user" is not set in the YANG model.'() { + given: 'Updated json for bookstore data' + def jsonData = "{'book-store:books':{'title':'Matilda', 'editions': [2011, 1988, 2001, 2022, 2025]}}" + when: 'update is performed for leaves' + objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now) + and: 'the updated data nodes are retrieved' + def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS) + then: 'the leaf-list values have natural order' + assert result[0].leaves['editions'] == [1988, 2001, 2011, 2022, 2025] cleanup: restoreBookstoreDataAnchor(2) } @@ -540,7 +566,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def expectedSourceDataInParentNode = ['name':'Children'] def expectedTargetDataInParentNode = ['name':'Kids'] def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]] - def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[2023, 1988, 2000]]] + def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[1988, 2000, 2023]]] when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) def deltaReportEntities = getDeltaReportEntities(result) @@ -555,7 +581,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { assert deltaReportEntities.get('xpaths').containsAll(["/bookstore/categories[@code='1']/books[@title='The Gruffalo']", "/bookstore/categories[@code='1']/books[@title='Matilda']"]) and: 'the delta report also has expected source and target data of child nodes' assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode) - //assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode) CPS-2057 + assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode) } def getDeltaReportEntities(List<DeltaReport> deltaReport) { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy index 3807a14bc5..b7b6fa11a7 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy @@ -36,8 +36,8 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { CpsModuleService objectUnderTest private static def originalNumberOfModuleReferences = 2 // bookstore has two modules - private static def bookStoreModuleReference = new ModuleReference('stores','2024-01-30') - private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-01-30', 'org:onap:cps:sample') + private static def bookStoreModuleReference = new ModuleReference('stores','2024-02-08') + private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-02-08', 'org:onap:cps:sample') private static def bookStoreTypesModuleReference = new ModuleReference('bookstore-types','2024-01-30') private static def bookStoreTypesModuleReferenceWithNamespace = new ModuleReference('bookstore-types','2024-01-30', 'org:onap:cps:types:sample') static def NEW_RESOURCE_REVISION = '2023-05-10' @@ -155,7 +155,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { def result = objectUnderTest.getModuleDefinitionsByAnchorName(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1) then: 'the correct module definitions are returned' assert result.size() == 2 - assert result.contains(new ModuleDefinition('stores','2024-01-30',bookstoreModelFileContent)) + assert result.contains(new ModuleDefinition('stores','2024-02-08',bookstoreModelFileContent)) assert result.contains(new ModuleDefinition('bookstore-types','2024-01-30', bookstoreTypesFileContent)) } @@ -165,12 +165,12 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { then: 'the correct module definitions are returned' if (expectedNumberOfDefinitions > 0) { assert result.size() == expectedNumberOfDefinitions - def expectedModuleDefinition = new ModuleDefinition('stores', '2024-01-30', bookstoreModelFileContent) + def expectedModuleDefinition = new ModuleDefinition('stores', '2024-02-08', bookstoreModelFileContent) assert result[0] == expectedModuleDefinition } where: 'following parameters are used' scenarios | moduleName | moduleRevision || expectedNumberOfDefinitions - 'correct module name and revision' | 'stores' | '2024-01-30' || 1 + 'correct module name and revision' | 'stores' | '2024-02-08' || 1 'correct module name' | 'stores' | null || 1 'incorrect module name' | 'other' | null || 0 'incorrect revision' | 'stores' | '2025-11-22' || 0 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy index b455e69c36..ce0aab45b0 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/PerfTestBase.groovy @@ -21,7 +21,6 @@ package org.onap.cps.integration.performance.base import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.springframework.util.StopWatch abstract class PerfTestBase extends CpsIntegrationSpecBase { diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang index 2abde656d4..0d093ea36c 100644 --- a/integration-test/src/test/resources/data/bookstore/bookstore.yang +++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang @@ -9,6 +9,11 @@ module stores { revision-date 2024-01-30; } + revision "2024-02-08" { + description + "Order of book authors is preserved"; + } + revision "2024-01-30" { description "Extracted bookstore types"; @@ -107,6 +112,7 @@ module stores { type string; } leaf-list authors { + ordered-by user; type string; } leaf-list editions { |