Merge "get resource data for operational passthrough"
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml
index fcd91c2..e185bf2 100644
--- a/cps-application/src/main/resources/application.yml
+++ b/cps-application/src/main/resources/application.yml
@@ -72,12 +72,14 @@
data-updated:
enabled: false
topic: ${CPS_CHANGE_EVENT_TOPIC:cps.cfg-state-events}
+ filters:
+ enabled-dataspaces: ${DATASPACE_FILTER_PATTERNS:""}
springdoc:
swagger-ui:
url: /openapi.yml
path: /swagger-ui/index.html
-
+
security:
# comma-separated uri patterns which do not require authorization
permit-uri: /manage/**,/swagger-ui/**,/swagger-resources/**,/v3/api-docs
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index bad66dd..5c79472 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -96,6 +96,13 @@
return new ResponseEntity<>(HttpStatus.OK);
}
+ @Override
+ public ResponseEntity<Void> deleteListNodeElements(final String dataspaceName, final String anchorName,
+ final String listNodeXpath) {
+ cpsDataService.deleteListNodeData(dataspaceName, anchorName, listNodeXpath);
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+
private static boolean isRootXpath(final String xpath) {
return ROOT_XPATH.equals(xpath);
}
diff --git a/cps-rest/src/main/resources/static/cpsData.yml b/cps-rest/src/main/resources/static/cpsData.yml
index 7e9f71d..9c4f333 100644
--- a/cps-rest/src/main/resources/static/cpsData.yml
+++ b/cps-rest/src/main/resources/static/cpsData.yml
@@ -97,6 +97,26 @@
'403':
$ref: 'components.yml#/components/responses/Forbidden'
+ delete:
+ description: Delete list-node child elements under existing node for a given anchor and dataspace
+ tags:
+ - cps-data
+ summary: Delete list-node child element(s) under existing parent node
+ operationId: deleteListNodeElements
+ parameters:
+ - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+ - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+ - $ref: 'components.yml#/components/parameters/requiredXpathInQuery'
+ responses:
+ '204':
+ $ref: 'components.yml#/components/responses/NoContent'
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
+ '401':
+ $ref: 'components.yml#/components/responses/Unauthorized'
+ '403':
+ $ref: 'components.yml#/components/responses/Forbidden'
+
nodesByDataspaceAndAnchor:
post:
description: Create a node for a given anchor and dataspace
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 8675f42..d3d42e3 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -24,6 +24,7 @@
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
@@ -235,4 +236,18 @@
then: 'the java API was called with the correct parameters'
1 * mockCpsDataService.replaceListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData)
}
+
+ def 'Delete list node child elements.'() {
+ given: 'list node xpath'
+ def listNodeXpath = 'list node xpath'
+ when: 'delete is invoked list-node endpoint'
+ def response = mvc.perform(
+ delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-node")
+ .param('xpath', listNodeXpath)
+ ).andReturn().response
+ then: 'a success response is returned'
+ response.status == HttpStatus.NO_CONTENT.value()
+ then: 'the java API was called with the correct parameters'
+ 1 * mockCpsDataService.deleteListNodeData(dataspaceName, anchorName, listNodeXpath)
+ }
}
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
index 6856f7c..c69ead0 100644
--- a/cps-service/pom.xml
+++ b/cps-service/pom.xml
@@ -91,6 +91,10 @@
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+ <dependency>
<!-- For parsing JSON object -->
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java
new file mode 100644
index 0000000..eb75e3f
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Bell Canada. All rights reserved.
+ * ================================================================================
+ * 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.notification;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+@ConfigurationProperties(prefix = "notification.data-updated")
+@Component
+@Data
+@Validated
+public class NotificationProperties {
+
+ @NotNull
+ private String topic;
+ private Map<String, String> filters = Collections.emptyMap();
+ @NotNull
+ private boolean enabled = false;
+}
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 67fc54b..9cb2c52 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
@@ -18,39 +18,56 @@
package org.onap.cps.notification;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class NotificationService {
- private boolean dataUpdatedEventNotificationEnabled;
+ private NotificationProperties notificationProperties;
private NotificationPublisher notificationPublisher;
private CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory;
private NotificationErrorHandler notificationErrorHandler;
+ private List<Pattern> dataspacePatterns;
/**
* Create an instance of Notification Subscriber.
*
- * @param dataUpdatedEventNotificationEnabled notification can be enabled by setting
- * 'notification.data-updated.enabled=true' in application properties
- * @param notificationPublisher notification Publisher
- * @param cpsDataUpdatedEventFactory to create CPSDataUpdatedEvent
- * @param notificationErrorHandler error handler
+ * @param notificationProperties properties for notification
+ * @param notificationPublisher notification Publisher
+ * @param cpsDataUpdatedEventFactory to create CPSDataUpdatedEvent
+ * @param notificationErrorHandler error handler
*/
@Autowired
public NotificationService(
- @Value("${notification.data-updated.enabled}") final boolean dataUpdatedEventNotificationEnabled,
+ final NotificationProperties notificationProperties,
final NotificationPublisher notificationPublisher,
final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory,
final NotificationErrorHandler notificationErrorHandler) {
- this.dataUpdatedEventNotificationEnabled = dataUpdatedEventNotificationEnabled;
+ this.notificationProperties = notificationProperties;
this.notificationPublisher = notificationPublisher;
this.cpsDataUpdatedEventFactory = cpsDataUpdatedEventFactory;
this.notificationErrorHandler = notificationErrorHandler;
+ this.dataspacePatterns = getDataspaceFilterPatterns(notificationProperties);
+ }
+
+ private List<Pattern> getDataspaceFilterPatterns(final NotificationProperties notificationProperties) {
+ if (notificationProperties.isEnabled()) {
+ return Arrays.stream(notificationProperties.getFilters()
+ .getOrDefault("enabled-dataspaces", "")
+ .split(","))
+ .map(filterPattern -> Pattern.compile(filterPattern, Pattern.CASE_INSENSITIVE))
+ .collect(Collectors.toList());
+ } else {
+ return Collections.emptyList();
+ }
}
/**
@@ -62,7 +79,7 @@
public void processDataUpdatedEvent(final String dataspaceName, final String anchorName) {
log.debug("process data updated event for dataspace '{}' & anchor '{}'", dataspaceName, anchorName);
try {
- if (shouldSendNotification()) {
+ if (shouldSendNotification(dataspaceName)) {
final var cpsDataUpdatedEvent =
cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, anchorName);
log.debug("data updated event to be published {}", cpsDataUpdatedEvent);
@@ -80,8 +97,11 @@
/*
Add more complex rules based on dataspace and anchor later
*/
- private boolean shouldSendNotification() {
- return dataUpdatedEventNotificationEnabled;
+ private boolean shouldSendNotification(final String dataspaceName) {
+
+ return notificationProperties.isEnabled()
+ && dataspacePatterns.stream()
+ .anyMatch(pattern -> pattern.matcher(dataspaceName).find());
}
}
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 a742795..b60d093 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
@@ -20,59 +20,76 @@
package org.onap.cps.notification
import org.onap.cps.event.model.CpsDataUpdatedEvent
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Shared
import spock.lang.Specification
+@SpringBootTest
+@EnableConfigurationProperties
+@ContextConfiguration(classes = [NotificationProperties])
class NotificationServiceSpec extends Specification {
- def mockNotificationPublisher = Mock(NotificationPublisher)
- def spyNotificationErrorHandler = Spy(new NotificationErrorHandler())
- def mockCpsDataUpdatedEventFactory = Mock(CpsDataUpdatedEventFactory)
+ @SpringBean
+ NotificationPublisher mockNotificationPublisher = Mock()
+ @SpringBean
+ NotificationErrorHandler spyNotificationErrorHandler = Spy(new NotificationErrorHandler())
+ @SpringBean
+ CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock()
- def objectUnderTest = new NotificationService(true, mockNotificationPublisher,
- mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler)
+ @Autowired
+ NotificationProperties notificationProperties
+ NotificationProperties spyNotificationProperties
- def myDataspaceName = 'my-dataspace'
+ @Shared
+ def myDataspacePublishedName = 'my-dataspace-published'
def myAnchorName = 'my-anchorname'
def 'Skip sending notification when disabled.'() {
-
given: 'notification is disabled'
- objectUnderTest.dataUpdatedEventNotificationEnabled = false
-
+ def objectUnderTest = createNotificationService(false)
when: 'dataUpdatedEvent is received'
- objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
+ objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName)
then: 'the notification is not sent'
0 * mockNotificationPublisher.sendNotification(_)
}
- def 'Send notification when enabled.'() {
-
+ def 'Send notification when enabled: #scenario.'() {
given: 'notification is enabled'
- objectUnderTest.dataUpdatedEventNotificationEnabled = true
+ def objectUnderTest = createNotificationService(true)
and: 'event factory can create event successfully'
def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
- mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspaceName, myAnchorName) >> cpsDataUpdatedEvent
-
+ mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, myAnchorName) >> cpsDataUpdatedEvent
when: 'dataUpdatedEvent is received'
- objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
- then: 'notification is sent with correct event'
- 1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+ objectUnderTest.processDataUpdatedEvent(dataspaceName, myAnchorName)
+ then: 'notification is sent'
+ expectedSendNotificationCount * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+ where:
+ scenario | dataspaceName || expectedSendNotificationCount
+ 'dataspace name does not match filter' | 'does-not-match-pattern' || 0
+ 'dataspace name matches filter' | myDataspacePublishedName || 1
}
- def 'Error handling in notification service.'(){
- given: 'event factory can not create event successfully'
- mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspaceName, myAnchorName) >>
- { throw new Exception("Could not create event") }
-
+ def 'Error handling in notification service.'() {
+ given: 'notification is enabled'
+ def objectUnderTest = createNotificationService(true)
+ and: 'event factory can not create event successfully'
+ mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName) >>
+ { throw new Exception("Could not create event") }
when: 'event is sent for processing'
- objectUnderTest.processDataUpdatedEvent(myDataspaceName, myAnchorName)
-
+ objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName)
then: 'error is handled and not thrown to caller'
notThrown Exception
- 1 * spyNotificationErrorHandler.onException(_,_,_,_)
-
+ 1 * spyNotificationErrorHandler.onException(_, _, _, _)
}
+ NotificationService createNotificationService(boolean notificationEnabled) {
+ spyNotificationProperties = Spy(notificationProperties)
+ spyNotificationProperties.isEnabled() >> notificationEnabled
+ return new NotificationService(spyNotificationProperties, mockNotificationPublisher,
+ mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler)
+ }
}
diff --git a/cps-service/src/test/resources/application.yml b/cps-service/src/test/resources/application.yml
index c934486..94f7e81 100644
--- a/cps-service/src/test/resources/application.yml
+++ b/cps-service/src/test/resources/application.yml
@@ -17,8 +17,10 @@
notification:
data-updated:
- topic: cps-event
+ filters:
+ enabled-dataspaces: ".*-published,.*-important"
enabled: true
+ topic: cps-event
spring:
kafka: