diff options
6 files changed, 494 insertions, 6 deletions
diff --git a/docs/api/swagger/openapi-datajob.yaml b/docs/api/swagger/openapi-datajob.yaml new file mode 100644 index 00000000..04a5bf1d --- /dev/null +++ b/docs/api/swagger/openapi-datajob.yaml @@ -0,0 +1,214 @@ +openapi: 3.0.3 +info: + description: Support datajobs through one or more subjob for each DMI and Data Producer + Identifier combination + title: NCMP Data Subjob API + version: 1.0.0 +servers: + - url: /dmi +tags: + - description: DMI plugin rest apis + name: dmi-datajob +paths: + /v1/dataJob/{requestId}: + post: + description: Create a read request + operationId: createReadRequest + parameters: + - description: Identifier for the overall Datajob + explode: false + in: path + name: requestId + required: true + schema: + example: some-identifier + type: string + style: simple + requestBody: + content: + application/3gpp-json-patch+json: + schema: + $ref: '#/components/schemas/SubjobRequest' + description: Operation body + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/createReadRequest_200_response' + description: Response for subjob + tags: + - dmi-datajob +components: + parameters: + requestIdInPath: + description: Identifier for the overall Datajob + explode: false + in: path + name: requestId + required: true + schema: + example: some-identifier + type: string + style: simple + schemas: + SubjobRequest: + properties: + dataAcceptType: + description: Defines the data response accept type + example: application/vnd.3gpp.object-tree-hierarchical+json + type: string + dataContentType: + description: Defines the data request content type + example: application/3gpp-json-patch+json + type: string + dataProducerId: + description: ID of the producer registered by DMI for the paths in the operations + in this request + example: my-data-producer-identifier + type: string + data: + $ref: '#/components/schemas/SubjobRequest_data' + required: + - data + - dataProducerId + type: object + ReadOperation: + example: + op: read + operationId: 1 + path: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003/GNBCUCPFunction=2 + attributes: userLabel + scope: + scopeTyp: BASE_ONLY + items: + $ref: '#/components/schemas/ReadOperation_inner' + type: array + WriteOperation: + example: + op: add + path: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003/GNBCUCPFunction=1/EUtraNetwork=1/EUtranFrequency=12 + value: + id: 12 + attributes: + userLabel: label12 + items: + $ref: '#/components/schemas/WriteOperation_inner' + type: array + CmHandleProperties: + description: Private properties of the cm handle for the given path + type: object + Resource: + properties: + id: + description: Identifier of the resource object + example: resource-identifier + type: string + attributes: + additionalProperties: + example: "userLabel: label11" + type: string + description: Key value map representing the objects class attributes and + values + type: object + type: object + ActionParameters: + additionalProperties: + type: string + description: The input of the action in the form of key value pairs + type: object + Object: + type: object + createReadRequest_200_response: + example: + dataProducerJobId: dataProducerJobId + properties: + dataProducerJobId: + description: The data job ID. + type: string + type: object + SubjobRequest_data: + oneOf: + - $ref: '#/components/schemas/ReadOperation' + - $ref: '#/components/schemas/WriteOperation' + ReadOperation_inner: + properties: + path: + description: Defines the resource on which operation is executed + example: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003 + type: string + op: + description: Describes the operation to execute + example: read + type: string + operationId: + description: Unique identifier for the operation within the request + example: "1" + type: string + attributes: + description: This parameter specifies the attributes of the scoped resources + that are returned + items: + example: cellId + type: string + type: array + fields: + description: This parameter specifies the attribute fields of the scoped + resources that are returned + items: + type: string + type: array + filter: + description: This parameter is used to filter the scoped Managed Objects. + Only Managed Objects passing the filter criteria will be fetched + example: NRCellDU/attributes/administrativeState==LOCKED + type: string + scopeType: + description: ScopeType selects MOs depending on relationships with Base + Managed Object + example: BASE_ONLY + type: string + scopeLevel: + description: Only used when the scope type is BASE_NTH_LEVEL to specify + amount of levels to search + example: 0 + type: integer + moduleSetTag: + description: Module set identifier + example: my-module-set-tag + type: string + cmHandleProperties: + description: Private properties of the cm handle for the given path + type: object + required: + - op + - path + type: object + WriteOperation_inner_value: + description: Value dependent on the op specified. Resource for an add. Object + for a replace. ActionParameters for an action. + oneOf: + - $ref: '#/components/schemas/Resource' + - $ref: '#/components/schemas/ActionParameters' + - $ref: '#/components/schemas/Object' + type: object + WriteOperation_inner: + properties: + path: + description: Defines the resource on which operation is executed + example: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003 + type: string + op: + description: Describes the operation to execute + example: add + type: string + operationId: + description: Unique identifier for the operation within the request + example: "1" + type: string + value: + $ref: '#/components/schemas/WriteOperation_inner_value' + required: + - op + - path + type: object diff --git a/openapi/openapi-datajob.yml b/openapi/openapi-datajob.yml new file mode 100644 index 00000000..b572ff3e --- /dev/null +++ b/openapi/openapi-datajob.yml @@ -0,0 +1,203 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2024 Nordix Foundation +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +openapi: 3.0.3 +info: + title: NCMP Data Subjob API + description: Support datajobs through one or more subjob for each DMI and Data Producer Identifier combination + version: 1.0.0 +servers: + - url: /dmi +tags: + - description: DMI plugin rest apis + name: dmi-datajob +paths: + /v1/dataJob/{requestId}: + post: + description: Create a read request + operationId: createReadRequest + parameters: + - $ref: '#/components/parameters/requestIdInPath' + requestBody: + description: Operation body + content: + application/3gpp-json-patch+json: + schema: + $ref: '#/components/schemas/SubjobRequest' + tags: + - dmi-datajob + responses: + "200": + description: Response for subjob + content: + application/json: + schema: + type: object + properties: + dataProducerJobId: + type: string + description: The data job ID. +components: + parameters: + requestIdInPath: + description: Identifier for the overall Datajob + in: path + name: requestId + required: true + schema: + example: some-identifier + type: string + schemas: + SubjobRequest: + type: object + required: + - dataProducerId + - data + properties: + dataAcceptType: + description: Defines the data response accept type + example: application/vnd.3gpp.object-tree-hierarchical+json + type: string + dataContentType: + description: Defines the data request content type + example: application/3gpp-json-patch+json + type: string + dataProducerId: + description: ID of the producer registered by DMI for the paths in the operations in this request + example: my-data-producer-identifier + type: string + data: + oneOf: + - $ref: '#/components/schemas/ReadOperation' + - $ref: '#/components/schemas/WriteOperation' + ReadOperation: + example: + op: read + operationId: 1 + path: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003/GNBCUCPFunction=2 + attributes: userLabel + scope: + scopeTyp: BASE_ONLY + type: array + items: + type: object + required: + - path + - op + properties: + path: + description: Defines the resource on which operation is executed + example: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003 + type: string + op: + description: Describes the operation to execute + example: read + type: string + operationId: + description: Unique identifier for the operation within the request + example: 1 + type: string + attributes: + description: This parameter specifies the attributes of the scoped resources that are returned + type: array + items: + example: cellId + type: string + fields: + description: This parameter specifies the attribute fields of the scoped resources that are returned + type: array + items: + type: string + filter: + description: This parameter is used to filter the scoped Managed Objects. Only Managed Objects passing the filter criteria will be fetched + example: NRCellDU/attributes/administrativeState==LOCKED + type: string + scopeType: + description: ScopeType selects MOs depending on relationships with Base Managed Object + example: BASE_ONLY + type: string + scopeLevel: + description: Only used when the scope type is BASE_NTH_LEVEL to specify amount of levels to search + example: 0 + type: integer + moduleSetTag: + description: Module set identifier + example: my-module-set-tag + type: string + cmHandleProperties: + description: Private properties of the cm handle for the given path + $ref: '#/components/schemas/CmHandleProperties' + WriteOperation: + example: + op: add + path: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003/GNBCUCPFunction=1/EUtraNetwork=1/EUtranFrequency=12 + value: + id: 12 + attributes: + userLabel: label12 + type: array + items: + type: object + required: + - path + - op + properties: + path: + description: Defines the resource on which operation is executed + example: SubNetwork=Europe/SubNetwork=Ireland/MeContext=NR03gNodeBRadio00003/ManagedElement=NR03gNodeBRadio00003 + type: string + op: + description: Describes the operation to execute + example: add + type: string + operationId: + description: Unique identifier for the operation within the request + example: 1 + type: string + value: + description: Value dependent on the op specified. Resource for an add. Object for a replace. ActionParameters for an action. + type: object + oneOf: + - $ref: '#/components/schemas/Resource' + - $ref: '#/components/schemas/ActionParameters' + - $ref: '#/components/schemas/Object' + CmHandleProperties: + description: Private properties of the cm handle for the given path + type: object + Resource: + type: object + properties: + id: + description: Identifier of the resource object + example: resource-identifier + type: string + attributes: + description: Key value map representing the objects class attributes and values + type: object + additionalProperties: + example: 'userLabel: label11' + type: string + ActionParameters: + description: The input of the action in the form of key value pairs + type: object + additionalProperties: + type: string + Object: + type: object + + @@ -1,6 +1,6 @@ <!-- ============LICENSE_START======================================================= - Copyright (c) 2021-2023 Nordix Foundation. + Copyright (c) 2021-2024 Nordix Foundation. Modifications Copyright (C) 2021 Bell Canada. ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); @@ -315,6 +315,7 @@ <version>6.6.0</version> <executions> <execution> + <id>dmi-code-gen</id> <goals> <goal>generate</goal> </goals> @@ -338,6 +339,30 @@ </configuration> </execution> <execution> + <id>dmi-datajob-code-gen</id> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-datajob.yml</inputSpec> + <generatorName>spring</generatorName> + <generateSupportingFiles>false</generateSupportingFiles> + <invokerPackage>org.onap.cps.ncmp.dmi.rest.controller</invokerPackage> + <apiPackage>org.onap.cps.ncmp.dmi.rest.api</apiPackage> + <modelPackage>org.onap.cps.ncmp.dmi.model</modelPackage> + <generateAliasAsModel>true</generateAliasAsModel> + <configOptions> + <sourceFolder>src/gen/java</sourceFolder> + <dateLibrary>java11</dateLibrary> + <interfaceOnly>true</interfaceOnly> + <useTags>true</useTags> + <useSpringBoot3>true</useSpringBoot3> + <openApiNullable>false</openApiNullable> + <skipDefaultInterface>true</skipDefaultInterface> + </configOptions> + </configuration> + </execution> + <execution> <id>openapi-yaml-gen</id> <goals> <goal>generate</goal> @@ -346,6 +371,23 @@ <configuration> <inputSpec>${project.basedir}/openapi/openapi.yml</inputSpec> <generatorName>openapi-yaml</generatorName> + <configOptions> + <outputFile>openapi/openapi.yaml</outputFile> + </configOptions> + </configuration> + </execution> + <execution> + <id>openapi-datajob-yaml-gen</id> + <goals> + <goal>generate</goal> + </goals> + <phase>compile</phase> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-datajob.yml</inputSpec> + <generatorName>openapi-yaml</generatorName> + <configOptions> + <outputFile>openapi/openapi-datajob.yaml</outputFile> + </configOptions> </configuration> </execution> </executions> @@ -365,7 +407,7 @@ <resource> <directory>${project.basedir}/target/generated-sources/openapi/openapi</directory> <includes> - <include>openapi.yaml</include> + <include>openapi*.yaml</include> </includes> </resource> </resources> diff --git a/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java b/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java index ecfef6f3..da6243ca 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java @@ -91,7 +91,7 @@ public class CmNotificationSubscriptionDmiInEventConsumer { if (cmNotificationSubscriptionStatus.equals(CmNotificationSubscriptionStatus.ACCEPTED)) { cmNotificationSubscriptionDmiOutEventData.setStatusCode("1"); cmNotificationSubscriptionDmiOutEventData.setStatusMessage("ACCEPTED"); - } else if (cmNotificationSubscriptionStatus.equals(CmNotificationSubscriptionStatus.REJECTED)) { + } else { cmNotificationSubscriptionDmiOutEventData.setStatusCode("2"); cmNotificationSubscriptionDmiOutEventData.setStatusMessage("REJECTED"); } diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy index 7ca2d54c..12ca05cf 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.dmi.notifications.async import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.dmi.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.dmi.exception.HttpClientRequestException +import org.onap.cps.ncmp.dmi.model.DataAccessRequest import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent import org.spockframework.spring.SpringBean import org.springframework.boot.test.context.SpringBootTest @@ -31,6 +32,7 @@ import org.springframework.test.annotation.DirtiesContext import org.testcontainers.spock.Testcontainers import java.time.Duration +import java.util.function.Supplier @SpringBootTest(classes = [AsyncTaskExecutor, DmiAsyncRequestResponseEventProducer]) @Testcontainers @@ -42,6 +44,7 @@ class AsyncTaskExecutorIntegrationSpec extends MessagingBaseSpec { new DmiAsyncRequestResponseEventProducer(kafkaTemplate) def spiedObjectMapper = Spy(ObjectMapper) + def mockSupplier = Mock(Supplier) def objectUnderTest = new AsyncTaskExecutor(cpsAsyncRequestResponseEventProducer) @@ -83,4 +86,25 @@ class AsyncTaskExecutorIntegrationSpec extends MessagingBaseSpec { assert event.getEventContent().getResponseCode() == '500' } + def 'Execute an Async Task using asyncTaskExecutor and throw an error'() { + given: 'A task to be executed' + def requestId = '123456' + def operationEnum = DataAccessRequest.OperationEnum.CREATE + def timeOut = 100 + when: 'AsyncTask has been executed' + objectUnderTest.executeAsyncTask(taskSupplierForFailingTask(), TEST_TOPIC, requestId, operationEnum, timeOut) + def records = kafkaConsumer.poll(Duration.ofMillis(1500)) + then: 'the record received is the event sent' + def record = records.iterator().next() + DmiAsyncRequestResponseEvent event = spiedObjectMapper.readValue(record.value(), DmiAsyncRequestResponseEvent) + and: 'the status & code matches expected' + assert event.getEventContent().getResponseStatus() == 'Internal Server Error' + assert event.getEventContent().getResponseCode() == '500' + + } + + def taskSupplierForFailingTask() { + return () -> { throw new RuntimeException('original exception message') } + } + }
\ No newline at end of file diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy index 47953439..aa331c4b 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy @@ -73,23 +73,28 @@ class CmNotificationSubscriptionDmiInEventConsumerSpec extends MessagingBaseSpec objectUnderTest.dmiName = 'test-ncmp-dmi' objectUnderTest.cmNotificationSubscriptionResponseTopic = testTopic def correlationId = 'test-subscriptionId#test-ncmp-dmi' - def cmSubscriptionDmiOutEventData = new Data(statusCode: '1', statusMessage: 'ACCEPTED') + def cmSubscriptionDmiOutEventData = new Data(statusCode: subscriptionStatusCode, statusMessage: subscriptionStatusMessage) def subscriptionEventResponse = new CmNotificationSubscriptionDmiOutEvent().withData(cmSubscriptionDmiOutEventData) and: 'consumer has a subscription' kafkaConsumer.subscribe([testTopic] as List<String>) when: 'an event is published' def eventKey = UUID.randomUUID().toString() - objectUnderTest.createAndSendCmNotificationSubscriptionDmiOutEvent(eventKey, "subscriptionCreatedStatus", correlationId, CmNotificationSubscriptionStatus.ACCEPTED) + objectUnderTest.createAndSendCmNotificationSubscriptionDmiOutEvent(eventKey, "subscriptionCreatedStatus", correlationId, subscriptionAcceptanceType) and: 'topic is polled' def records = kafkaConsumer.poll(Duration.ofMillis(1500)) - then: 'poll returns one record' + then: 'poll returns one record and close kafkaConsumer' assert records.size() == 1 def record = records.iterator().next() + kafkaConsumer.close() and: 'the record value matches the expected event value' def expectedValue = objectMapper.writeValueAsString(subscriptionEventResponse) assert expectedValue == record.value assert eventKey == record.key + where: 'given #scenario' + scenario | subscriptionAcceptanceType | subscriptionStatusCode | subscriptionStatusMessage + 'Subscription is Accepted' | CmNotificationSubscriptionStatus.ACCEPTED | '1' | 'ACCEPTED' + 'Subscription is Rejected' | CmNotificationSubscriptionStatus.REJECTED | '2' | 'REJECTED' } def 'Consume valid message.'() { |