summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json25
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/EventsPublisher.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisher.java)23
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java25
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarder.java106
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsService.java5
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java20
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy55
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarderSpec.groovy96
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisherSpec.groovy7
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsServiceSpec.groovy5
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy4
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy31
-rw-r--r--cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json10
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java37
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java25
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java31
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java59
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java20
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy2
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy48
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy16
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java130
-rw-r--r--cps-service/src/main/java/org/onap/cps/notification/NotificationService.java12
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java2
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java8
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java40
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy39
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy14
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy22
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy8
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy16
32 files changed, 631 insertions, 314 deletions
diff --git a/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json b/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json
index 5ab446cbbe..feff48c36a 100644
--- a/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json
+++ b/cps-ncmp-events/src/main/resources/schemas/avc-subscription-event-v1.json
@@ -29,6 +29,7 @@
"event": {
"description": "The event content.",
"type": "object",
+ "javaType": "InnerSubscriptionEvent",
"properties": {
"subscription": {
"description": "The subscription details.",
@@ -88,12 +89,26 @@
],
"predicates": {
"description": "Additional values to be added into the subscription",
- "existingJavaType" : "java.util.Map<String,Object>",
- "type" : "object"
- }
+ "type" : "object",
+ "properties": {
+ "targets": {
+ "description": "CM Handles to be targeted by the subscription",
+ "type" : "array"
+ },
+ "datastore": {
+ "description": "datastore which is to be used by the subscription",
+ "type": "string"
+ },
+ "xpath-filter": {
+ "description": "filter to be applied to the CM Handles through this event",
+ "type": "string"
+ }
+ },
+ "required": ["datastore"]
}
- },
- "required": [
+ }
+ },
+ "required": [
"subscription",
"dataType"
]
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/EventsPublisher.java
index eda881767d..60d39db7a2 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisher.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/EventsPublisher.java
@@ -18,11 +18,10 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.api.impl.event.lcm;
+package org.onap.cps.ncmp.api.impl.event;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.onap.ncmp.cmhandle.event.lcm.LcmEvent;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
@@ -30,36 +29,36 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
/**
- * LcmEventsPublisher to publish the LcmEvents on event of CREATE, UPDATE and DELETE.
+ * EventsPublisher to publish events.
*/
@Slf4j
@Service
@RequiredArgsConstructor
-public class LcmEventsPublisher {
+public class EventsPublisher<T> {
- private final KafkaTemplate<String, LcmEvent> lcmEventKafkaTemplate;
+ private final KafkaTemplate<String, T> eventKafkaTemplate;
/**
* LCM Event publisher.
*
* @param topicName valid topic name
* @param eventKey message key
- * @param lcmEvent message payload
+ * @param event message payload
*/
- public void publishEvent(final String topicName, final String eventKey, final LcmEvent lcmEvent) {
- final ListenableFuture<SendResult<String, LcmEvent>> lcmEventFuture =
- lcmEventKafkaTemplate.send(topicName, eventKey, lcmEvent);
+ public void publishEvent(final String topicName, final String eventKey, final T event) {
+ final ListenableFuture<SendResult<String, T>> eventFuture =
+ eventKafkaTemplate.send(topicName, eventKey, event);
- lcmEventFuture.addCallback(new ListenableFutureCallback<>() {
+ eventFuture.addCallback(new ListenableFutureCallback<>() {
@Override
public void onFailure(final Throwable throwable) {
log.error("Unable to publish event to topic : {} due to {}", topicName, throwable.getMessage());
}
@Override
- public void onSuccess(final SendResult<String, LcmEvent> sendResult) {
- log.debug("Successfully published event to topic : {} , LcmEvent : {}",
+ public void onSuccess(final SendResult<String, T> sendResult) {
+ log.debug("Successfully published event to topic : {} , Event : {}",
sendResult.getRecordMetadata().topic(), sendResult.getProducerRecord().value());
}
});
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java
index 92949cbb79..d08baac5d4 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumer.java
@@ -22,7 +22,10 @@ package org.onap.cps.ncmp.api.impl.event.avc;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.event.model.InnerSubscriptionEvent;
import org.onap.cps.ncmp.event.model.SubscriptionEvent;
+import org.onap.cps.spi.exceptions.OperationNotYetSupportedException;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@@ -32,6 +35,11 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class SubscriptionEventConsumer {
+ private final SubscriptionEventForwarder subscriptionEventForwarder;
+
+ @Value("${notification.enabled:true}")
+ private boolean notificationFeatureEnabled;
+
/**
* Consume the specified event.
*
@@ -40,12 +48,21 @@ public class SubscriptionEventConsumer {
@KafkaListener(topics = "${app.ncmp.avc.subscription-topic}",
properties = {"spring.json.value.default.type=org.onap.cps.ncmp.event.model.SubscriptionEvent"})
public void consumeSubscriptionEvent(final SubscriptionEvent subscriptionEvent) {
- if ("CM".equals(subscriptionEvent.getEvent().getDataType().getDataCategory())) {
+ final InnerSubscriptionEvent event = subscriptionEvent.getEvent();
+ final String eventDatastore = event.getPredicates().getDatastore();
+ if (!(eventDatastore.equals("passthrough-running") || eventDatastore.equals("passthrough-operational"))) {
+ throw new OperationNotYetSupportedException(
+ "passthrough datastores are currently only supported for event subscriptions");
+ }
+ if ("CM".equals(event.getDataType().getDataCategory())) {
log.debug("Consuming event {} ...", subscriptionEvent);
if ("CREATE".equals(subscriptionEvent.getEventType().value())) {
- log.info("Subscription for ClientID {} with name{} ...",
- subscriptionEvent.getEvent().getSubscription().getClientID(),
- subscriptionEvent.getEvent().getSubscription().getName());
+ log.info("Subscription for ClientID {} with name {} ...",
+ event.getSubscription().getClientID(),
+ event.getSubscription().getName());
+ if (notificationFeatureEnabled) {
+ subscriptionEventForwarder.forwardCreateSubscriptionEvent(subscriptionEvent);
+ }
}
} else {
log.trace("Non-CM subscription event ignored");
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarder.java
new file mode 100644
index 0000000000..635059bfe1
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarder.java
@@ -0,0 +1,106 @@
+/*
+ * ============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.event.avc;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.event.EventsPublisher;
+import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.event.model.SubscriptionEvent;
+import org.onap.cps.spi.exceptions.OperationNotYetSupportedException;
+import org.springframework.stereotype.Component;
+
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class SubscriptionEventForwarder {
+
+ private final InventoryPersistence inventoryPersistence;
+ private final EventsPublisher<SubscriptionEvent> eventsPublisher;
+
+ private static final String DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX = "ncmp-dmi-cm-avc-subscription-";
+
+ /**
+ * Forward subscription event.
+ *
+ * @param subscriptionEvent the event to be forwarded
+ */
+ public void forwardCreateSubscriptionEvent(final SubscriptionEvent subscriptionEvent) {
+ final List<Object> cmHandleTargets = subscriptionEvent.getEvent().getPredicates().getTargets();
+ if (cmHandleTargets == null || cmHandleTargets.isEmpty()
+ || cmHandleTargets.stream().anyMatch(id -> ((String) id).contains("*"))) {
+ throw new OperationNotYetSupportedException(
+ "CMHandle targets are required. \"Wildcard\" operations are not yet supported");
+ }
+ final List<String> cmHandleTargetsAsStrings = cmHandleTargets.stream().map(
+ Objects::toString).collect(Collectors.toList());
+ final Collection<YangModelCmHandle> yangModelCmHandles =
+ inventoryPersistence.getYangModelCmHandles(cmHandleTargetsAsStrings);
+ final Map<String, Map<String, Map<String, String>>> dmiNameCmHandleMap =
+ organizeByDmiName(yangModelCmHandles);
+ dmiNameCmHandleMap.forEach((dmiName, cmHandlePropertiesMap) -> {
+ subscriptionEvent.getEvent().getPredicates().setTargets(Collections.singletonList(cmHandlePropertiesMap));
+ final String eventKey = createEventKey(subscriptionEvent, dmiName);
+ eventsPublisher.publishEvent(DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX + dmiName, eventKey, subscriptionEvent);
+ });
+ }
+
+ private Map<String, Map<String, Map<String, String>>> organizeByDmiName(
+ final Collection<YangModelCmHandle> yangModelCmHandles) {
+ final Map<String, Map<String, Map<String, String>>> dmiNameCmHandlePropertiesMap = new HashMap<>();
+ yangModelCmHandles.forEach(cmHandle -> {
+ final String dmiName = cmHandle.resolveDmiServiceName(RequiredDmiService.DATA);
+ if (!dmiNameCmHandlePropertiesMap.containsKey(dmiName)) {
+ final Map<String, Map<String, String>> cmHandleDmiPropertiesMap = new HashMap<>();
+ cmHandleDmiPropertiesMap.put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle));
+ dmiNameCmHandlePropertiesMap.put(cmHandle.getDmiDataServiceName(), cmHandleDmiPropertiesMap);
+ } else {
+ dmiNameCmHandlePropertiesMap.get(cmHandle.getDmiDataServiceName())
+ .put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle));
+ }
+ });
+ return dmiNameCmHandlePropertiesMap;
+ }
+
+ private String createEventKey(final SubscriptionEvent subscriptionEvent, final String dmiName) {
+ return subscriptionEvent.getEvent().getSubscription().getClientID()
+ + "-"
+ + subscriptionEvent.getEvent().getSubscription().getName()
+ + "-"
+ + dmiName;
+ }
+
+ public Map<String, String> dmiPropertiesAsMap(final YangModelCmHandle yangModelCmHandle) {
+ return yangModelCmHandle.getDmiProperties().stream().collect(
+ Collectors.toMap(YangModelCmHandle.Property::getName, YangModelCmHandle.Property::getValue));
+ }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsService.java
index a94d664de9..2eba83053b 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsService.java
@@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.impl.event.lcm;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.event.EventsPublisher;
import org.onap.ncmp.cmhandle.event.lcm.LcmEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.KafkaException;
@@ -37,7 +38,7 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class LcmEventsService {
- private final LcmEventsPublisher lcmEventsPublisher;
+ private final EventsPublisher<LcmEvent> eventsPublisher;
@Value("${app.lcm.events.topic:ncmp-events}")
private String topicName;
@@ -56,7 +57,7 @@ public class LcmEventsService {
public void publishLcmEvent(final String cmHandleId, final LcmEvent lcmEvent) {
if (notificationsEnabled) {
try {
- lcmEventsPublisher.publishEvent(topicName, cmHandleId, lcmEvent);
+ eventsPublisher.publishEvent(topicName, cmHandleId, lcmEvent);
} catch (final KafkaException e) {
log.error("Unable to publish message to topic : {} and cause : {}", topicName, e.getMessage());
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java
index 0d82bb5c1c..705c9d2664 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java
@@ -22,11 +22,13 @@ package org.onap.cps.ncmp.init;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
import java.util.Map;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsAdminService;
+import org.onap.cps.api.CpsDataService;
import org.onap.cps.api.CpsModuleService;
import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
@@ -42,9 +44,11 @@ public class SubscriptionModelLoader implements ModelLoader {
private final CpsAdminService cpsAdminService;
private final CpsModuleService cpsModuleService;
+ private final CpsDataService cpsDataService;
private static final String SUBSCRIPTION_DATASPACE_NAME = "NCMP-Admin";
private static final String SUBSCRIPTION_ANCHOR_NAME = "AVC-Subscriptions";
private static final String SUBSCRIPTION_SCHEMASET_NAME = "subscriptions";
+ private static final String SUBSCRIPTION_REGISTRY_DATANODE_NAME = "subscription-registry";
@Value("${ncmp.model-loader.subscription:false}")
private boolean subscriptionModelLoaderEnabled;
@@ -76,6 +80,8 @@ public class SubscriptionModelLoader implements ModelLoader {
if (!yangResourceContentMap.get("subscription.yang").isEmpty()) {
createSchemaSet(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_SCHEMASET_NAME, yangResourceContentMap);
createAnchor(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_SCHEMASET_NAME, SUBSCRIPTION_ANCHOR_NAME);
+ createTopLevelDataNode(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+ SUBSCRIPTION_REGISTRY_DATANODE_NAME);
}
}
@@ -116,6 +122,20 @@ public class SubscriptionModelLoader implements ModelLoader {
return true;
}
+ private void createTopLevelDataNode(final String dataspaceName,
+ final String anchorName,
+ final String dataNodeName) {
+ final String nodeData = "{\"" + dataNodeName + "\":{}}";
+ try {
+ cpsDataService.saveData(dataspaceName, anchorName, nodeData, OffsetDateTime.now());
+ } catch (final AlreadyDefinedException exception) {
+ log.info("Creating new data node {} failed as data node already exists", dataNodeName);
+ } catch (final Exception exception) {
+ log.debug("Creating data node for subscription model failed: {}", exception.getMessage());
+ throw new NcmpStartUpException("Creating data node failed", exception.getMessage());
+ }
+ }
+
private String getFileContentAsString() {
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("model/subscription.yang")) {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy
index 20d60e3963..7a9dadebc5 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventConsumerSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (c) 2022 Nordix Foundation.
+ * 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.
@@ -24,29 +24,68 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
import org.onap.cps.ncmp.event.model.SubscriptionEvent
import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.spi.exceptions.OperationNotYetSupportedException
import org.onap.cps.utils.JsonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
-@SpringBootTest(classes = [SubscriptionEventConsumer, ObjectMapper, JsonObjectMapper])
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
class SubscriptionEventConsumerSpec extends MessagingBaseSpec {
- def objectUnderTest = new SubscriptionEventConsumer()
+ def subscriptionEventForwarder = Mock(SubscriptionEventForwarder)
+ def objectUnderTest = new SubscriptionEventConsumer(subscriptionEventForwarder)
@Autowired
JsonObjectMapper jsonObjectMapper
- def 'Consume valid message'() {
+ def 'Consume and forward valid CM create message'() {
+ given: 'an event with data category CM'
+ def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+ def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
+ and: 'notifications are enabled'
+ objectUnderTest.notificationFeatureEnabled = true
+ when: 'the valid event is consumed'
+ objectUnderTest.consumeSubscriptionEvent(testEventSent)
+ then: 'the event is forwarded'
+ 1 * subscriptionEventForwarder.forwardCreateSubscriptionEvent(testEventSent)
+ }
+
+ def 'Consume valid CM create message where notifications are disabled'() {
+ given: 'an event with data category CM'
+ def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+ def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
+ and: 'notifications are disabled'
+ objectUnderTest.notificationFeatureEnabled = false
+ when: 'the valid event is consumed'
+ objectUnderTest.consumeSubscriptionEvent(testEventSent)
+ then: 'the event is forwarded'
+ 0 * subscriptionEventForwarder.forwardCreateSubscriptionEvent(testEventSent)
+ }
+
+ def 'Consume valid FM message'() {
given: 'an event'
def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
- and: 'dataCategory is set'
- testEventSent.getEvent().getDataType().setDataCategory(dataCategory)
+ and: 'dataCategory is set to FM'
+ testEventSent.getEvent().getDataType().setDataCategory("FM")
when: 'the valid event is consumed'
objectUnderTest.consumeSubscriptionEvent(testEventSent)
then: 'no exception is thrown'
noExceptionThrown()
- where: 'data category is changed'
- dataCategory << [ 'CM' , 'FM' ]
+ and: 'No event is forwarded'
+ 0 * subscriptionEventForwarder.forwardCreateSubscriptionEvent(*_)
+ }
+
+ def 'Consume event with wrong datastore causes an exception'() {
+ given: 'an event'
+ def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+ def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
+ and: 'datastore is set to a non passthrough datastore'
+ testEventSent.getEvent().getPredicates().setDatastore("operational")
+ when: 'the valid event is consumed'
+ objectUnderTest.consumeSubscriptionEvent(testEventSent)
+ then: 'an operation not yet supported exception is thrown'
+ thrown(OperationNotYetSupportedException)
}
+
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarderSpec.groovy
new file mode 100644
index 0000000000..f9e801ddbb
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventForwarderSpec.groovy
@@ -0,0 +1,96 @@
+/*
+ * ============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.event.avc
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.impl.event.EventsPublisher
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.inventory.InventoryPersistence
+import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.event.model.SubscriptionEvent
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.spi.exceptions.OperationNotYetSupportedException
+import org.onap.cps.utils.JsonObjectMapper
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.util.concurrent.ListenableFuture;
+
+@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper])
+class SubscriptionEventForwarderSpec extends MessagingBaseSpec {
+
+ def mockInventoryPersistence = Mock(InventoryPersistence)
+ def mockSubscriptionEventPublisher = Mock(EventsPublisher<SubscriptionEvent>)
+ def objectUnderTest = new SubscriptionEventForwarder(mockInventoryPersistence, mockSubscriptionEventPublisher)
+
+ @Autowired
+ JsonObjectMapper jsonObjectMapper
+
+ def 'Forward valid CM create subscription'() {
+ given: 'an event'
+ def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+ def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.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")
+ ]
+ when: 'the valid event is forwarded'
+ objectUnderTest.forwardCreateSubscriptionEvent(testEventSent)
+ then: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future'
+ 1 * mockSubscriptionEventPublisher.publishEvent("ncmp-dmi-cm-avc-subscription-DMIName1", "SCO-9989752-cm-subscription-001-DMIName1",
+ subscriptionEvent -> {
+ Map targets = subscriptionEvent.getEvent().getPredicates().getTargets().get(0)
+ targets["CMHandle1"] == ["shape":"circle"]
+ targets["CMHandle2"] == ["shape":"square"]
+ }
+ )
+ 1 * mockSubscriptionEventPublisher.publishEvent("ncmp-dmi-cm-avc-subscription-DMIName2", "SCO-9989752-cm-subscription-001-DMIName2",
+ subscriptionEvent -> {
+ Map targets = subscriptionEvent.getEvent().getPredicates().getTargets().get(0)
+ targets["CMHandle3"] == ["shape":"triangle"]
+ }
+ )
+ }
+
+ def 'Forward CM create subscription where target CM Handles are #scenario'() {
+ given: 'an event'
+ def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+ def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class)
+ and: 'the target CMHandles are set to #scenario'
+ testEventSent.getEvent().getPredicates().setTargets(invalidTargets)
+ when: 'the event is forwarded'
+ objectUnderTest.forwardCreateSubscriptionEvent(testEventSent)
+ then: 'an operation not yet supported exception is thrown'
+ thrown(OperationNotYetSupportedException)
+ where:
+ scenario | invalidTargets
+ 'null' | null
+ 'empty' | []
+ 'wildcard' | ['CMHandle*']
+ }
+
+ static def createYangModelCmHandleWithDmiProperty(id, dmiId,propertyName, propertyValue) {
+ return new YangModelCmHandle(id:"CMHandle" + id, dmiDataServiceName: "DMIName" + dmiId, dmiProperties: [new YangModelCmHandle.Property(propertyName,propertyValue)])
+ }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisherSpec.groovy
index 61bf33d199..f5b58a7536 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisherSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsPublisherSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * 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.
@@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl.event.lcm
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.kafka.clients.consumer.KafkaConsumer
+import org.onap.cps.ncmp.api.impl.event.EventsPublisher
import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
import org.onap.cps.ncmp.utils.TestUtils
import org.onap.cps.utils.JsonObjectMapper
@@ -35,7 +36,7 @@ import org.testcontainers.spock.Testcontainers
import java.time.Duration
-@SpringBootTest(classes = [LcmEventsPublisher, ObjectMapper, JsonObjectMapper])
+@SpringBootTest(classes = [EventsPublisher, ObjectMapper, JsonObjectMapper])
@Testcontainers
@DirtiesContext
class LcmEventsPublisherSpec extends MessagingBaseSpec {
@@ -45,7 +46,7 @@ class LcmEventsPublisherSpec extends MessagingBaseSpec {
def testTopic = 'ncmp-events-test'
@SpringBean
- LcmEventsPublisher lcmEventsPublisher = new LcmEventsPublisher(kafkaTemplate)
+ EventsPublisher<LcmEvent> lcmEventsPublisher = new EventsPublisher(kafkaTemplate)
@Autowired
JsonObjectMapper jsonObjectMapper
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsServiceSpec.groovy
index ef399e1c65..4c632ddf04 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsServiceSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/lcm/LcmEventsServiceSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * 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.
@@ -20,13 +20,14 @@
package org.onap.cps.ncmp.api.impl.event.lcm
+import org.onap.cps.ncmp.api.impl.event.EventsPublisher
import org.onap.ncmp.cmhandle.event.lcm.LcmEvent
import org.springframework.kafka.KafkaException
import spock.lang.Specification
class LcmEventsServiceSpec extends Specification {
- def mockLcmEventsPublisher = Mock(LcmEventsPublisher)
+ def mockLcmEventsPublisher = Mock(EventsPublisher)
def objectUnderTest = new LcmEventsService(mockLcmEventsPublisher)
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy
index bb0ce8745d..337178e128 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/kafka/MessagingBaseSpec.groovy
@@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.kafka
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.kafka.common.serialization.StringSerializer
+import org.spockframework.spring.SpringBean
import org.springframework.kafka.core.DefaultKafkaProducerFactory
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.support.serializer.JsonSerializer
@@ -62,7 +63,8 @@ class MessagingBaseSpec extends Specification {
]
}
- def kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<Integer, String>(producerConfigProperties()))
+ @SpringBean
+ KafkaTemplate kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<Integer, String>(producerConfigProperties()))
@DynamicPropertySource
static void registerKafkaProperties(DynamicPropertyRegistry dynamicPropertyRegistry) {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy
index 0e647ad871..907208228c 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy
@@ -26,9 +26,11 @@ import ch.qos.logback.core.read.ListAppender
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException
import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
import org.springframework.boot.SpringApplication
import org.slf4j.LoggerFactory
@@ -39,11 +41,13 @@ class SubscriptionModelLoaderSpec extends Specification {
def mockCpsAdminService = Mock(CpsAdminService)
def mockCpsModuleService = Mock(CpsModuleService)
- def objectUnderTest = new SubscriptionModelLoader(mockCpsAdminService, mockCpsModuleService)
+ def mockCpsDataService = Mock(CpsDataService)
+ def objectUnderTest = new SubscriptionModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService)
def SUBSCRIPTION_DATASPACE_NAME = objectUnderTest.SUBSCRIPTION_DATASPACE_NAME;
def SUBSCRIPTION_ANCHOR_NAME = objectUnderTest.SUBSCRIPTION_ANCHOR_NAME;
def SUBSCRIPTION_SCHEMASET_NAME = objectUnderTest.SUBSCRIPTION_SCHEMASET_NAME;
+ def SUBSCRIPTION_REGISTRY_DATANODE_NAME = objectUnderTest.SUBSCRIPTION_REGISTRY_DATANODE_NAME;
def sampleYangContentMap = ['subscription.yang':'module subscription { *sample content* }']
@@ -75,6 +79,8 @@ class SubscriptionModelLoaderSpec extends Specification {
1 * mockCpsModuleService.createSchemaSet(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_SCHEMASET_NAME,sampleYangContentMap)
and: 'the admin service to create an anchor set is called once'
1 * mockCpsAdminService.createAnchor(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_SCHEMASET_NAME, SUBSCRIPTION_ANCHOR_NAME)
+ and: 'the data service to create a top level datanode is called once'
+ 1 * mockCpsDataService.saveData(SUBSCRIPTION_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, '{"' + SUBSCRIPTION_REGISTRY_DATANODE_NAME + '":{}}', _)
}
def 'Create schema set from model file'() {
@@ -138,6 +144,29 @@ class SubscriptionModelLoaderSpec extends Specification {
thrown(NcmpStartUpException)
}
+ def 'Create top level node fails due to an AlreadyDefined exception'() {
+ given: 'the saving of the node data will throw an Already Defined exception'
+ mockCpsDataService.saveData(*_) >>
+ { AlreadyDefinedException.forDataNode('/xpath', "sampleContextName", null) }
+ when: 'the method to onboard model is called'
+ objectUnderTest.onboardSubscriptionModel()
+ then: 'no exception thrown'
+ noExceptionThrown()
+ }
+
+ def 'Create top level node fails due to any other exception'() {
+ given: 'the saving of the node data will throw an exception'
+ mockCpsDataService.saveData(*_) >>
+ { throw new DataValidationException("Invalid JSON", "JSON Data is invalid") }
+ when: 'the method to onboard model is called'
+ objectUnderTest.onboardSubscriptionModel()
+ then: 'the log message contains the correct exception message'
+ def debugMessage = appender.list[0].toString()
+ assert debugMessage.contains("Creating data node for subscription model failed: Invalid JSON")
+ and: 'exception is thrown'
+ thrown(NcmpStartUpException)
+ }
+
def 'Get file content as string'() {
when: 'the method to get yang content is called'
def response = objectUnderTest.getFileContentAsString()
diff --git a/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json b/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json
index 1d84c3a5f2..63fca1f415 100644
--- a/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json
+++ b/cps-ncmp-service/src/test/resources/avcSubscriptionCreationEvent.json
@@ -14,10 +14,10 @@
"schemaVersion": "1.0"
},
"predicates": {
- "datastore": "passthrough-operational",
- "datastore-xpath-filter": "//_3gpp-nr-nrm-gnbdufunction:GNBDUFunction/ ",
- "_3gpp-nr-nrm-nrcelldu": "NRCellDU"
-
- }
+ "targets" : ["CMHandle1", "CMHandle2", "CMHandle3"],
+ "datastore": "passthrough-running",
+ "xpath-filter": "//_3gpp-nr-nrm-gnbdufunction:GNBDUFunction/_3gpp-nr-nrm-nrcelldu:NRCellDU/ | //_3gpp-nr-nrm-gnbcuupfunction:GNBCUUPFunction// | //_3gpp-nr-nrm-gnbcucpfunction:GNBCUCPFunction/_3gpp-nr-nrm-nrcelldu:NRCellCU// | //_3gpp-nr-nrm-nrsectorcarrier:NRSectorCarrier//"
}
+
+}
} \ No newline at end of file
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
index 3ea388242e..4756991138 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
@@ -256,12 +256,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
final Collection<String> xpaths,
final FetchDescendantsOption fetchDescendantsOption) {
final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
- final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpaths);
+ final Collection<FragmentEntity> fragmentEntities =
+ getFragmentEntities(anchorEntity, xpaths, fetchDescendantsOption);
return toDataNodes(fragmentEntities, fetchDescendantsOption);
}
private Collection<FragmentEntity> getFragmentEntities(final AnchorEntity anchorEntity,
- final Collection<String> xpaths) {
+ final Collection<String> xpaths,
+ final FetchDescendantsOption fetchDescendantsOption) {
final Collection<String> nonRootXpaths = new HashSet<>(xpaths);
final boolean haveRootXpath = nonRootXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath);
@@ -273,15 +275,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
}
}
- final Collection<FragmentEntity> fragmentEntities =
- new HashSet<>(fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths));
-
if (haveRootXpath) {
- final List<FragmentExtract> fragmentExtracts = fragmentRepository.findAllExtractsByAnchor(anchorEntity);
- fragmentEntities.addAll(FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts));
+ normalizedXpaths.addAll(fragmentRepository.findAllXpathByAnchorAndParentIdIsNull(anchorEntity));
}
- return fragmentEntities;
+ final List<FragmentExtract> fragmentExtracts =
+ fragmentRepository.findExtractsWithDescendants(anchorEntity.getId(), normalizedXpaths,
+ fetchDescendantsOption.getDepth());
+
+ return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
}
private FragmentEntity getFragmentEntity(final AnchorEntity anchorEntity, final String xpath) {
@@ -325,7 +327,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
if (cpsPathQuery.hasAncestorAxis()) {
- fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
+ final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
+ fragmentEntities = getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption);
}
return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities);
}
@@ -346,19 +349,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex);
fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
if (cpsPathQuery.hasAncestorAxis()) {
- fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
+ final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
+ fragmentEntities = getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption);
}
return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
}
- private Collection<FragmentEntity> getAncestorFragmentEntities(final int anchorId,
- final CpsPathQuery cpsPathQuery,
- final Collection<FragmentEntity> fragmentEntities) {
- final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
- return ancestorXpaths.isEmpty() ? Collections.emptyList()
- : fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorId, ancestorXpaths);
- }
-
private List<DataNode> createDataNodesFromProxiedFragmentEntities(
final FetchDescendantsOption fetchDescendantsOption,
final AnchorEntity anchorEntity,
@@ -490,14 +486,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
@Override
public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
- final List<DataNode> updatedDataNodes) {
+ final Collection<DataNode> updatedDataNodes) {
final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
final Map<String, DataNode> xpathToUpdatedDataNode = updatedDataNodes.stream()
.collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode));
final Collection<String> xpaths = xpathToUpdatedDataNode.keySet();
- final Collection<FragmentEntity> existingFragmentEntities = getFragmentEntities(anchorEntity, xpaths);
+ final Collection<FragmentEntity> existingFragmentEntities =
+ getFragmentEntities(anchorEntity, xpaths, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) {
final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath());
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
index 996e55e777..426a4601c7 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
@@ -37,7 +37,7 @@ import org.springframework.stereotype.Repository;
@Repository
public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,
- FragmentRepositoryMultiPathQuery, FragmentNativeRepository {
+ FragmentNativeRepository {
Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);
@@ -68,9 +68,30 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,
@Param("xpathRegex") String xpathRegex);
- @Query("SELECT xpath FROM FragmentEntity f WHERE anchor = :anchor AND xpath IN :xpaths")
+ @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND xpath IN :xpaths")
List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,
@Param("xpaths") Collection<String> xpaths);
boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);
+
+ @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND parentId IS NULL")
+ List<String> findAllXpathByAnchorAndParentIdIsNull(@Param("anchor") AnchorEntity anchorEntity);
+
+ @Query(value
+ = "WITH RECURSIVE parent_search AS ("
+ + " SELECT id, 0 AS depth "
+ + " FROM fragment "
+ + " WHERE anchor_id = :anchorId AND xpath IN :xpaths "
+ + " UNION "
+ + " SELECT c.id, depth + 1 "
+ + " FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"
+ + " WHERE depth <= (SELECT CASE WHEN :maxDepth = -1 THEN " + Integer.MAX_VALUE + " ELSE :maxDepth END) "
+ + ") "
+ + "SELECT f.id, anchor_id AS anchorId, xpath, f.parent_id AS parentId, CAST(attributes AS TEXT) AS attributes "
+ + "FROM fragment f INNER JOIN parent_search p ON f.id = p.id",
+ nativeQuery = true
+ )
+ List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") int anchorId,
+ @Param("xpaths") Collection<String> xpaths,
+ @Param("maxDepth") int maxDepth);
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java
deleted file mode 100644
index 9c34a459e9..0000000000
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*-
- * ============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.spi.repository;
-
-import java.util.Collection;
-import java.util.List;
-import org.onap.cps.spi.entities.FragmentEntity;
-
-public interface FragmentRepositoryMultiPathQuery {
-
- List<FragmentEntity> findByAnchorAndMultipleCpsPaths(Integer anchorId, Collection<String> cpsPathQuery);
-
-}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java
deleted file mode 100644
index 151fe97b34..0000000000
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java
+++ /dev/null
@@ -1,59 +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.spi.repository;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.transaction.Transactional;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.spi.entities.FragmentEntity;
-
-@Slf4j
-@RequiredArgsConstructor
-public class FragmentRepositoryMultiPathQueryImpl implements FragmentRepositoryMultiPathQuery {
-
- @PersistenceContext
- private final EntityManager entityManager;
-
- private final TempTableCreator tempTableCreator;
-
- @Override
- @Transactional
- public List<FragmentEntity> findByAnchorAndMultipleCpsPaths(final Integer anchorId,
- final Collection<String> cpsPathQueryList) {
- if (cpsPathQueryList.isEmpty()) {
- return Collections.emptyList();
- }
- final String tempTableName = tempTableCreator.createTemporaryTable(
- "xpathTemporaryTable", cpsPathQueryList, "xpath");
- final String sql = String.format(
- "SELECT * FROM FRAGMENT WHERE anchor_id = %d AND xpath IN (select xpath FROM %s);",
- anchorId, tempTableName);
- final List<FragmentEntity> fragmentEntities = entityManager.createNativeQuery(sql, FragmentEntity.class)
- .getResultList();
- log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
- return fragmentEntities;
- }
-}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
index d798932c02..139a8b3063 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
@@ -20,10 +20,8 @@
package org.onap.cps.spi.repository;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -66,24 +64,6 @@ public class TempTableCreator {
return tempTableName;
}
- /**
- * Create a uniquely named temporary table with a single column.
- *
- * @param prefix prefix for the table name (so you can recognize it)
- * @param sqlData data to insert (strings only); each entry is a single row of data
- * @param columnName column name
- * @return a unique temporary table name with given prefix
- */
- public String createTemporaryTable(final String prefix,
- final Collection<String> sqlData,
- final String columnName) {
- final Collection<List<String>> tableData = new ArrayList<>(sqlData.size());
- for (final String entry : sqlData) {
- tableData.add(Collections.singletonList(entry));
- }
- return createTemporaryTable(prefix, tableData, columnName);
- }
-
private static void defineColumns(final StringBuilder sqlStringBuilder, final String[] columnNames) {
sqlStringBuilder.append('(');
final Iterator<String> it = Arrays.stream(columnNames).iterator();
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index 25f19f7a8d..e60afa78df 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -307,7 +307,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
'root xpath' | ["/"] || 7
'empty (root) xpath' | [""] || 7
'root and top-level xpaths' | ["/", "/parent-200", "/parent-201"] || 7
- 'root and child xpaths' | ["/", "/parent-200/child-201"] || 8
+ 'root and child xpaths' | ["/", "/parent-200/child-201"] || 7
}
@Sql([CLEAR_DATA, SET_DATA])
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
index e74b4a7450..3d7003d2a7 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
@@ -26,6 +26,7 @@ import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.entities.AnchorEntity
import org.onap.cps.spi.entities.DataspaceEntity
import org.onap.cps.spi.entities.FragmentEntity
+import org.onap.cps.spi.entities.FragmentExtract
import org.onap.cps.spi.exceptions.ConcurrencyException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.DataNode
@@ -150,9 +151,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
def 'Retrieving multiple data nodes.'() {
given: 'fragment repository returns a collection of fragments'
- def fragmentEntity1 = new FragmentEntity(xpath: '/xpath1', childFragments: [])
- def fragmentEntity2 = new FragmentEntity(xpath: '/xpath2', childFragments: [])
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/xpath1', '/xpath2'] as Set<String>) >> [fragmentEntity1, fragmentEntity2]
+ mockFragmentRepository.findExtractsWithDescendants(123, ['/xpath1', '/xpath2'] as Set, _) >> [
+ mockFragmentExtract(1, null, 123, '/xpath1', null),
+ mockFragmentExtract(2, null, 123, '/xpath2', null)
+ ]
when: 'getting data nodes for 2 xpaths'
def result = objectUnderTest.getDataNodesForMultipleXpaths('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
then: '2 data nodes are returned'
@@ -203,8 +205,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
def 'update data node and descendants: #scenario'(){
given: 'the fragment repository returns fragment entities related to the xpath inputs'
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, [] as Set) >> []
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, ['/test/xpath'] as Set) >> [new FragmentEntity(xpath: '/test/xpath', childFragments: [])]
+ mockFragmentRepository.findExtractsWithDescendants(_, [] as Set, _) >> []
+ mockFragmentRepository.findExtractsWithDescendants(_, ['/test/xpath'] as Set, _) >> [
+ mockFragmentExtract(1, null, 123, '/test/xpath', null)
+ ]
when: 'replace data node tree'
objectUnderTest.updateDataNodesAndDescendants('dataspaceName', 'anchorName', dataNodes)
then: 'call fragment repository save all method'
@@ -217,9 +221,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
def 'update data nodes and descendants'() {
given: 'the fragment repository returns fragment entities related to the xpath inputs'
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, ['/test/xpath1', '/test/xpath2'] as Set) >> [
- new FragmentEntity(xpath: '/test/xpath1', childFragments: [], anchor: anchorEntity),
- new FragmentEntity(xpath: '/test/xpath2', childFragments: [], anchor: anchorEntity)]
+ mockFragmentRepository.findExtractsWithDescendants(123, ['/test/xpath1', '/test/xpath2'] as Set, _) >> [
+ mockFragmentExtract(1, null, 123, '/test/xpath1', null),
+ mockFragmentExtract(2, null, 123, '/test/xpath2', null)
+ ]
and: 'some data nodes with descendants'
def dataNode1 = new DataNode(xpath: '/test/xpath1', leaves: ['id': 'testId1'], childDataNodes: [new DataNode(xpath: '/test/xpath1/child', leaves: ['id': 'childTestId1'])])
def dataNode2 = new DataNode(xpath: '/test/xpath2', leaves: ['id': 'testId2'], childDataNodes: [new DataNode(xpath: '/test/xpath2/child', leaves: ['id': 'childTestId2'])])
@@ -247,26 +252,39 @@ class CpsDataPersistenceServiceSpec extends Specification {
def createDataNodesAndMockRepositoryMethodSupportingThem(Map<String, String> xpathToScenarioMap) {
def dataNodes = []
- def fragmentEntities = []
+ def fragmentExtracts = []
+ def fragmentId = 1
xpathToScenarioMap.each {
def xpath = it.key
def scenario = it.value
def dataNode = new DataNodeBuilder().withXpath(xpath).build()
dataNodes.add(dataNode)
- def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: [])
- fragmentEntities.add(fragmentEntity)
- mockFragmentRepository.getByAnchorAndXpath(_, xpath) >> fragmentEntity
+ def fragmentExtract = mockFragmentExtract(fragmentId, null, null, xpath, null)
+ fragmentExtracts.add(fragmentExtract)
+ def fragmentEntity = new FragmentEntity(id: fragmentId, xpath: xpath, childFragments: [])
+ mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity
if ('EXCEPTION' == scenario) {
mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") }
}
+ fragmentId++
}
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, xpathToScenarioMap.keySet()) >> fragmentEntities
+ mockFragmentRepository.findExtractsWithDescendants(_, xpathToScenarioMap.keySet(), _) >> fragmentExtracts
return dataNodes
}
def mockFragmentWithJson(json) {
- def fragmentEntity = new FragmentEntity(xpath: '/parent-01', childFragments: [], attributes: json)
- mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/parent-01'] as Set<String>) >> [fragmentEntity]
+ def fragmentExtract = mockFragmentExtract(456, null, null, '/parent-01', json)
+ mockFragmentRepository.findExtractsWithDescendants(123, ['/parent-01'] as Set, _) >> [fragmentExtract]
+ }
+
+ def mockFragmentExtract(id, parentId, anchorId, xpath, attributes) {
+ def fragmentExtract = Mock(FragmentExtract)
+ fragmentExtract.getId() >> id
+ fragmentExtract.getParentId() >> parentId
+ fragmentExtract.getAnchorId() >> anchorId
+ fragmentExtract.getXpath() >> xpath
+ fragmentExtract.getAttributes() >> attributes
+ return fragmentExtract
}
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
index 7f4716a7fa..98ff211a60 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
@@ -72,8 +72,8 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
assert countDataNodes(result[0]) == TOTAL_NUMBER_OF_NODES
where: 'the following xPaths are used'
scenario | xpath || allowedDuration
- 'parent' | PERF_TEST_PARENT || 5000
- 'root' | '' || 500
+ 'parent' | PERF_TEST_PARENT || 500
+ 'root' | '/' || 500
}
def 'Query parent data node with many descendants by cps-path'() {
@@ -82,8 +82,8 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
def result = objectUnderTest.queryDataNodes(PERF_DATASPACE, PERF_ANCHOR, '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
- then: 'read duration is under 500 milliseconds'
- recordAndAssertPerformance('Query with many descendants', 500, readDurationInMillis)
+ then: 'read duration is under 350 milliseconds'
+ recordAndAssertPerformance('Query with many descendants', 350, readDurationInMillis)
and: 'data node is returned with all the descendants populated'
assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES
}
@@ -97,8 +97,8 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'the returned number of entities equal to the number of children * number of grandchildren'
assert result.size() == xpathsToAllGrandChildren.size()
- and: 'it took less then 5000ms'
- recordAndAssertPerformance('Find multiple xpaths', 5000, readDurationInMillis)
+ and: 'it took less then 1000ms'
+ recordAndAssertPerformance('Find multiple xpaths', 1000, readDurationInMillis)
}
def 'Query many descendants by cps-path with #scenario'() {
@@ -131,8 +131,8 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
objectUnderTest.updateDataNodesAndDescendants(PERF_DATASPACE, PERF_ANCHOR, dataNodes)
stopWatch.stop()
def updateDurationInMillis = stopWatch.getTotalTimeMillis()
- then: 'update duration is under 900 milliseconds'
- recordAndAssertPerformance('Update data nodes with descendants', 900, updateDurationInMillis)
+ then: 'update duration is under 500 milliseconds'
+ recordAndAssertPerformance('Update data nodes with descendants', 500, updateDurationInMillis)
}
def 'Update data nodes without descendants'() {
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 721d4a9fbb..cd14795ad5 100755
--- 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
@@ -30,9 +30,7 @@ import static org.onap.cps.notification.Operation.UPDATE;
import io.micrometer.core.annotation.Timed;
import java.time.OffsetDateTime;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
@@ -81,10 +79,10 @@ public class CpsDataServiceImpl implements CpsDataService {
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodes(dataspaceName, anchorName, ROOT_NODE_XPATH, nodeData, contentType);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType);
cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
- processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, CREATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, CREATE, observedTimestamp);
}
@Override
@@ -100,10 +98,10 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final OffsetDateTime observedTimestamp,
final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType);
cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, CREATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, CREATE, observedTimestamp);
}
@Override
@@ -112,11 +110,12 @@ public class CpsDataServiceImpl implements CpsDataService {
public void saveListElements(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<DataNode> listElementDataNodeCollection =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
+ buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
listElementDataNodeCollection);
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -125,11 +124,12 @@ public class CpsDataServiceImpl implements CpsDataService {
public void saveListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<String> jsonDataList, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<Collection<DataNode>> listElementDataNodeCollections =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList, ContentType.JSON);
+ buildDataNodes(anchor, parentNodeXpath, jsonDataList, ContentType.JSON);
cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
listElementDataNodeCollections);
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -159,10 +159,11 @@ public class CpsDataServiceImpl implements CpsDataService {
public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
- cpsDataPersistenceService
- .updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ final DataNode dataNode = buildDataNode(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+ cpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(),
+ dataNode.getLeaves());
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -173,13 +174,13 @@ public class CpsDataServiceImpl implements CpsDataService {
final String dataNodeUpdatesAsJson,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<DataNode> dataNodeUpdates =
- buildDataNodes(dataspaceName, anchorName,
- parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
+ buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
- processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
+ processDataNodeUpdate(anchor, dataNodeUpdate);
}
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -210,11 +211,10 @@ public class CpsDataServiceImpl implements CpsDataService {
final String parentNodeXpath, final String jsonData,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
- final ArrayList<DataNode> nodes = new ArrayList<>(dataNodes);
- cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, nodes);
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+ cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -224,11 +224,11 @@ public class CpsDataServiceImpl implements CpsDataService {
final Map<String, String> nodesJsonData,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final List<DataNode> dataNodes = buildDataNodes(dataspaceName, anchorName, nodesJsonData);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData);
cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
nodesJsonData.keySet().forEach(nodeXpath ->
- processDataUpdatedEventAsync(dataspaceName, anchorName, nodeXpath,
- UPDATE, observedTimestamp));
+ processDataUpdatedEventAsync(anchor, nodeXpath, UPDATE, observedTimestamp));
}
@Override
@@ -237,8 +237,9 @@ public class CpsDataServiceImpl implements CpsDataService {
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<DataNode> newListElements =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
+ buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
}
@@ -248,8 +249,9 @@ public class CpsDataServiceImpl implements CpsDataService {
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes);
- processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@Override
@@ -258,8 +260,9 @@ public class CpsDataServiceImpl implements CpsDataService {
public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
- processDataUpdatedEventAsync(dataspaceName, anchorName, dataNodeXpath, DELETE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, dataNodeXpath, DELETE, observedTimestamp);
}
@Override
@@ -269,8 +272,9 @@ public class CpsDataServiceImpl implements CpsDataService {
final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
dataNodeXpaths.forEach(dataNodeXpath ->
- processDataUpdatedEventAsync(dataspaceName, anchorName, dataNodeXpath, DELETE, observedTimestamp));
+ processDataUpdatedEventAsync(anchor, dataNodeXpath, DELETE, observedTimestamp));
}
@Override
@@ -279,7 +283,8 @@ public class CpsDataServiceImpl implements CpsDataService {
public void deleteDataNodes(final String dataspaceName, final String anchorName,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, DELETE, observedTimestamp);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, DELETE, observedTimestamp);
cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
}
@@ -290,8 +295,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName);
cpsValidator.validateNameCharacters(anchorNames);
- for (final String anchorName : anchorNames) {
- processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, DELETE, observedTimestamp);
+ for (final Anchor anchor : cpsAdminService.getAnchors(dataspaceName, anchorNames)) {
+ processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, DELETE, observedTimestamp);
}
cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
}
@@ -302,16 +307,14 @@ public class CpsDataServiceImpl implements CpsDataService {
public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
- processDataUpdatedEventAsync(dataspaceName, anchorName, listNodeXpath, DELETE, observedTimestamp);
+ processDataUpdatedEventAsync(anchor, listNodeXpath, DELETE, observedTimestamp);
}
- private DataNode buildDataNode(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final String nodeData,
+ private DataNode buildDataNode(final Anchor anchor, final String parentNodeXpath, final String nodeData,
final ContentType contentType) {
-
- final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
- final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
+ final SchemaContext schemaContext = getSchemaContext(anchor);
if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
final ContainerNode containerNode = timedYangParser.parseData(contentType, nodeData, schemaContext);
@@ -327,21 +330,15 @@ public class CpsDataServiceImpl implements CpsDataService {
.build();
}
- private List<DataNode> buildDataNodes(final String dataspaceName, final String anchorName,
- final Map<String, String> nodesJsonData) {
+ private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) {
return nodesJsonData.entrySet().stream().map(nodeJsonData ->
- buildDataNode(dataspaceName, anchorName, nodeJsonData.getKey(),
+ buildDataNode(anchor, nodeJsonData.getKey(),
nodeJsonData.getValue(), ContentType.JSON)).collect(Collectors.toList());
}
- private Collection<DataNode> buildDataNodes(final String dataspaceName,
- final String anchorName,
- final String parentNodeXpath,
- final String nodeData,
- final ContentType contentType) {
-
- final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
- final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
+ private Collection<DataNode> 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);
@@ -365,37 +362,38 @@ public class CpsDataServiceImpl implements CpsDataService {
return dataNodes;
}
- private Collection<Collection<DataNode>> buildDataNodes(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final Collection<String> nodeDataList, final ContentType contentType) {
+ private Collection<Collection<DataNode>> buildDataNodes(final Anchor anchor, final String parentNodeXpath,
+ final Collection<String> nodeDataList,
+ final ContentType contentType) {
return nodeDataList.stream()
- .map(nodeData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType))
- .collect(Collectors.toList());
+ .map(nodeData -> buildDataNodes(anchor, parentNodeXpath, nodeData, contentType))
+ .collect(Collectors.toList());
}
- private void processDataUpdatedEventAsync(final String dataspaceName, final String anchorName, final String xpath,
- final Operation operation, final OffsetDateTime observedTimestamp) {
+ private void processDataUpdatedEventAsync(final Anchor anchor, final String xpath,
+ final Operation operation, final OffsetDateTime observedTimestamp) {
try {
- notificationService.processDataUpdatedEvent(dataspaceName, anchorName, xpath, operation, observedTimestamp);
+ notificationService.processDataUpdatedEvent(anchor, xpath, operation, observedTimestamp);
} catch (final Exception exception) {
//If async message can't be queued for notification service, the initial request should not failed.
log.error("Failed to send message to notification service", exception);
}
}
- private SchemaContext getSchemaContext(final String dataspaceName, final String schemaSetName) {
- return yangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName).getSchemaContext();
+ private SchemaContext getSchemaContext(final Anchor anchor) {
+ return yangTextSchemaSourceSetCache
+ .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
}
- private void processDataNodeUpdate(final String dataspaceName, final String anchorName,
- final DataNode dataNodeUpdate) {
+ private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
if (dataNodeUpdate == null) {
return;
}
- cpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, dataNodeUpdate.getXpath(),
- dataNodeUpdate.getLeaves());
+ cpsDataPersistenceService.updateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
+ dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves());
final Collection<DataNode> childDataNodeUpdates = dataNodeUpdate.getChildDataNodes();
for (final DataNode childDataNodeUpdate : childDataNodeUpdates) {
- processDataNodeUpdate(dataspaceName, anchorName, childDataNodeUpdate);
+ processDataNodeUpdate(anchor, childDataNodeUpdate);
}
}
diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
index 7da3a61236..b9d40740e0 100644
--- a/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
+++ b/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (c) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022 Nordix Foundation
+ * Modifications 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.
@@ -70,21 +70,19 @@ public class NotificationService {
/**
* Process Data Updated Event and publishes the notification.
*
- * @param dataspaceName dataspaceName
- * @param anchorName anchorName
+ * @param anchor anchor
* @param xpath xpath of changed data node
* @param operation operation
* @param observedTimestamp observedTimestamp
* @return future
*/
@Async("notificationExecutor")
- public Future<Void> processDataUpdatedEvent(final String dataspaceName, final String anchorName,
- final String xpath, final Operation operation, final OffsetDateTime observedTimestamp) {
+ public Future<Void> processDataUpdatedEvent(final Anchor anchor, final String xpath, final Operation operation,
+ final OffsetDateTime observedTimestamp) {
- final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
log.debug("process data updated event for anchor '{}'", anchor);
try {
- if (shouldSendNotification(dataspaceName)) {
+ if (shouldSendNotification(anchor.getDataspaceName())) {
final var cpsDataUpdatedEvent =
cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor,
observedTimestamp, getRootNodeOperation(xpath, operation));
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index 90e6ec761d..f10443fda8 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -153,7 +153,7 @@ public interface CpsDataPersistenceService {
* @param anchorName anchor name
* @param dataNodes data nodes
*/
- void updateDataNodesAndDescendants(String dataspaceName, String anchorName, final List<DataNode> dataNodes);
+ void updateDataNodesAndDescendants(String dataspaceName, String anchorName, final Collection<DataNode> dataNodes);
/**
* Replaces list content by removing all existing elements and inserting the given new elements
diff --git a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
index cf5e04dc46..02574995dc 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
@@ -76,6 +76,14 @@ public class FetchDescendantsOption {
}
/**
+ * Get depth.
+ * @return depth: -1 for all descendants, 0 for no descendants, or positive value for fixed level of descendants
+ */
+ public int getDepth() {
+ return depth;
+ }
+
+ /**
* get fetch descendants option for given descendant.
*
* @param fetchDescendantsOptionAsString fetch descendants option string
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java
new file mode 100644
index 0000000000..6a4e2a098f
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java
@@ -0,0 +1,40 @@
+/*
+ * ============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.spi.exceptions;
+
+/**
+ * Operation Not Yet Supported Exception.
+ * Indicates the operation is not supported and has intention to be supported in the future.
+ */
+
+public class OperationNotYetSupportedException extends CpsException {
+
+ private static final long serialVersionUID = 1517903069236383746L;
+
+ /**
+ * Constructor.
+ *
+ * @param details reason for the exception
+ */
+ public OperationNotYetSupportedException(final String details) {
+ super("Operation Not Yet Supported Exception", details);
+ }
+}
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 e304d28d8a..faa5d2edbd 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
@@ -61,7 +61,7 @@ class CpsDataServiceImplSpec extends Specification {
def dataspaceName = 'some-dataspace'
def anchorName = 'some-anchor'
def schemaSetName = 'some-schema-set'
- def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
+ def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
def observedTimestamp = OffsetDateTime.now()
def 'Saving multicontainer json data.'() {
@@ -76,7 +76,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp)
where:
index | xpath
0 | '/first-container'
@@ -96,7 +96,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp)
where: 'given parameters'
scenario | dataFile | contentType
'json' | 'test-tree.json' | ContentType.JSON
@@ -129,7 +129,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.CREATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.CREATE, observedTimestamp)
}
def 'Saving list element data fragment under existing node.'() {
@@ -151,7 +151,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
}
def 'Saving collection of a batch with data fragment under existing node.'() {
@@ -171,7 +171,7 @@ class CpsDataServiceImplSpec extends Specification {
}
}
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
}
def 'Saving empty list element data fragment.'() {
@@ -219,7 +219,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
where: 'following parameters were used'
scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves
'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap()
@@ -254,7 +254,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'the data updated event is sent to the notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/bookstore', Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp)
}
def 'Replace data node using singular data node: #scenario.'() {
@@ -266,7 +266,7 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
{ dataNode -> dataNode.xpath[0] == expectedNodeXpath })
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
where: 'following parameters were used'
@@ -284,8 +284,8 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
{ dataNode -> dataNode.xpath == expectedNodeXpath})
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
where: 'following parameters were used'
@@ -313,7 +313,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
}
def 'Replace whole list content with empty list element.'() {
@@ -336,7 +336,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree/branch', Operation.DELETE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp)
}
def 'Delete multiple list elements under existing node.'() {
@@ -349,7 +349,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'two data updated events are sent to notification service'
- 2 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, _, Operation.DELETE, observedTimestamp)
+ 2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp)
}
def 'Delete data node under anchor and dataspace.'() {
@@ -362,7 +362,7 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/data-node', Operation.DELETE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp)
}
def 'Delete all data nodes for a given anchor and dataspace.'() {
@@ -371,7 +371,7 @@ class CpsDataServiceImplSpec extends Specification {
when: 'delete data node method is invoked with correct parameters'
objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
then: 'data updated event is sent to notification service before the delete'
- 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.DELETE, observedTimestamp)
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'the persistence service method is invoked with the correct parameters'
@@ -381,10 +381,13 @@ class CpsDataServiceImplSpec extends Specification {
def 'Delete all data nodes for given dataspace and multiple anchors.'() {
given: 'schema set for given anchors and dataspace references test tree model'
setupSchemaSetMocks('test-tree.yang')
+ mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
+ [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
+ new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
when: 'delete data node method is invoked with correct parameters'
objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
then: 'data updated events are sent to notification service before the delete'
- 2 * mockNotificationService.processDataUpdatedEvent(dataspaceName, _, '/', Operation.DELETE, observedTimestamp)
+ 2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp)
and: 'the CpsValidator is called on the dataspace name and the anchor names'
2 * mockCpsValidator.validateNameCharacters(_)
and: 'the persistence service method is invoked with the correct parameters'
@@ -431,4 +434,4 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
'some-anchorName', 250L)
}
-} \ No newline at end of file
+}
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 8ed7aede62..75f29746d7 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
@@ -90,7 +90,7 @@ class E2ENetworkSliceSpec extends Specification {
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-Cavsta-Data.txt')
and : 'all the further dependencies are mocked '
mockCpsAdminService.getAnchor(dataspaceName, anchorName) >>
- new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).build()
+ new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >>
YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
mockModuleStoreService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext
@@ -123,7 +123,7 @@ class E2ENetworkSliceSpec extends Specification {
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-ran-inventory-data.json')
and : 'all the further dependencies are mocked '
mockCpsAdminService.getAnchor('someDataspace', 'someAnchor') >>
- new Anchor().builder().name('someAnchor').schemaSetName('someSchemaSet').build()
+ new Anchor().builder().name('someAnchor').schemaSetName('someSchemaSet').dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
when: 'saveData method is invoked'
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
index a996195c07..2ef468bb53 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (c) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022 Nordix Foundation
+ * Modifications 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.
@@ -72,7 +72,7 @@ class NotificationServiceSpec extends Specification {
given: 'notification is disabled'
spyNotificationProperties.isEnabled() >> false
when: 'dataUpdatedEvent is received'
- objectUnderTest.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, myObservedTimestamp)
+ objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp)
then: 'the notification is not sent'
0 * mockNotificationPublisher.sendNotification(_)
}
@@ -84,11 +84,9 @@ class NotificationServiceSpec extends Specification {
def anchor = new Anchor('my-anchorname', dataspaceName, 'my-schemaset-name')
and: 'event factory can create event successfully'
def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
- mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >>
- cpsDataUpdatedEvent
+ mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >> cpsDataUpdatedEvent
when: 'dataUpdatedEvent is received'
- def future = objectUnderTest.processDataUpdatedEvent(dataspaceName, anchorName,
- '/', Operation.CREATE, myObservedTimestamp)
+ def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp)
and: 'wait for async processing to complete'
future.get(10, TimeUnit.SECONDS)
then: 'async process completed successfully'
@@ -109,7 +107,7 @@ class NotificationServiceSpec extends Specification {
mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, expectedOperationInEvent) >>
cpsDataUpdatedEvent
when: 'dataUpdatedEvent is received for #xpath'
- def future = objectUnderTest.processDataUpdatedEvent(dataspaceName, anchorName, xpath, operation, myObservedTimestamp)
+ def future = objectUnderTest.processDataUpdatedEvent(anchor, xpath, operation, myObservedTimestamp)
and: 'wait for async processing to complete'
future.get(10, TimeUnit.SECONDS)
then: 'async process completed successfully'
@@ -139,7 +137,7 @@ class NotificationServiceSpec extends Specification {
mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >>
{ throw new Exception("Could not create event") }
when: 'event is sent for processing'
- def future = objectUnderTest.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, myObservedTimestamp)
+ def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp)
and: 'wait for async processing to complete'
future.get(10, TimeUnit.SECONDS)
then: 'async process completed successfully'
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
index 6fb6d844a9..e75f1dce36 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy
@@ -20,6 +20,8 @@
package org.onap.cps.integration.performance.base
+import org.onap.cps.spi.FetchDescendantsOption
+
import java.time.OffsetDateTime
import org.onap.cps.integration.base.CpsIntegrationSpecBase
import org.onap.cps.rest.utils.MultipartFileUtil
@@ -44,11 +46,21 @@ class CpsPerfTestBase extends PerfTestBase {
}
def createInitialData() {
+ createWarmupData()
createLargeBookstoresData()
addOpenRoadModel()
addOpenRoadData()
}
+ def createWarmupData() {
+ def data = "{\"bookstore\":{}}"
+ stopWatch.start()
+ addAnchorsWithData(1, CpsIntegrationSpecBase.BOOKSTORE_SCHEMA_SET, 'warmup', data)
+ stopWatch.stop()
+ def durationInMillis = stopWatch.getTotalTimeMillis()
+ recordAndAssertPerformance('Creating warmup anchor with tiny data tree', 250, durationInMillis)
+ }
+
def createLargeBookstoresData() {
def data = CpsIntegrationSpecBase.readResourceDataFile('bookstore/largeModelData.json')
stopWatch.start()
@@ -80,7 +92,17 @@ class CpsPerfTestBase extends PerfTestBase {
cpsAdminService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetName, anchorNamePrefix + it)
cpsDataService.saveData(CPS_PERFORMANCE_TEST_DATASPACE, anchorNamePrefix + it, data, OffsetDateTime.now())
}
+ }
+ def 'Warm the database'() {
+ when: 'get data nodes for warmup anchor'
+ stopWatch.start()
+ def result = cpsDataService.getDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'warmup1', '/', FetchDescendantsOption.OMIT_DESCENDANTS)
+ assert countDataNodesInTree(result) == 1
+ stopWatch.stop()
+ def durationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'all data is read within 15 seconds (warm up not critical)'
+ recordAndAssertPerformance("Warming database", 15_000, durationInMillis)
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
index 753faf44f1..30e8bf23d4 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy
@@ -42,10 +42,10 @@ class GetPerfTest extends CpsPerfTestBase {
recordAndAssertPerformance("Read datatrees using ${scenario}", durationLimit, durationInMillis)
where: 'the following xpaths are used'
scenario | anchorPrefix | xpath || durationLimit | expectedNumberOfDataNodes
- 'bookstore root' | 'bookstore' | '/' || 25_000 | 78
- 'bookstore top element' | 'bookstore' | '/bookstore' || 1_000 | 78
- 'openroadm root' | 'openroadm' | '/' || 1_000 | 2151
- 'openroadm top element' | 'openroadm' | '/openroadm-devices' || 10_000 | 2151
+ 'bookstore root' | 'bookstore' | '/' || 130 | 78
+ 'bookstore top element' | 'bookstore' | '/bookstore' || 130 | 78
+ 'openroadm root' | 'openroadm' | '/' || 750 | 2151
+ 'openroadm top element' | 'openroadm' | '/openroadm-devices' || 750 | 2151
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
index 939281a73c..87327030c7 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
@@ -21,12 +21,11 @@
package org.onap.cps.integration.performance.ncmp
import java.util.stream.Collectors
-
+import org.onap.cps.integration.performance.base.NcmpRegistryPerfTestBase
+import org.springframework.dao.DataAccessResourceFailureException
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import org.onap.cps.integration.performance.base.NcmpRegistryPerfTestBase
-
class CmHandleQueryPerfTest extends NcmpRegistryPerfTestBase {
def objectUnderTest
@@ -52,14 +51,13 @@ class CmHandleQueryPerfTest extends NcmpRegistryPerfTestBase {
assert countDataNodesInTree(result) == 5 * 999
}
- def 'Multiple get limitation: 32,764 (~ 2^15) xpaths.'() {
+ def 'Multiple get limit exceeded: 32,764 (~ 2^15) xpaths.'() {
given: 'more than 32,764 xpaths)'
- def xpaths = []
- (0..32_765).each { xpaths.add("/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']") }
- when: 'get single get is executed to get all the parent objects and their descendants'
+ def xpaths = (0..32_764).collect(i -> "/size/of/this/path/does/not/matter/for/limit[@id='" + i + "']")
+ when: 'single get is executed to get all the parent objects and their descendants'
cpsDataService.getDataNodesForMultipleXpaths(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, xpaths, INCLUDE_ALL_DESCENDANTS)
- then: 'no exception is thrown (limit is not present in current implementation)'
- noExceptionThrown()
+ then: 'an exception is thrown'
+ thrown(DataAccessResourceFailureException.class)
}
}